$darkmode
map.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2020 Robert Bosch GmbH
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * SPDX-License-Identifier: Apache-2.0
17  */
24 #pragma once
25 
26 #include <limits> // for numeric_limits<>
27 #include <map> // for map<>
28 #include <optional> // for optional<>
29 #include <regex> // for regex, regex_match
30 #include <string> // for string
31 #include <utility> // for move
32 #include <vector> // for vector<>
33 
34 #include <fable/schema/interface.hpp> // for Base<>
35 
36 namespace fable::schema {
37 
46 template <typename T, typename P>
47 class Map : public Base<Map<T, P>> {
48  public: // Types and Constructors
49  using Type = std::map<std::string, T>;
50  using PrototypeSchema = P;
51 
52  Map(Type* ptr, std::string desc) : Map(ptr, make_prototype<T>(), std::move(desc)) {}
53 
54  Map(Type* ptr, PrototypeSchema prototype)
55  : Base<Map<T, P>>(JsonType::object), prototype_(std::move(prototype)), ptr_(ptr) {
56  prototype_.reset_ptr();
57  }
58 
59  Map(Type* ptr, PrototypeSchema prototype, std::string desc)
60  : Base<Map<T, P>>(JsonType::object, std::move(desc))
61  , prototype_(std::move(prototype))
62  , ptr_(ptr) {
63  prototype_.reset_ptr();
64  }
65 
66  public: // Special
67  [[nodiscard]] bool unique_properties() const { return unique_properties_; }
68  Map<T, P> unique_properties(bool value) && {
69  unique_properties_ = value;
70  return std::move(*this);
71  }
72 
73  [[nodiscard]] const std::vector<std::string>& required() const { return required_; }
74  Map<T, P> require_properties(const std::vector<std::string>& values) && {
75  required_ = values;
76  return std::move(*this);
77  }
78  Map<T, P> require_property(const std::string& value) && {
79  required_.emplace_back(value);
80  return std::move(*this);
81  }
82 
83  [[nodiscard]] const std::string& pattern() const { return pattern_; }
84  Map<T, P> pattern(const std::string& value) && {
85  pattern_ = value;
86  return std::move(*this);
87  }
88 
89  public: // Overrides
90  [[nodiscard]] Json json_schema() const override {
91  Json j{
92  {"type", "object"},
93  {"additionalProperties", prototype_.json_schema()},
94  };
95  if (!required_.empty()) {
96  j["required"] = required_;
97  }
98  if (min_properties_ != 0) {
99  j["minProperties"] = min_properties_;
100  }
101  if (max_properties_ != std::numeric_limits<size_t>::max()) {
102  j["maxProperties"] = max_properties_;
103  }
104  if (!pattern_.empty()) {
105  j["propertyNames"]["pattern"] = pattern_;
106  }
107  this->augment_schema(j);
108  return j;
109  }
110 
111  bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
112  if (!this->validate_type(c, err)) {
113  return false;
114  }
115 
116  if (c->size() < min_properties_) {
117  return this->set_error(err, c, "expect at least {} properties, got {}", max_properties_, c->size());
118  }
119  assert(required_.size() <= max_properties_);
120  if (c->size() > max_properties_) {
121  return this->set_error(err, c, "expect at most {} properties, got {}", max_properties_, c->size());
122  }
123 
124  for (const auto& k : required_) {
125  if (!c.has(k)) {
126  return this->set_error(err, c, "missing property: {}", k);
127  }
128  }
129 
130  std::optional<std::regex> pattern;
131  if (!pattern_.empty()) {
132  try {
133  *pattern = std::regex(pattern_);
134  } catch (std::regex_error& e) {
135  // NOTE: This is actually a programmer error, and we should probably catch
136  // it at the point where the pattern is set.
137  return this->set_error(err, c, "invalid regex '{}': {}", pattern_, e.what());
138  }
139  }
140  for (const auto& kv : c->items()) {
141  if (!prototype_.validate(c.at(kv.key()), err)) {
142  return false;
143  }
144  if (pattern && !std::regex_match(kv.key(), *pattern)) {
145  return this->set_error(err, c, "expect property name to match regex '{}': {}", pattern_, kv.key());
146  }
147  }
148  return true;
149  }
150 
151  using Interface::to_json;
152  void to_json(Json& j) const override {
153  assert(ptr_ != nullptr);
154  j = serialize(*ptr_);
155  }
156 
157  void from_conf(const Conf& c) override {
158  assert(ptr_ != nullptr);
159  for (const auto& i : c->items()) {
160  const auto& key = i.key();
161  if (unique_properties_ && ptr_->count(key)) {
162  throw this->error(c, "key {} has already been defined", key);
163  }
164  ptr_->insert(std::make_pair(key, deserialize_item(c, key)));
165  }
166  }
167 
168  [[nodiscard]] Json serialize(const Type& x) const {
169  Json j;
170  serialize_into(j, x);
171  return j;
172  }
173 
174  [[nodiscard]] Type deserialize(const Conf& c) const {
175  Type tmp;
176  deserialize_into(c, tmp);
177  return tmp;
178  }
179 
180  void serialize_into(Json& j, const Type& x) const {
181  for (const auto& kv : x) {
182  j[kv.first] = prototype_.serialize(kv.second);
183  }
184  }
185 
186  void deserialize_into(const Conf& c, Type& x) const {
187  for (const auto& i : c->items()) {
188  const auto& key = i.key();
189  x.insert(std::make_pair(key, deserialize_item(c, key)));
190  }
191  }
192 
193  T deserialize_item(const Conf& c, const std::string& key) const {
194  return prototype_.deserialize(c.at(key));
195  }
196 
197  void reset_ptr() override { ptr_ = nullptr; }
198 
199  private:
200  bool unique_properties_{true};
201  size_t min_properties_{0};
202  size_t max_properties_{std::numeric_limits<size_t>::max()};
203  std::string pattern_{};
204  std::vector<std::string> required_{};
205  PrototypeSchema prototype_{};
206  Type* ptr_{nullptr};
207 };
208 
209 template <typename T, typename P>
210 Map<T, P> make_schema(std::map<std::string, T>* ptr, P prototype, std::string desc) {
211  return Map<T, P>(ptr, std::move(prototype), std::move(desc));
212 }
213 
214 template <typename T>
215 Map<T, decltype(make_prototype<T>())> make_schema(std::map<std::string, T>* ptr, std::string desc) {
216  return Map<T, decltype(make_prototype<T>())>(ptr, std::move(desc));
217 }
218 
219 } // namespace fable::schema
Definition: conf.hpp:76
bool has(const std::string &key) const
Definition: conf.hpp:132
Conf at(const std::string &key) const
Definition: conf.cpp:58
Definition: interface.hpp:398
bool validate_type(const Conf &c, std::optional< SchemaError > &err) const
Definition: interface.hpp:459
virtual Json to_json() const
Definition: interface.hpp:254
Definition: map.hpp:47
void reset_ptr() override
Definition: map.hpp:197
void to_json(Json &j) const override
Definition: map.hpp:152
void from_conf(const Conf &c) override
Definition: map.hpp:157
Json json_schema() const override
Definition: map.hpp:90
bool validate(const Conf &c, std::optional< SchemaError > &err) const override
Definition: map.hpp:111
nlohmann::json Json
Definition: fable_fwd.hpp:35