$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 = std::remove_cv_t<std::remove_reference_t<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)), prototype_(std::move(prototype)), ptr_(ptr) {
61  prototype_.reset_ptr();
62  }
63 
64  public: // Special
65  [[nodiscard]] bool unique_properties() const { return unique_properties_; }
66  [[nodiscard]] Map<T, P> unique_properties(bool value) && {
67  set_unique_properties(value);
68  return std::move(*this);
69  }
70  void set_unique_properties(bool value) {
71  unique_properties_ = value;
72  }
73 
74  [[nodiscard]] const std::vector<std::string>& required_properties() const { return required_; }
75  [[nodiscard]] Map<T, P> require_properties(const std::vector<std::string>& values) && {
76  set_required_properties(values);
77  return std::move(*this);
78  }
79  [[nodiscard]] Map<T, P> require_property(const std::string& value) && {
80  add_required_property(value);
81  return std::move(*this);
82  }
83  void set_required_properties(const std::vector<std::string>& values) {
84  required_ = values;
85  }
86  void add_required_property(const std::string& value) {
87  required_.emplace_back(value);
88  }
89 
90  [[nodiscard]] const std::string& pattern() const { return pattern_; }
91  [[nodiscard]] Map<T, P> pattern(const std::string& value) && {
92  pattern_ = value;
93  return std::move(*this);
94  }
95  void set_pattern(const std::string& value) {
96  pattern_ = value;
97  }
98 
99  public: // Overrides
100  [[nodiscard]] Json json_schema() const override {
101  Json j{
102  {"type", "object"},
103  {"additionalProperties", prototype_.json_schema()},
104  };
105  if (!required_.empty()) {
106  j["required"] = required_;
107  }
108  if (min_properties_ != 0) {
109  j["minProperties"] = min_properties_;
110  }
111  if (max_properties_ != std::numeric_limits<size_t>::max()) {
112  j["maxProperties"] = max_properties_;
113  }
114  if (!pattern_.empty()) {
115  j["propertyNames"]["pattern"] = pattern_;
116  }
117  this->augment_schema(j);
118  return j;
119  }
120 
121  bool validate(const Conf& c, std::optional<SchemaError>& err) const override {
122  if (!this->validate_type(c, err)) {
123  return false;
124  }
125 
126  if (c->size() < min_properties_) {
127  return this->set_error(err, c, "expect at least {} properties, got {}", max_properties_, c->size());
128  }
129  assert(required_.size() <= max_properties_);
130  if (c->size() > max_properties_) {
131  return this->set_error(err, c, "expect at most {} properties, got {}", max_properties_, c->size());
132  }
133 
134  for (const auto& k : required_) {
135  if (!c.has(k)) {
136  return this->set_error(err, c, "missing property: {}", k);
137  }
138  }
139 
140  std::optional<std::regex> pattern;
141  if (!pattern_.empty()) {
142  try {
143  pattern.emplace(pattern_);
144  } catch (std::regex_error& e) {
145  // NOTE: This is actually a programmer error, and we should probably catch
146  // it at the point where the pattern is set.
147  return this->set_error(err, c, "invalid regex '{}': {}", pattern_, e.what());
148  }
149  }
150  for (const auto& kv : c->items()) {
151  if (!prototype_.validate(c.at(kv.key()), err)) {
152  return false;
153  }
154  if (pattern && !std::regex_match(kv.key(), *pattern)) {
155  return this->set_error(err, c, "expect property name to match regex '{}': {}", pattern_, kv.key());
156  }
157  }
158  return true;
159  }
160 
161  using Interface::to_json;
162  void to_json(Json& j) const override {
163  assert(ptr_ != nullptr);
164  j = serialize(*ptr_);
165  }
166 
167  void from_conf(const Conf& c) override {
168  assert(ptr_ != nullptr);
169  for (const auto& i : c->items()) {
170  const auto& key = i.key();
171  if (unique_properties_ && ptr_->count(key)) {
172  throw this->error(c, "key {} has already been defined", key);
173  }
174  ptr_->insert(std::make_pair(key, deserialize_item(c, key)));
175  }
176  }
177 
178  [[nodiscard]] Json serialize(const Type& x) const {
179  Json j;
180  serialize_into(j, x);
181  return j;
182  }
183 
184  [[nodiscard]] Type deserialize(const Conf& c) const {
185  Type tmp;
186  deserialize_into(c, tmp);
187  return tmp;
188  }
189 
190  void serialize_into(Json& j, const Type& x) const {
191  for (const auto& kv : x) {
192  j[kv.first] = prototype_.serialize(kv.second);
193  }
194  }
195 
196  void deserialize_into(const Conf& c, Type& x) const {
197  for (const auto& i : c->items()) {
198  const auto& key = i.key();
199  x.insert(std::make_pair(key, deserialize_item(c, key)));
200  }
201  }
202 
203  [[nodiscard]] T deserialize_item(const Conf& c, const std::string& key) const {
204  return prototype_.deserialize(c.at(key));
205  }
206 
207  void reset_ptr() override { ptr_ = nullptr; }
208 
209  private:
210  bool unique_properties_{true};
211  size_t min_properties_{0};
212  size_t max_properties_{std::numeric_limits<size_t>::max()};
213  std::string pattern_{};
214  std::vector<std::string> required_{};
215  PrototypeSchema prototype_{};
216  Type* ptr_{nullptr};
217 };
218 
219 template <typename T, typename P, typename S>
220 Map<T, P> make_schema(std::map<std::string, T>* ptr, P&& prototype, S&& desc) {
221  return {ptr, std::forward<P>(prototype), std::forward<S>(desc)};
222 }
223 
224 template <typename T, typename S>
225 Map<T, decltype(make_prototype<T>())> make_schema(std::map<std::string, T>* ptr, S&& desc) {
226  return {ptr, std::forward<S>(desc)};
227 }
228 
229 } // namespace fable::schema
Definition: conf.hpp:82
bool has(const std::string &key) const
Definition: conf.hpp:166
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:207
void to_json(Json &j) const override
Definition: map.hpp:162
void from_conf(const Conf &c) override
Definition: map.hpp:167
Json json_schema() const override
Definition: map.hpp:100
bool validate(const Conf &c, std::optional< SchemaError > &err) const override
Definition: map.hpp:121
nlohmann::json Json
Definition: fable_fwd.hpp:35