/src/valijson/include/valijson/subschema.hpp
Line | Count | Source |
1 | | #pragma once |
2 | | |
3 | | #include <functional> |
4 | | #include <memory> |
5 | | #include <optional> |
6 | | #include <vector> |
7 | | |
8 | | #include <valijson/constraints/constraint.hpp> |
9 | | #include <valijson/exceptions.hpp> |
10 | | |
11 | | namespace valijson { |
12 | | |
13 | | /** |
14 | | * Represents a sub-schema within a JSON Schema |
15 | | * |
16 | | * While all JSON Schemas have at least one sub-schema, the root, some will |
17 | | * have additional sub-schemas that are defined as part of constraints that are |
18 | | * included in the schema. For example, a 'oneOf' constraint maintains a set of |
19 | | * references to one or more nested sub-schemas. As per the definition of a |
20 | | * oneOf constraint, a document is valid within that constraint if it validates |
21 | | * against one of the nested sub-schemas. |
22 | | */ |
23 | | class Subschema |
24 | | { |
25 | | public: |
26 | | |
27 | | /// Typedef for custom new-/malloc-like function |
28 | | typedef void * (*CustomAlloc)(size_t size); |
29 | | |
30 | | /// Typedef for custom free-like function |
31 | | typedef void (*CustomFree)(void *); |
32 | | |
33 | | /// Typedef the Constraint class into the local namespace for convenience |
34 | | typedef constraints::Constraint Constraint; |
35 | | |
36 | | /// Typedef for a function that can be applied to each of the Constraint |
37 | | /// instances owned by a Schema. |
38 | | typedef std::function<bool (const Constraint &)> ApplyFunction; |
39 | | |
40 | | // Disable copy construction |
41 | | Subschema(const Subschema &) = delete; |
42 | | |
43 | | // Disable copy assignment |
44 | | Subschema & operator=(const Subschema &) = delete; |
45 | | |
46 | | /** |
47 | | * @brief Move construct a new Subschema |
48 | | * |
49 | | * @param other Subschema that is moved into the new Subschema |
50 | | */ |
51 | | Subschema(Subschema &&other) |
52 | | : m_allocFn(other.m_allocFn), |
53 | | m_freeFn(other.m_freeFn), |
54 | | m_alwaysInvalid(std::move(other.m_alwaysInvalid)), |
55 | | m_constraints(std::move(other.m_constraints)), |
56 | | m_description(std::move(other.m_description)), |
57 | | m_id(std::move(other.m_id)), |
58 | 0 | m_title(std::move(other.m_title)) { } |
59 | | |
60 | | /** |
61 | | * @brief Move assign a Subschema |
62 | | * |
63 | | * @param other Subschema that is move assigned to this Subschema |
64 | | * @return Subschema& |
65 | | */ |
66 | | Subschema & operator=(Subschema &&other) |
67 | 0 | { |
68 | 0 | // Swaps all members |
69 | 0 | std::swap(m_allocFn, other.m_allocFn); |
70 | 0 | std::swap(m_freeFn, other.m_freeFn); |
71 | 0 | std::swap(m_alwaysInvalid, other.m_alwaysInvalid); |
72 | 0 | std::swap(m_constraints, other.m_constraints); |
73 | 0 | std::swap(m_description, other.m_description); |
74 | 0 | std::swap(m_id, other.m_id); |
75 | 0 | std::swap(m_title, other.m_title); |
76 | 0 |
|
77 | 0 | return *this; |
78 | 0 | } |
79 | | |
80 | | /** |
81 | | * @brief Construct a new Subschema object |
82 | | */ |
83 | | Subschema() |
84 | 624k | : m_allocFn([](size_t size) { return ::operator new(size, std::nothrow); }) |
85 | 462k | , m_freeFn(::operator delete) |
86 | 462k | , m_alwaysInvalid(false) { } |
87 | | |
88 | | /** |
89 | | * @brief Construct a new Subschema using custom memory management |
90 | | * functions |
91 | | * |
92 | | * @param allocFn malloc- or new-like function to allocate memory |
93 | | * within Schema, such as for Subschema instances |
94 | | * @param freeFn free-like function to free memory allocated with |
95 | | * the `customAlloc` function |
96 | | */ |
97 | | Subschema(CustomAlloc allocFn, CustomFree freeFn) |
98 | | : m_allocFn(allocFn) |
99 | | , m_freeFn(freeFn) |
100 | | , m_alwaysInvalid(false) |
101 | 0 | { |
102 | 0 | // explicitly initialise optionals. See: https://github.com/tristanpenman/valijson/issues/124 |
103 | 0 | m_description = std::nullopt; |
104 | 0 | m_id = std::nullopt; |
105 | 0 | m_title = std::nullopt; |
106 | 0 | } |
107 | | |
108 | | /** |
109 | | * @brief Clean up and free all memory managed by the Subschema |
110 | | */ |
111 | | virtual ~Subschema() |
112 | 462k | { |
113 | 462k | #if VALIJSON_USE_EXCEPTIONS |
114 | 462k | try { |
115 | 462k | #endif |
116 | 462k | m_constraints.clear(); |
117 | 462k | #if VALIJSON_USE_EXCEPTIONS |
118 | 462k | } catch (const std::exception &e) { |
119 | 0 | fprintf(stderr, "Caught an exception in Subschema destructor: %s", |
120 | 0 | e.what()); |
121 | 0 | } |
122 | 462k | #endif |
123 | 462k | } |
124 | | |
125 | | /** |
126 | | * @brief Add a constraint to this sub-schema |
127 | | * |
128 | | * The constraint will be copied before being added to the list of |
129 | | * constraints for this Subschema. Note that constraints will be copied |
130 | | * only as deep as references to other Subschemas - e.g. copies of |
131 | | * constraints that refer to sub-schemas, will continue to refer to the |
132 | | * same Subschema instances. |
133 | | * |
134 | | * @param constraint Reference to the constraint to copy |
135 | | */ |
136 | | void addConstraint(const Constraint &constraint) |
137 | 211k | { |
138 | | // the vector allocation might throw but the constraint memory will be taken care of anyways |
139 | 211k | m_constraints.push_back(constraint.clone(m_allocFn, m_freeFn)); |
140 | 211k | } |
141 | | |
142 | | /** |
143 | | * @brief Invoke a function on each child Constraint |
144 | | * |
145 | | * This function will apply the callback function to each constraint in |
146 | | * the Subschema, even if one of the invocations returns \c false. However, |
147 | | * if one or more invocations of the callback function return \c false, |
148 | | * this function will also return \c false. |
149 | | * |
150 | | * @returns \c true if all invocations of the callback function are |
151 | | * successful, \c false otherwise |
152 | | */ |
153 | | bool apply(ApplyFunction &applyFunction) const |
154 | 1.03M | { |
155 | 1.03M | bool allTrue = true; |
156 | 1.03M | for (auto &&constraint : m_constraints) { |
157 | | // Even if an application fails, we want to continue checking the |
158 | | // schema. In that case we set allTrue to false, and then fall |
159 | | // through to the next constraint |
160 | 922k | if (!applyFunction(*constraint)) { |
161 | 77.9k | allTrue = false; |
162 | 77.9k | } |
163 | 922k | } |
164 | | |
165 | 1.03M | return allTrue; |
166 | 1.03M | } |
167 | | |
168 | | /** |
169 | | * @brief Invoke a function on each child Constraint |
170 | | * |
171 | | * This is a stricter version of the apply() function that will return |
172 | | * immediately if any of the invocations of the callback function return |
173 | | * \c false. |
174 | | * |
175 | | * @returns \c true if all invocations of the callback function are |
176 | | * successful, \c false otherwise |
177 | | */ |
178 | | bool applyStrict(ApplyFunction &applyFunction) const |
179 | 71.1k | { |
180 | 71.1k | for (auto &&constraint : m_constraints) { |
181 | 57.3k | if (!applyFunction(*constraint)) { |
182 | 30.4k | return false; |
183 | 30.4k | } |
184 | 57.3k | } |
185 | | |
186 | 40.6k | return true; |
187 | 71.1k | } |
188 | | |
189 | | bool getAlwaysInvalid() const |
190 | 1.10M | { |
191 | 1.10M | return m_alwaysInvalid; |
192 | 1.10M | } |
193 | | |
194 | | /** |
195 | | * @brief Get the description associated with this sub-schema |
196 | | * |
197 | | * @throws std::runtime_error if a description has not been set |
198 | | * |
199 | | * @returns string containing sub-schema description |
200 | | */ |
201 | | std::string getDescription() const |
202 | 0 | { |
203 | 0 | if (m_description) { |
204 | 0 | return *m_description; |
205 | 0 | } |
206 | 0 |
|
207 | 0 | throwRuntimeError("Schema does not have a description"); |
208 | 0 | } |
209 | | |
210 | | /** |
211 | | * @brief Get the ID associated with this sub-schema |
212 | | * |
213 | | * @throws std::runtime_error if an ID has not been set |
214 | | * |
215 | | * @returns string containing sub-schema ID |
216 | | */ |
217 | | std::string getId() const |
218 | 0 | { |
219 | 0 | if (m_id) { |
220 | 0 | return *m_id; |
221 | 0 | } |
222 | 0 |
|
223 | 0 | throwRuntimeError("Schema does not have an ID"); |
224 | 0 | } |
225 | | |
226 | | /** |
227 | | * @brief Get the title associated with this sub-schema |
228 | | * |
229 | | * @throws std::runtime_error if a title has not been set |
230 | | * |
231 | | * @returns string containing sub-schema title |
232 | | */ |
233 | | std::string getTitle() const |
234 | 0 | { |
235 | 0 | if (m_title) { |
236 | 0 | return *m_title; |
237 | 0 | } |
238 | 0 |
|
239 | 0 | throwRuntimeError("Schema does not have a title"); |
240 | 0 | } |
241 | | |
242 | | /** |
243 | | * @brief Check whether this sub-schema has a description |
244 | | * |
245 | | * @return boolean value |
246 | | */ |
247 | | bool hasDescription() const |
248 | 0 | { |
249 | 0 | return static_cast<bool>(m_description); |
250 | 0 | } |
251 | | |
252 | | /** |
253 | | * @brief Check whether this sub-schema has an ID |
254 | | * |
255 | | * @return boolean value |
256 | | */ |
257 | | bool hasId() const |
258 | 0 | { |
259 | 0 | return static_cast<bool>(m_id); |
260 | 0 | } |
261 | | |
262 | | /** |
263 | | * @brief Check whether this sub-schema has a title |
264 | | * |
265 | | * @return boolean value |
266 | | */ |
267 | | bool hasTitle() const |
268 | 0 | { |
269 | 0 | return static_cast<bool>(m_title); |
270 | 0 | } |
271 | | |
272 | | void setAlwaysInvalid(bool value) |
273 | 1.85k | { |
274 | 1.85k | m_alwaysInvalid = value; |
275 | 1.85k | } |
276 | | |
277 | | /** |
278 | | * @brief Set the description for this sub-schema |
279 | | * |
280 | | * The description will not be used for validation, but may be used as part |
281 | | * of the user interface for interacting with schemas and sub-schemas. As |
282 | | * an example, it may be used as part of the validation error descriptions |
283 | | * that are produced by the Validator and ValidationVisitor classes. |
284 | | * |
285 | | * @param description new description |
286 | | */ |
287 | | void setDescription(const std::string &description) |
288 | 2.08k | { |
289 | 2.08k | m_description = description; |
290 | 2.08k | } |
291 | | |
292 | | void setId(const std::string &id) |
293 | 20.9k | { |
294 | 20.9k | m_id = id; |
295 | 20.9k | } |
296 | | |
297 | | /** |
298 | | * @brief Set the title for this sub-schema |
299 | | * |
300 | | * The title will not be used for validation, but may be used as part |
301 | | * of the user interface for interacting with schemas and sub-schema. As an |
302 | | * example, it may be used as part of the validation error descriptions |
303 | | * that are produced by the Validator and ValidationVisitor classes. |
304 | | * |
305 | | * @param title new title |
306 | | */ |
307 | | void setTitle(const std::string &title) |
308 | 2.64k | { |
309 | 2.64k | m_title = title; |
310 | 2.64k | } |
311 | | |
312 | | protected: |
313 | | |
314 | | CustomAlloc m_allocFn; |
315 | | |
316 | | CustomFree m_freeFn; |
317 | | |
318 | | private: |
319 | | |
320 | | bool m_alwaysInvalid; |
321 | | |
322 | | /// List of pointers to constraints that apply to this schema. |
323 | | std::vector<Constraint::OwningPointer> m_constraints; |
324 | | |
325 | | /// Schema description (optional) |
326 | | std::optional<std::string> m_description; |
327 | | |
328 | | /// ID to apply when resolving the schema URI |
329 | | std::optional<std::string> m_id; |
330 | | |
331 | | /// Title string associated with the schema (optional) |
332 | | std::optional<std::string> m_title; |
333 | | }; |
334 | | |
335 | | } // namespace valijson |