/src/valijson/include/valijson/schema_parser.hpp
Line | Count | Source |
1 | | #pragma once |
2 | | |
3 | | #include <functional> |
4 | | #include <iostream> |
5 | | #include <optional> |
6 | | #include <vector> |
7 | | |
8 | | #include <valijson/constraints/concrete_constraints.hpp> |
9 | | #include <valijson/internal/adapter.hpp> |
10 | | #include <valijson/internal/debug.hpp> |
11 | | #include <valijson/internal/json_pointer.hpp> |
12 | | #include <valijson/internal/json_reference.hpp> |
13 | | #include <valijson/internal/uri.hpp> |
14 | | #include <valijson/constraint_builder.hpp> |
15 | | #include <valijson/schema.hpp> |
16 | | #include <valijson/schema_registry.hpp> |
17 | | #include <valijson/exceptions.hpp> |
18 | | |
19 | | namespace valijson { |
20 | | |
21 | | /** |
22 | | * @brief Parser for populating a Schema based on a JSON Schema document. |
23 | | * |
24 | | * The SchemaParser class supports Drafts 3 and 4 of JSON Schema, however |
25 | | * Draft 3 support should be considered deprecated. |
26 | | * |
27 | | * The functions provided by this class have been templated so that they can |
28 | | * be used with different Adapter types. |
29 | | */ |
30 | | class SchemaParser |
31 | | { |
32 | | public: |
33 | | /// Supported versions of JSON Schema |
34 | | enum Version { |
35 | | kDraft3, ///< @deprecated JSON Schema v3 has been superseded by v4 |
36 | | kDraft4, |
37 | | kDraft7 |
38 | | }; |
39 | | |
40 | | /** |
41 | | * @brief Construct a new SchemaParser for a given version of JSON Schema |
42 | | * |
43 | | * @param version Version of JSON Schema that will be expected |
44 | | */ |
45 | | explicit SchemaParser(const Version version = kDraft7) |
46 | 56.9k | : m_version(version) { } |
47 | | |
48 | | /** |
49 | | * @brief Release memory associated with custom ConstraintBuilders |
50 | | */ |
51 | | virtual ~SchemaParser() |
52 | 56.9k | { |
53 | 56.9k | for (const auto& entry : constraintBuilders) { |
54 | 0 | delete entry.second; |
55 | 0 | } |
56 | 56.9k | } |
57 | | |
58 | | /** |
59 | | * @brief Struct to contain templated function type for fetching documents |
60 | | */ |
61 | | template<typename AdapterType> |
62 | | struct FunctionPtrs |
63 | | { |
64 | | typedef typename adapters::AdapterTraits<AdapterType>::DocumentType DocumentType; |
65 | | |
66 | | /// Templated function pointer type for fetching remote documents |
67 | | typedef std::function<const DocumentType* (const std::string &uri)> FetchDoc; |
68 | | |
69 | | /// Templated function pointer type for freeing fetched documents |
70 | | typedef std::function<void (const DocumentType *)> FreeDoc; |
71 | | }; |
72 | | |
73 | | /** |
74 | | * @brief Add a custom constraint to this SchemaParser |
75 | | |
76 | | * @param key name that will be used to identify relevant constraints |
77 | | * while parsing a schema document |
78 | | * @param builder pointer to a subclass of ConstraintBuilder that can |
79 | | * parse custom constraints found in a schema document, |
80 | | * and return an appropriate instance of Constraint; this |
81 | | * class guarantees that it will take ownership of this |
82 | | * pointer - unless this function throws an exception |
83 | | * |
84 | | * @todo consider accepting a list of custom ConstraintBuilders in |
85 | | * constructor, so that this class remains immutable after |
86 | | * construction |
87 | | * |
88 | | * @todo Add additional checks for key conflicts, empty keys, and |
89 | | * potential restrictions relating to case sensitivity |
90 | | */ |
91 | | void addConstraintBuilder(const std::string &key, const ConstraintBuilder *builder) |
92 | 0 | { |
93 | 0 | constraintBuilders.push_back(std::make_pair(key, builder)); |
94 | 0 | } |
95 | | |
96 | | /** |
97 | | * @brief Populate a Schema object from JSON Schema document |
98 | | * |
99 | | * When processing Draft 3 schemas, the parentSubschema and ownName pointers |
100 | | * should be set in contexts where a 'required' constraint would be valid. |
101 | | * These are used to add a RequiredConstraint object to the Schema that |
102 | | * contains the required property. |
103 | | * |
104 | | * @param node Reference to node to parse |
105 | | * @param schema Reference to Schema to populate |
106 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
107 | | */ |
108 | | template<typename AdapterType> |
109 | | void populateSchema( |
110 | | const AdapterType &node, |
111 | | Schema &schema, |
112 | | typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc = nullptr, |
113 | | typename FunctionPtrs<AdapterType>::FreeDoc freeDoc = nullptr) |
114 | 56.9k | { |
115 | 56.9k | if ((fetchDoc == nullptr ) ^ (freeDoc == nullptr)) { |
116 | 0 | throwRuntimeError("Remote document fetching can't be enabled without both fetch and free functions"); |
117 | 0 | } |
118 | | |
119 | 56.9k | typename DocumentCache<AdapterType>::Type docCache; |
120 | 56.9k | SchemaRegistry schemaRegistry; |
121 | 56.9k | #if VALIJSON_USE_EXCEPTIONS |
122 | 56.9k | try { |
123 | 56.9k | #endif |
124 | 56.9k | resolveThenPopulateSchema(schema, node, node, schema, std::optional<std::string>(), "", fetchDoc, nullptr, |
125 | 56.9k | nullptr, docCache, schemaRegistry); |
126 | 56.9k | #if VALIJSON_USE_EXCEPTIONS |
127 | 56.9k | } catch (...) { |
128 | 37.0k | freeDocumentCache<AdapterType>(docCache, freeDoc); |
129 | 37.0k | throw; |
130 | 37.0k | } |
131 | 0 | #endif |
132 | | |
133 | 19.9k | freeDocumentCache<AdapterType>(docCache, freeDoc); |
134 | 19.9k | } |
135 | | |
136 | | private: |
137 | | |
138 | | typedef std::vector<std::pair<std::string, const ConstraintBuilder *>> |
139 | | ConstraintBuilders; |
140 | | |
141 | | ConstraintBuilders constraintBuilders; |
142 | | |
143 | | template<typename AdapterType> |
144 | | struct DocumentCache |
145 | | { |
146 | | typedef typename adapters::AdapterTraits<AdapterType>::DocumentType DocumentType; |
147 | | |
148 | | typedef std::map<std::string, const DocumentType*> Type; |
149 | | }; |
150 | | |
151 | | /** |
152 | | * @brief Free memory used by fetched documents |
153 | | * |
154 | | * If a custom 'free' function has not been provided, then the default |
155 | | * delete operator will be used. |
156 | | * |
157 | | * @param docCache collection of fetched documents to free |
158 | | * @param freeDoc optional custom free function |
159 | | */ |
160 | | template<typename AdapterType> |
161 | | void freeDocumentCache(const typename DocumentCache<AdapterType>::Type |
162 | | &docCache, typename FunctionPtrs<AdapterType>::FreeDoc freeDoc) |
163 | 56.9k | { |
164 | 56.9k | typedef typename DocumentCache<AdapterType>::Type DocCacheType; |
165 | | |
166 | 56.9k | for (const typename DocCacheType::value_type &v : docCache) { |
167 | 0 | freeDoc(v.second); |
168 | 0 | } |
169 | 56.9k | } |
170 | | |
171 | | /** |
172 | | * @brief Find the complete URI for a document, within a resolution scope |
173 | | * |
174 | | * This function captures five different cases that can occur when |
175 | | * attempting to resolve a document URI within a particular resolution |
176 | | * scope: |
177 | | * |
178 | | * (1) resolution scope not present, but URN or absolute document URI is |
179 | | * => document URI as-is |
180 | | * (2) resolution scope not present, and document URI is relative or absent |
181 | | * => document URI, if present, otherwise no result |
182 | | * (3) resolution scope is present, and document URI is a relative path |
183 | | * => resolve document URI relative to resolution scope |
184 | | * (4) resolution scope is present, and document URI is absolute |
185 | | * => document URI as-is |
186 | | * (5) resolution scope is present, but document URI is not |
187 | | * => resolution scope as-is |
188 | | * |
189 | | * This function assumes that the resolution scope is absolute. |
190 | | * |
191 | | * When resolving a document URI relative to the resolution scope, the |
192 | | * document URI should be used to replace the path, query and fragment |
193 | | * portions of URI provided by the resolution scope. |
194 | | */ |
195 | | virtual std::optional<std::string> resolveDocumentUri( |
196 | | const std::optional<std::string>& resolutionScope, |
197 | | const std::optional<std::string>& documentUri) |
198 | 240k | { |
199 | 240k | if (resolutionScope) { |
200 | 78.1k | if (documentUri) { |
201 | 9.56k | if (internal::uri::isUriAbsolute(*documentUri) || internal::uri::isUrn(*documentUri)) { |
202 | | // (4) resolution scope is present, and document URI is absolute |
203 | | // => document URI as-is |
204 | 3.20k | return *documentUri; |
205 | 6.36k | } else { |
206 | | // (3) resolution scope is present, and document URI is a relative path |
207 | | // => resolve document URI relative to resolution scope |
208 | 6.36k | return internal::uri::resolveRelativeUri(*resolutionScope, *documentUri); |
209 | 6.36k | } |
210 | 68.5k | } else { |
211 | | // (5) resolution scope is present, but document URI is not |
212 | | // => resolution scope as-is |
213 | 68.5k | return *resolutionScope; |
214 | 68.5k | } |
215 | 162k | } else if (documentUri && internal::uri::isUriAbsolute(*documentUri)) { |
216 | | // (1a) resolution scope not present, but absolute document URI is |
217 | | // => document URI as-is |
218 | 1.36k | return *documentUri; |
219 | 161k | } else if (documentUri && internal::uri::isUrn(*documentUri)) { |
220 | | // (1b) resolution scope not present, but URN is |
221 | | // => document URI as-is |
222 | 1.09k | return *documentUri; |
223 | 159k | } else { |
224 | | // (2) resolution scope not present, and document URI is relative or absent |
225 | | // => document URI, if present, otherwise no result |
226 | | // documentUri is already std::optional |
227 | 159k | return documentUri; |
228 | 159k | } |
229 | 240k | } |
230 | | |
231 | | /** |
232 | | * @brief Extract a JSON Reference string from a node |
233 | | * |
234 | | * @param node node to extract the JSON Reference from |
235 | | * @param result reference to string to set with the result |
236 | | * |
237 | | * @throws std::invalid_argument if node is an object containing a `$ref` |
238 | | * property but with a value that cannot be interpreted as a string |
239 | | * |
240 | | * @return \c true if a JSON Reference was extracted; \c false otherwise |
241 | | */ |
242 | | template<typename AdapterType> |
243 | | bool extractJsonReference(const AdapterType &node, std::string &result) |
244 | 794k | { |
245 | 794k | if (!node.isObject()) { |
246 | 4.76k | return false; |
247 | 4.76k | } |
248 | | |
249 | 789k | const typename AdapterType::Object o = node.getObject(); |
250 | 789k | const typename AdapterType::Object::const_iterator itr = o.find("$ref"); |
251 | 789k | if (itr == o.end()) { |
252 | 548k | return false; |
253 | 548k | } else if (!itr->second.getString(result)) { |
254 | 245 | throwRuntimeError("$ref property expected to contain string value."); |
255 | 245 | } |
256 | | |
257 | 240k | return true; |
258 | 789k | } |
259 | | |
260 | | /** |
261 | | * Sanitise an optional JSON Pointer, trimming trailing slashes |
262 | | */ |
263 | | static std::string sanitiseJsonPointer(const std::optional<std::string>& input) |
264 | 240k | { |
265 | 240k | if (input) { |
266 | 234k | if (*input == "/") { |
267 | 13.3k | return ""; |
268 | 13.3k | } |
269 | | |
270 | 220k | return *input; |
271 | 234k | } |
272 | | |
273 | | // If the JSON Pointer is not set, assume that the URI points to |
274 | | // the root of the document |
275 | 6.27k | return ""; |
276 | 240k | } |
277 | | |
278 | | static std::string escapeJsonPointerToken(const std::string &token) |
279 | 1.47M | { |
280 | 1.47M | std::string result; |
281 | 52.5M | for (const char c : token) { |
282 | 52.5M | if (c == '~') { |
283 | 59.8k | result += "~0"; |
284 | 52.5M | } else if (c == '/') { |
285 | 908k | result += "~1"; |
286 | 51.6M | } else { |
287 | 51.6M | result += c; |
288 | 51.6M | } |
289 | 52.5M | } |
290 | | |
291 | 1.47M | return result; |
292 | 1.47M | } |
293 | | |
294 | | const char *idKeyword() const |
295 | 1.82M | { |
296 | 1.82M | return m_version == kDraft7 ? "$id" : "id"; |
297 | 1.82M | } |
298 | | |
299 | | std::optional<std::string> resolveId( |
300 | | const std::optional<std::string> ¤tScope, |
301 | | const std::string &id) const |
302 | 113k | { |
303 | 113k | if (!currentScope || internal::uri::isUriAbsolute(id) || |
304 | 84.0k | internal::uri::isUrn(id)) { |
305 | 29.8k | return id; |
306 | 29.8k | } |
307 | | |
308 | 83.8k | return internal::uri::resolveRelativeUri(*currentScope, id); |
309 | 113k | } |
310 | | |
311 | | template<typename AdapterType> |
312 | | bool findSchemaById( |
313 | | const AdapterType &node, |
314 | | const std::optional<std::string> ¤tScope, |
315 | | const std::string &targetUri, |
316 | | const std::string &nodePath, |
317 | | std::string &foundPath, |
318 | | std::optional<std::string> &foundScope) const |
319 | 1.05M | { |
320 | 1.05M | std::optional<std::string> updatedScope = currentScope; |
321 | | |
322 | 1.05M | if (node.isObject()) { |
323 | 587k | const typename AdapterType::Object object = node.asObject(); |
324 | 587k | const typename AdapterType::Object::const_iterator itr = |
325 | 587k | object.find(idKeyword()); |
326 | 587k | if (itr != object.end() && itr->second.maybeString()) { |
327 | 50.3k | updatedScope = resolveId(currentScope, itr->second.asString()); |
328 | 50.3k | if (updatedScope && *updatedScope == targetUri) { |
329 | 2.95k | foundPath = nodePath; |
330 | 2.95k | foundScope = currentScope; |
331 | 2.95k | return true; |
332 | 2.95k | } |
333 | 50.3k | } |
334 | | |
335 | 611k | for (const typename AdapterType::ObjectMember member : object) { |
336 | 611k | const std::string childPath = |
337 | 611k | nodePath + "/" + escapeJsonPointerToken(member.first); |
338 | 611k | if (findSchemaById(member.second, updatedScope, targetUri, |
339 | 611k | childPath, foundPath, foundScope)) { |
340 | 8.14k | return true; |
341 | 8.14k | } |
342 | 611k | } |
343 | 584k | } else if (node.maybeArray()) { |
344 | 30.4k | size_t index = 0; |
345 | 419k | for (const AdapterType item : node.asArray()) { |
346 | 419k | const std::string childPath = nodePath + "/" + |
347 | 419k | std::to_string(index++); |
348 | 419k | if (findSchemaById(item, updatedScope, targetUri, childPath, |
349 | 419k | foundPath, foundScope)) { |
350 | 7.10k | return true; |
351 | 7.10k | } |
352 | 419k | } |
353 | 30.4k | } |
354 | | |
355 | 1.03M | return false; |
356 | 1.05M | } |
357 | | |
358 | | template<typename AdapterType> |
359 | | bool findScopeForPath( |
360 | | const AdapterType &node, |
361 | | const std::optional<std::string> ¤tScope, |
362 | | const std::string &targetPath, |
363 | | const std::string &nodePath, |
364 | | std::optional<std::string> &foundScope) const |
365 | 1.34M | { |
366 | 1.34M | if (nodePath == targetPath) { |
367 | 5.06k | foundScope = currentScope; |
368 | 5.06k | return true; |
369 | 5.06k | } |
370 | | |
371 | 1.33M | std::optional<std::string> updatedScope = currentScope; |
372 | | |
373 | 1.33M | if (node.isObject()) { |
374 | 744k | const typename AdapterType::Object object = node.asObject(); |
375 | 744k | const typename AdapterType::Object::const_iterator itr = |
376 | 744k | object.find(idKeyword()); |
377 | 744k | if (itr != object.end() && itr->second.maybeString()) { |
378 | 28.7k | updatedScope = resolveId(currentScope, itr->second.asString()); |
379 | 28.7k | } |
380 | | |
381 | 834k | for (const typename AdapterType::ObjectMember member : object) { |
382 | 834k | const std::string childPath = |
383 | 834k | nodePath + "/" + escapeJsonPointerToken(member.first); |
384 | 834k | if (findScopeForPath(member.second, updatedScope, targetPath, |
385 | 834k | childPath, foundScope)) { |
386 | 2.63k | return true; |
387 | 2.63k | } |
388 | 834k | } |
389 | 744k | } else { |
390 | 595k | if (node.maybeArray()) { |
391 | 88.6k | size_t index = 0; |
392 | 493k | for (const AdapterType item : node.asArray()) { |
393 | 493k | const std::string childPath = nodePath + "/" + |
394 | 493k | std::to_string(index++); |
395 | 493k | if (findScopeForPath(item, updatedScope, targetPath, |
396 | 493k | childPath, foundScope)) { |
397 | 25 | return true; |
398 | 25 | } |
399 | 493k | } |
400 | 88.6k | } |
401 | 595k | } |
402 | | |
403 | 1.33M | return false; |
404 | 1.33M | } |
405 | | |
406 | | /** |
407 | | * @brief Search the schema registry for a schema matching a given key |
408 | | * |
409 | | * If the key is not present in the schema registry, a nullptr will be |
410 | | * returned, and the contents of the registry will remain unchanged. This is |
411 | | * in contrast to the behaviour of the std::map [] operator, which would |
412 | | * add the nullptr to the registry. |
413 | | * |
414 | | * @param schemaRegistry schema registry to query |
415 | | * @param queryKey key to search for |
416 | | * |
417 | | * @return shared pointer to Schema if found, nullptr otherwise |
418 | | */ |
419 | | static const Subschema * querySchemaRegistry(SchemaRegistry &schemaRegistry, |
420 | | const std::string &queryKey) |
421 | 736k | { |
422 | 736k | const SchemaRegistry::iterator itr = schemaRegistry.find(queryKey); |
423 | 736k | if (itr == schemaRegistry.end()) { |
424 | 472k | return nullptr; |
425 | 472k | } |
426 | | |
427 | 263k | return itr->second; |
428 | 736k | } |
429 | | |
430 | | /** |
431 | | * @brief Add entries to the schema registry for a given list of keys |
432 | | * |
433 | | * @param schemaRegistry schema registry to update |
434 | | * @param keysToRegister list of keys to register |
435 | | * @param schema shared pointer to schema that keys will map to |
436 | | * |
437 | | * @throws std::logic_error if any of the keys are already present in the |
438 | | * schema registry. This behaviour is intended to help detect incorrect |
439 | | * usage of the schema registry during development, and is not expected |
440 | | * to occur otherwise, even for malformed schemas. |
441 | | */ |
442 | | static void updateSchemaRegistry(SchemaRegistry &schemaRegistry, |
443 | | const std::vector<std::string> &keysToRegister, |
444 | | const Subschema *schema) |
445 | 710k | { |
446 | 710k | for (const std::string &keyToRegister : keysToRegister) { |
447 | 15.2k | const SchemaRegistry::value_type value(keyToRegister, schema); |
448 | 15.2k | if (!schemaRegistry.insert(value).second) { |
449 | 0 | throwLogicError("Key '" + keyToRegister + "' already in schema registry."); |
450 | 0 | } |
451 | 15.2k | } |
452 | 710k | } |
453 | | |
454 | | /** |
455 | | * @brief Recursive helper function for retrieving or creating schemas |
456 | | * |
457 | | * This function will be applied recursively until a concrete node is found. |
458 | | * A concrete node is a node that contains actual schema constraints rather |
459 | | * than a JSON Reference. |
460 | | * |
461 | | * This termination condition may be trigged by visiting the concrete node |
462 | | * at the end of a series of $ref nodes, or by finding a schema for one of |
463 | | * those $ref nodes in the schema registry. An entry will be added to the |
464 | | * schema registry for each node visited on the path to the concrete node. |
465 | | * |
466 | | * @param rootSchema The Schema instance, and root subschema, through |
467 | | * which other subschemas can be created and |
468 | | * modified |
469 | | * @param rootNode Reference to the node from which JSON References |
470 | | * will be resolved when they refer to the current |
471 | | * document |
472 | | * @param node Reference to the node to parse |
473 | | * @param currentScope URI for current resolution scope |
474 | | * @param nodePath JSON Pointer representing path to current node |
475 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
476 | | * @param parentSubschema Optional pointer to the parent schema, used to |
477 | | * support required keyword in Draft 3 |
478 | | * @param ownName Optional pointer to a node name, used to support |
479 | | * the 'required' keyword in Draft 3 |
480 | | * @param docCache Cache of resolved and fetched remote documents |
481 | | * @param schemaRegistry Registry of populated schemas |
482 | | * @param newRegistryKeys A list of keys that should be added to the registry |
483 | | * when recursion terminates |
484 | | */ |
485 | | template<typename AdapterType> |
486 | | const Subschema * makeOrReuseSchema( |
487 | | Schema &rootSchema, |
488 | | const AdapterType &rootNode, |
489 | | const AdapterType &node, |
490 | | const std::optional<std::string> currentScope, |
491 | | const std::string &nodePath, |
492 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
493 | | const Subschema *parentSubschema, |
494 | | const std::string *ownName, |
495 | | typename DocumentCache<AdapterType>::Type &docCache, |
496 | | SchemaRegistry &schemaRegistry, |
497 | | std::vector<std::string> &newRegistryKeys) |
498 | 736k | { |
499 | 736k | std::string jsonRef; |
500 | | |
501 | | // Check for the first termination condition (found a non-$ref node) |
502 | 736k | if (!extractJsonReference(node, jsonRef)) { |
503 | | |
504 | | // Construct a key that we can use to search the schema registry for |
505 | | // a schema corresponding to the current node |
506 | 505k | const std::string schemaRegistryKey = currentScope ? (*currentScope + nodePath) : nodePath; |
507 | | |
508 | | // Retrieve an existing schema from the registry if possible |
509 | 505k | const Subschema *registeredPtr = querySchemaRegistry(schemaRegistry, schemaRegistryKey); |
510 | | |
511 | | // Create a new schema otherwise |
512 | 505k | const Subschema *subschema = registeredPtr ? registeredPtr : rootSchema.createSubschema(); |
513 | | |
514 | | // Add registry entries for keys belonging to any $ref nodes that were |
515 | | // visited before arriving at the current node |
516 | 505k | updateSchemaRegistry(schemaRegistry, newRegistryKeys, subschema); |
517 | 505k | if (!registeredPtr) { |
518 | 446k | schemaRegistry.emplace(schemaRegistryKey, subschema); |
519 | 446k | } |
520 | | |
521 | | // Schema registry did not contain a preexisting schema corresponding |
522 | | // to the current node, so the schema that was returned will need |
523 | | // to be populated |
524 | 505k | if (!registeredPtr) { |
525 | 446k | populateSchema(rootSchema, rootNode, node, *subschema, |
526 | 446k | currentScope, nodePath, fetchDoc, parentSubschema, |
527 | 446k | ownName, docCache, schemaRegistry); |
528 | 446k | } |
529 | | |
530 | 505k | return subschema; |
531 | 505k | } |
532 | | |
533 | | // Returns a document URI if the reference points somewhere |
534 | | // other than the current document |
535 | 230k | const std::optional<std::string> documentUri = internal::json_reference::getJsonReferenceUri(jsonRef); |
536 | | |
537 | | // Extract JSON Pointer from JSON Reference, with any trailing |
538 | | // slashes removed so that keys in the schema registry end |
539 | | // consistently |
540 | 230k | const std::string actualJsonPointer = sanitiseJsonPointer( |
541 | 230k | internal::json_reference::getJsonReferencePointer(jsonRef)); |
542 | | |
543 | | // Determine the actual document URI based on the resolution |
544 | | // scope. An absolute document URI will take precedence when |
545 | | // present, otherwise we need to resolve the URI relative to |
546 | | // the current resolution scope |
547 | 230k | const std::optional<std::string> actualDocumentUri = resolveDocumentUri(currentScope, documentUri); |
548 | | |
549 | | // Construct a key to search the schema registry for an existing schema |
550 | 230k | const std::string queryKey = actualDocumentUri ? (*actualDocumentUri + actualJsonPointer) : actualJsonPointer; |
551 | | |
552 | 230k | if (!actualJsonPointer.empty() && actualJsonPointer[0] != '/') { |
553 | 12.8k | const std::string idRef = actualDocumentUri ? |
554 | 12.5k | (*actualDocumentUri + "#" + actualJsonPointer) : |
555 | 12.8k | ("#" + actualJsonPointer); |
556 | 12.8k | std::string registeredPath; |
557 | 12.8k | std::optional<std::string> registeredScope; |
558 | 12.8k | if (findSchemaById(rootNode, currentScope, idRef, |
559 | 12.8k | "", registeredPath, registeredScope)) { |
560 | 2.53k | const AdapterType registeredRoot = |
561 | 2.53k | internal::json_pointer::resolveJsonPointerStrict( |
562 | 2.53k | rootNode, registeredPath); |
563 | | |
564 | 2.53k | const Subschema *registeredPtr = querySchemaRegistry(schemaRegistry, idRef); |
565 | 2.53k | if (registeredPtr) { |
566 | 2.43k | updateSchemaRegistry(schemaRegistry, newRegistryKeys, registeredPtr); |
567 | 2.43k | return registeredPtr; |
568 | 2.43k | } |
569 | | |
570 | 106 | const Subschema *subschema = rootSchema.createSubschema(); |
571 | 106 | newRegistryKeys.push_back(idRef); |
572 | 106 | updateSchemaRegistry(schemaRegistry, newRegistryKeys, subschema); |
573 | | |
574 | 106 | populateSchema(rootSchema, registeredRoot, registeredRoot, |
575 | 106 | *subschema, registeredScope, "", fetchDoc, |
576 | 106 | parentSubschema, ownName, docCache, schemaRegistry); |
577 | | |
578 | 106 | return subschema; |
579 | 2.53k | } |
580 | 12.8k | } |
581 | | |
582 | | // Check for the second termination condition (found a $ref node that |
583 | | // already has an entry in the schema registry) |
584 | 227k | const Subschema *registeredPtr = querySchemaRegistry(schemaRegistry, queryKey); |
585 | 227k | if (registeredPtr) { |
586 | 202k | updateSchemaRegistry(schemaRegistry, newRegistryKeys, registeredPtr); |
587 | 202k | return registeredPtr; |
588 | 202k | } |
589 | | |
590 | 25.4k | if (actualDocumentUri && (!currentScope || *actualDocumentUri != *currentScope)) { |
591 | 6.25k | std::string registeredPath; |
592 | 6.25k | std::optional<std::string> registeredScope; |
593 | 6.25k | if (findSchemaById(rootNode, currentScope, *actualDocumentUri, |
594 | 6.25k | "", registeredPath, registeredScope)) { |
595 | 282 | const AdapterType registeredRoot = |
596 | 282 | internal::json_pointer::resolveJsonPointerStrict( |
597 | 282 | rootNode, registeredPath); |
598 | 282 | const AdapterType referencedAdapter = actualJsonPointer.empty() |
599 | 282 | ? registeredRoot |
600 | 282 | : internal::json_pointer::resolveJsonPointerStrict( |
601 | 109 | registeredRoot, actualJsonPointer); |
602 | | |
603 | 282 | std::optional<std::string> referencedScope = registeredScope; |
604 | 282 | if (!actualJsonPointer.empty()) { |
605 | 3 | findScopeForPath(registeredRoot, registeredScope, |
606 | 3 | actualJsonPointer, "", referencedScope); |
607 | 3 | } |
608 | | |
609 | 282 | const Subschema *subschema = rootSchema.createSubschema(); |
610 | 282 | newRegistryKeys.push_back(queryKey); |
611 | 282 | updateSchemaRegistry(schemaRegistry, newRegistryKeys, subschema); |
612 | | |
613 | 282 | populateSchema(rootSchema, registeredRoot, referencedAdapter, |
614 | 282 | *subschema, referencedScope, actualJsonPointer, |
615 | 282 | fetchDoc, parentSubschema, ownName, docCache, |
616 | 282 | schemaRegistry); |
617 | | |
618 | 282 | return subschema; |
619 | 282 | } |
620 | | |
621 | 5.97k | const typename FunctionPtrs<AdapterType>::DocumentType *newDoc = nullptr; |
622 | | |
623 | | // Have we seen this document before? |
624 | 5.97k | typename DocumentCache<AdapterType>::Type::iterator docCacheItr = |
625 | 5.97k | docCache.find(*actualDocumentUri); |
626 | 5.97k | if (docCacheItr == docCache.end()) { |
627 | | // Resolve reference against remote document |
628 | 5.97k | if (!fetchDoc) { |
629 | 5.97k | throwRuntimeError("Fetching of remote JSON References not enabled."); |
630 | 5.97k | } |
631 | | |
632 | | // Returns a pointer to the remote document that was |
633 | | // retrieved, or null if retrieval failed. This class |
634 | | // will take ownership of the pointer, and call freeDoc |
635 | | // when it is no longer needed. |
636 | 5.97k | newDoc = fetchDoc(*actualDocumentUri); |
637 | | |
638 | | // Can't proceed without the remote document |
639 | 5.97k | if (!newDoc) { |
640 | 0 | throwRuntimeError("Failed to fetch referenced schema document: " + *actualDocumentUri); |
641 | 0 | } |
642 | | |
643 | 5.97k | typedef typename DocumentCache<AdapterType>::Type::value_type |
644 | 5.97k | DocCacheValueType; |
645 | | |
646 | 5.97k | docCache.insert(DocCacheValueType(*actualDocumentUri, newDoc)); |
647 | | |
648 | 5.97k | } else { |
649 | 0 | newDoc = docCacheItr->second; |
650 | 0 | } |
651 | | |
652 | 5.97k | const AdapterType newRootNode(*newDoc); |
653 | | |
654 | | // Find where we need to be in the document |
655 | 5.97k | const AdapterType &referencedAdapter = |
656 | 5.97k | internal::json_pointer::resolveJsonPointerStrict(newRootNode, |
657 | 5.97k | actualJsonPointer); |
658 | | |
659 | 5.97k | newRegistryKeys.push_back(queryKey); |
660 | | |
661 | | // Populate the schema, starting from the referenced node, with |
662 | | // nested JSON References resolved relative to the new root node |
663 | 5.97k | return makeOrReuseSchema(rootSchema, newRootNode, referencedAdapter, |
664 | 5.97k | actualDocumentUri, actualJsonPointer, fetchDoc, parentSubschema, |
665 | 5.97k | ownName, docCache, schemaRegistry, newRegistryKeys); |
666 | | |
667 | 6.25k | } |
668 | | |
669 | 19.2k | if (std::find(newRegistryKeys.begin(), newRegistryKeys.end(), queryKey) != newRegistryKeys.end()) { |
670 | 81 | throwRuntimeError("found cycle while resolving JSON reference"); |
671 | 81 | } |
672 | | |
673 | | // JSON References in nested schema will be resolved relative to the |
674 | | // current document |
675 | 19.2k | const AdapterType &referencedAdapter = |
676 | 19.2k | internal::json_pointer::resolveJsonPointerStrict( |
677 | 19.2k | rootNode, actualJsonPointer); |
678 | | |
679 | 19.2k | newRegistryKeys.push_back(queryKey); |
680 | | |
681 | 19.2k | std::optional<std::string> referencedScope = currentScope; |
682 | 19.2k | findScopeForPath(rootNode, currentScope, actualJsonPointer, "", |
683 | 19.2k | referencedScope); |
684 | | |
685 | | // Populate the schema, starting from the referenced node, with |
686 | | // nested JSON References resolved relative to the new root node |
687 | 19.2k | return makeOrReuseSchema(rootSchema, rootNode, referencedAdapter, |
688 | 19.2k | referencedScope, actualJsonPointer, fetchDoc, parentSubschema, |
689 | 19.2k | ownName, docCache, schemaRegistry, newRegistryKeys); |
690 | 25.4k | } |
691 | | |
692 | | /** |
693 | | * @brief Return pointer for the schema corresponding to a given node |
694 | | * |
695 | | * This function makes use of a schema registry, so that if the path to the |
696 | | * current node is the same as one that has already been parsed and |
697 | | * populated, a pointer to the existing Subschema will be returned. |
698 | | * |
699 | | * Should a series of $ref, or reference, nodes be resolved before reaching |
700 | | * a concrete node, an entry will be added to the schema registry for each of |
701 | | * the nodes in that path. |
702 | | * |
703 | | * @param rootSchema The Schema instance, and root subschema, through |
704 | | * which other subschemas can be created and |
705 | | * modified |
706 | | * @param rootNode Reference to the node from which JSON References |
707 | | * will be resolved when they refer to the current |
708 | | * document |
709 | | * @param node Reference to the node to parse |
710 | | * @param currentScope URI for current resolution scope |
711 | | * @param nodePath JSON Pointer representing path to current node |
712 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
713 | | * @param parentSubschema Optional pointer to the parent schema, used to |
714 | | * support required keyword in Draft 3 |
715 | | * @param ownName Optional pointer to a node name, used to support |
716 | | * the 'required' keyword in Draft 3 |
717 | | * @param docCache Cache of resolved and fetched remote documents |
718 | | * @param schemaRegistry Registry of populated schemas |
719 | | */ |
720 | | template<typename AdapterType> |
721 | | const Subschema * makeOrReuseSchema( |
722 | | Schema &rootSchema, |
723 | | const AdapterType &rootNode, |
724 | | const AdapterType &node, |
725 | | const std::optional<std::string> currentScope, |
726 | | const std::string &nodePath, |
727 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
728 | | const Subschema *parentSubschema, |
729 | | const std::string *ownName, |
730 | | typename DocumentCache<AdapterType>::Type &docCache, |
731 | | SchemaRegistry &schemaRegistry) |
732 | 721k | { |
733 | 721k | std::vector<std::string> schemaRegistryKeysToCreate; |
734 | | |
735 | 721k | return makeOrReuseSchema(rootSchema, rootNode, node, currentScope, |
736 | 721k | nodePath, fetchDoc, parentSubschema, ownName, docCache, |
737 | 721k | schemaRegistry, schemaRegistryKeysToCreate); |
738 | 721k | } |
739 | | |
740 | | /** |
741 | | * @brief Populate a Schema object from JSON Schema document |
742 | | * |
743 | | * When processing Draft 3 schemas, the parentSubschema and ownName pointers |
744 | | * should be set in contexts where a 'required' constraint would be valid. |
745 | | * These are used to add a RequiredConstraint object to the Schema that |
746 | | * contains the required property. |
747 | | * |
748 | | * @param rootSchema The Schema instance, and root subschema, through |
749 | | * which other subschemas can be created and |
750 | | * modified |
751 | | * @param rootNode Reference to the node from which JSON References |
752 | | * will be resolved when they refer to the current |
753 | | * document |
754 | | * @param node Reference to node to parse |
755 | | * @param subschema Reference to Schema to populate |
756 | | * @param currentScope URI for current resolution scope |
757 | | * @param nodePath JSON Pointer representing path to current node |
758 | | * @param fetchDoc Optional function to fetch remote JSON documents |
759 | | * @param parentSubschema Optional pointer to the parent schema, used to |
760 | | * support required keyword in Draft 3 |
761 | | * @param ownName Optional pointer to a node name, used to support |
762 | | * the 'required' keyword in Draft 3 |
763 | | * @param docCache Cache of resolved and fetched remote documents |
764 | | * @param schemaRegistry Registry of populated schemas |
765 | | */ |
766 | | template<typename AdapterType> |
767 | | void populateSchema( |
768 | | Schema &rootSchema, |
769 | | const AdapterType &rootNode, |
770 | | const AdapterType &node, |
771 | | const Subschema &subschema, |
772 | | const std::optional<std::string>& currentScope, |
773 | | const std::string &nodePath, |
774 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
775 | | const Subschema *parentSubschema, |
776 | | const std::string *ownName, |
777 | | typename DocumentCache<AdapterType>::Type &docCache, |
778 | | SchemaRegistry &schemaRegistry) |
779 | 494k | { |
780 | 494k | static_assert((std::is_convertible<AdapterType, |
781 | 494k | const valijson::adapters::Adapter &>::value), |
782 | 494k | "SchemaParser::populateSchema must be invoked with an " |
783 | 494k | "appropriate Adapter implementation"); |
784 | | |
785 | 494k | if (!node.isObject()) { |
786 | 4.48k | if (m_version == kDraft7 && node.maybeBool()) { |
787 | | // Boolean schema |
788 | 2.40k | if (!node.asBool()) { |
789 | 985 | rootSchema.setAlwaysInvalid(&subschema, true); |
790 | 985 | } |
791 | 2.40k | return; |
792 | 2.40k | } else { |
793 | 2.07k | std::string s; |
794 | 2.07k | s += "Expected node at "; |
795 | 2.07k | s += nodePath; |
796 | 2.07k | if (m_version == kDraft7) { |
797 | 1.29k | s += " to contain schema object or boolean value; actual node type is: "; |
798 | 1.29k | } else { |
799 | 782 | s += " to contain schema object; actual node type is: "; |
800 | 782 | } |
801 | 2.07k | s += internal::nodeTypeAsString(node); |
802 | 2.07k | throwRuntimeError(s); |
803 | 2.07k | } |
804 | 4.48k | } |
805 | | |
806 | 492k | const typename AdapterType::Object object = node.asObject(); |
807 | 492k | typename AdapterType::Object::const_iterator itr(object.end()); |
808 | | |
809 | | // Check for schema identifier attribute and update current scope. |
810 | 492k | std::optional<std::string> updatedScope; |
811 | 492k | bool foundId = false; |
812 | 492k | if ((itr = object.find(idKeyword())) != object.end() && itr->second.maybeString()) { |
813 | 34.5k | foundId = true; |
814 | 34.5k | const std::string id = itr->second.asString(); |
815 | 34.5k | rootSchema.setSubschemaId(&subschema, itr->second.asString()); |
816 | 34.5k | updatedScope = resolveId(currentScope, id); |
817 | 457k | } else { |
818 | 457k | updatedScope = currentScope; |
819 | 457k | } |
820 | | |
821 | 492k | if (foundId && updatedScope) { |
822 | 34.5k | schemaRegistry.emplace(*updatedScope, &subschema); |
823 | 34.5k | } |
824 | | |
825 | | // Add the type constraint first to be the first one to check because other constraints may rely on it |
826 | 492k | if ((itr = object.find("type")) != object.end()) { |
827 | 27.3k | rootSchema.addConstraintToSubschema( |
828 | 27.3k | makeTypeConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/type", fetchDoc, |
829 | 27.3k | docCache, schemaRegistry), |
830 | 27.3k | &subschema); |
831 | 27.3k | } |
832 | | |
833 | 492k | if ((itr = object.find("allOf")) != object.end()) { |
834 | 19.2k | rootSchema.addConstraintToSubschema( |
835 | 19.2k | makeAllOfConstraint(rootSchema, rootNode, itr->second, |
836 | 19.2k | updatedScope, nodePath + "/allOf", fetchDoc, |
837 | 19.2k | docCache, schemaRegistry), |
838 | 19.2k | &subschema); |
839 | 19.2k | } |
840 | | |
841 | 492k | if ((itr = object.find("anyOf")) != object.end()) { |
842 | 21.1k | rootSchema.addConstraintToSubschema( |
843 | 21.1k | makeAnyOfConstraint(rootSchema, rootNode, itr->second, |
844 | 21.1k | updatedScope, nodePath + "/anyOf", fetchDoc, |
845 | 21.1k | docCache, schemaRegistry), |
846 | 21.1k | &subschema); |
847 | 21.1k | } |
848 | | |
849 | 492k | if ((itr = object.find("const")) != object.end()) { |
850 | 10.9k | rootSchema.addConstraintToSubschema(makeConstConstraint(itr->second), &subschema); |
851 | 10.9k | } |
852 | | |
853 | 492k | if ((itr = object.find("contains")) != object.end()) { |
854 | 18.8k | rootSchema.addConstraintToSubschema( |
855 | 18.8k | makeContainsConstraint(rootSchema, rootNode, itr->second, |
856 | 18.8k | updatedScope, nodePath + "/contains", fetchDoc, |
857 | 18.8k | docCache, schemaRegistry), &subschema); |
858 | 18.8k | } |
859 | | |
860 | 492k | if ((itr = object.find("dependencies")) != object.end()) { |
861 | 17.0k | rootSchema.addConstraintToSubschema( |
862 | 17.0k | makeDependenciesConstraint(rootSchema, rootNode, |
863 | 17.0k | itr->second, updatedScope, |
864 | 17.0k | nodePath + "/dependencies", fetchDoc, docCache, |
865 | 17.0k | schemaRegistry), |
866 | 17.0k | &subschema); |
867 | 17.0k | } |
868 | | |
869 | 492k | if ((itr = object.find("description")) != object.end()) { |
870 | 2.21k | if (itr->second.maybeString()) { |
871 | 2.17k | rootSchema.setSubschemaDescription(&subschema, |
872 | 2.17k | itr->second.asString()); |
873 | 2.17k | } else { |
874 | 41 | throwRuntimeError( |
875 | 41 | "'description' attribute should have a string value"); |
876 | 41 | } |
877 | 2.21k | } |
878 | | |
879 | 492k | if ((itr = object.find("divisibleBy")) != object.end()) { |
880 | 74 | if (m_version == kDraft3) { |
881 | 0 | if (itr->second.maybeInteger()) { |
882 | 0 | rootSchema.addConstraintToSubschema( |
883 | 0 | makeMultipleOfIntConstraint(itr->second), |
884 | 0 | &subschema); |
885 | 0 | } else if (itr->second.maybeDouble()) { |
886 | 0 | rootSchema.addConstraintToSubschema( |
887 | 0 | makeMultipleOfDoubleConstraint(itr->second), |
888 | 0 | &subschema); |
889 | 0 | } else { |
890 | 0 | throwRuntimeError("Expected a numeric value for " |
891 | 0 | " 'divisibleBy' constraint."); |
892 | 0 | } |
893 | 74 | } else { |
894 | 74 | throwRuntimeError( |
895 | 74 | "'divisibleBy' constraint not valid after draft 3"); |
896 | 74 | } |
897 | 74 | } |
898 | | |
899 | 492k | if ((itr = object.find("enum")) != object.end()) { |
900 | 8.82k | rootSchema.addConstraintToSubschema(makeEnumConstraint(itr->second), &subschema); |
901 | 8.82k | } |
902 | | |
903 | | // Draft 3's 'extends' keyword is equivalent to 'allOf' in later drafts |
904 | | // and accepts either a single schema or an array of schemas. |
905 | 492k | if (m_version == kDraft3 && (itr = object.find("extends")) != object.end()) { |
906 | 0 | rootSchema.addConstraintToSubschema( |
907 | 0 | makeExtendsConstraint(rootSchema, rootNode, itr->second, |
908 | 0 | updatedScope, nodePath + "/extends", fetchDoc, |
909 | 0 | docCache, schemaRegistry), |
910 | 0 | &subschema); |
911 | 0 | } |
912 | | |
913 | 492k | if ((itr = object.find("format")) != object.end()) { |
914 | 6.04k | rootSchema.addConstraintToSubschema(makeFormatConstraint(itr->second), &subschema); |
915 | 6.04k | } |
916 | | |
917 | 492k | { |
918 | 492k | const typename AdapterType::Object::const_iterator itemsItr = |
919 | 492k | object.find("items"); |
920 | | |
921 | 492k | if (object.end() != itemsItr) { |
922 | 43.6k | if (!itemsItr->second.isArray()) { |
923 | 15.9k | rootSchema.addConstraintToSubschema( |
924 | 15.9k | makeSingularItemsConstraint(rootSchema, rootNode, |
925 | 15.9k | itemsItr->second, updatedScope, |
926 | 15.9k | nodePath + "/items", fetchDoc, docCache, |
927 | 15.9k | schemaRegistry), |
928 | 15.9k | &subschema); |
929 | | |
930 | 27.6k | } else { |
931 | 27.6k | const typename AdapterType::Object::const_iterator |
932 | 27.6k | additionalItemsItr = object.find("additionalItems"); |
933 | 27.6k | rootSchema.addConstraintToSubschema( |
934 | 27.6k | makeLinearItemsConstraint(rootSchema, rootNode, |
935 | 27.6k | itemsItr != object.end() ? &itemsItr->second : nullptr, |
936 | 27.6k | additionalItemsItr != object.end() ? &additionalItemsItr->second : nullptr, |
937 | 27.6k | updatedScope, nodePath + "/items", |
938 | 27.6k | nodePath + "/additionalItems", fetchDoc, |
939 | 27.6k | docCache, schemaRegistry), |
940 | 27.6k | &subschema); |
941 | 27.6k | } |
942 | 43.6k | } |
943 | 492k | } |
944 | | |
945 | 492k | { |
946 | 492k | const typename AdapterType::Object::const_iterator ifItr = object.find("if"); |
947 | 492k | const typename AdapterType::Object::const_iterator thenItr = object.find("then"); |
948 | 492k | const typename AdapterType::Object::const_iterator elseItr = object.find("else"); |
949 | | |
950 | 492k | if (object.end() != ifItr) { |
951 | 4.85k | if (m_version == kDraft7) { |
952 | 4.30k | rootSchema.addConstraintToSubschema( |
953 | 4.30k | makeConditionalConstraint(rootSchema, rootNode, |
954 | 4.30k | ifItr->second, |
955 | 4.30k | thenItr == object.end() ? nullptr : &thenItr->second, |
956 | 4.30k | elseItr == object.end() ? nullptr : &elseItr->second, |
957 | 4.30k | updatedScope, nodePath, fetchDoc, docCache, schemaRegistry), |
958 | 4.30k | &subschema); |
959 | 4.30k | } else { |
960 | 553 | throwRuntimeError("Not supported"); |
961 | 553 | } |
962 | 4.85k | } |
963 | 492k | } |
964 | | |
965 | 492k | if (m_version == kDraft7) { |
966 | 216k | if ((itr = object.find("exclusiveMaximum")) != object.end()) { |
967 | 776 | rootSchema.addConstraintToSubschema( |
968 | 776 | makeMaximumConstraintExclusive(itr->second), |
969 | 776 | &subschema); |
970 | 776 | } |
971 | | |
972 | 216k | if ((itr = object.find("maximum")) != object.end()) { |
973 | 1.32k | rootSchema.addConstraintToSubschema( |
974 | 1.32k | makeMaximumConstraint<AdapterType>(itr->second, nullptr), |
975 | 1.32k | &subschema); |
976 | 1.32k | } |
977 | 275k | } else if ((itr = object.find("maximum")) != object.end()) { |
978 | 1.52k | typename AdapterType::Object::const_iterator exclusiveMaximumItr = |
979 | 1.52k | object.find("exclusiveMaximum"); |
980 | 1.52k | if (exclusiveMaximumItr == object.end()) { |
981 | 1.03k | rootSchema.addConstraintToSubschema( |
982 | 1.03k | makeMaximumConstraint<AdapterType>(itr->second, nullptr), |
983 | 1.03k | &subschema); |
984 | 1.03k | } else { |
985 | 486 | rootSchema.addConstraintToSubschema( |
986 | 486 | makeMaximumConstraint(itr->second, &exclusiveMaximumItr->second), |
987 | 486 | &subschema); |
988 | 486 | } |
989 | 274k | } else if (object.find("exclusiveMaximum") != object.end()) { |
990 | 43 | throwRuntimeError("'exclusiveMaximum' constraint only valid if a 'maximum' " |
991 | 43 | "constraint is also present"); |
992 | 43 | } |
993 | | |
994 | 492k | if ((itr = object.find("maxItems")) != object.end()) { |
995 | 3.00k | rootSchema.addConstraintToSubschema( |
996 | 3.00k | makeMaxItemsConstraint(itr->second), &subschema); |
997 | 3.00k | } |
998 | | |
999 | 492k | if ((itr = object.find("maxLength")) != object.end()) { |
1000 | 3.66k | rootSchema.addConstraintToSubschema( |
1001 | 3.66k | makeMaxLengthConstraint(itr->second), &subschema); |
1002 | 3.66k | } |
1003 | | |
1004 | 492k | if ((itr = object.find("maxProperties")) != object.end()) { |
1005 | 2.32k | rootSchema.addConstraintToSubschema( |
1006 | 2.32k | makeMaxPropertiesConstraint(itr->second), &subschema); |
1007 | 2.32k | } |
1008 | | |
1009 | 492k | if (m_version == kDraft7) { |
1010 | 215k | if ((itr = object.find("exclusiveMinimum")) != object.end()) { |
1011 | 1.02k | rootSchema.addConstraintToSubschema( |
1012 | 1.02k | makeMinimumConstraintExclusive(itr->second), &subschema); |
1013 | 1.02k | } |
1014 | | |
1015 | 215k | if ((itr = object.find("minimum")) != object.end()) { |
1016 | 3.86k | rootSchema.addConstraintToSubschema( |
1017 | 3.86k | makeMinimumConstraint<AdapterType>(itr->second, nullptr), |
1018 | 3.86k | &subschema); |
1019 | 3.86k | } |
1020 | 276k | } else if ((itr = object.find("minimum")) != object.end()) { |
1021 | 2.66k | typename AdapterType::Object::const_iterator exclusiveMinimumItr = object.find("exclusiveMinimum"); |
1022 | 2.66k | if (exclusiveMinimumItr == object.end()) { |
1023 | 2.64k | rootSchema.addConstraintToSubschema( |
1024 | 2.64k | makeMinimumConstraint<AdapterType>(itr->second, nullptr), |
1025 | 2.64k | &subschema); |
1026 | 2.64k | } else { |
1027 | 12 | rootSchema.addConstraintToSubschema( |
1028 | 12 | makeMinimumConstraint<AdapterType>(itr->second, &exclusiveMinimumItr->second), |
1029 | 12 | &subschema); |
1030 | 12 | } |
1031 | 273k | } else if (object.find("exclusiveMinimum") != object.end()) { |
1032 | 197 | throwRuntimeError("'exclusiveMinimum' constraint only valid if a 'minimum' " |
1033 | 197 | "constraint is also present"); |
1034 | 197 | } |
1035 | | |
1036 | 492k | if ((itr = object.find("minItems")) != object.end()) { |
1037 | 3.46k | rootSchema.addConstraintToSubschema( |
1038 | 3.46k | makeMinItemsConstraint(itr->second), &subschema); |
1039 | 3.46k | } |
1040 | | |
1041 | 492k | if ((itr = object.find("minLength")) != object.end()) { |
1042 | 4.80k | rootSchema.addConstraintToSubschema( |
1043 | 4.80k | makeMinLengthConstraint(itr->second), &subschema); |
1044 | 4.80k | } |
1045 | | |
1046 | 492k | if ((itr = object.find("minProperties")) != object.end()) { |
1047 | 2.75k | rootSchema.addConstraintToSubschema( |
1048 | 2.75k | makeMinPropertiesConstraint(itr->second), &subschema); |
1049 | 2.75k | } |
1050 | | |
1051 | 492k | if ((itr = object.find("multipleOf")) != object.end()) { |
1052 | 8.17k | if (m_version == kDraft3) { |
1053 | 0 | throwRuntimeError("'multipleOf' constraint not available in draft 3"); |
1054 | 8.17k | } else if (itr->second.maybeInteger()) { |
1055 | 6.16k | rootSchema.addConstraintToSubschema( |
1056 | 6.16k | makeMultipleOfIntConstraint(itr->second), |
1057 | 6.16k | &subschema); |
1058 | 6.16k | } else if (itr->second.maybeDouble()) { |
1059 | 1.92k | rootSchema.addConstraintToSubschema( |
1060 | 1.92k | makeMultipleOfDoubleConstraint(itr->second), |
1061 | 1.92k | &subschema); |
1062 | 1.92k | } else { |
1063 | 89 | throwRuntimeError("Expected a numeric value for 'multipleOf' constraint."); |
1064 | 89 | } |
1065 | 8.17k | } |
1066 | | |
1067 | 492k | if ((itr = object.find("not")) != object.end()) { |
1068 | 13.3k | rootSchema.addConstraintToSubschema( |
1069 | 13.3k | makeNotConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/not", fetchDoc, |
1070 | 13.3k | docCache, schemaRegistry), |
1071 | 13.3k | &subschema); |
1072 | 13.3k | } |
1073 | | |
1074 | 492k | if ((itr = object.find("oneOf")) != object.end()) { |
1075 | 14.3k | rootSchema.addConstraintToSubschema( |
1076 | 14.3k | makeOneOfConstraint(rootSchema, rootNode, itr->second, updatedScope, nodePath + "/oneOf", fetchDoc, |
1077 | 14.3k | docCache, schemaRegistry), |
1078 | 14.3k | &subschema); |
1079 | 14.3k | } |
1080 | | |
1081 | 492k | if ((itr = object.find("pattern")) != object.end()) { |
1082 | 18.6k | rootSchema.addConstraintToSubschema( |
1083 | 18.6k | makePatternConstraint(itr->second), &subschema); |
1084 | 18.6k | } |
1085 | | |
1086 | 492k | { |
1087 | | // Check for schema keywords that require the creation of a |
1088 | | // PropertiesConstraint instance. |
1089 | 492k | const typename AdapterType::Object::const_iterator |
1090 | 492k | propertiesItr = object.find("properties"), |
1091 | 492k | patternPropertiesItr = object.find("patternProperties"), |
1092 | 492k | additionalPropertiesItr = object.find("additionalProperties"); |
1093 | 492k | if (object.end() != propertiesItr || |
1094 | 433k | object.end() != patternPropertiesItr || |
1095 | 414k | object.end() != additionalPropertiesItr) { |
1096 | 33.7k | rootSchema.addConstraintToSubschema( |
1097 | 33.7k | makePropertiesConstraint(rootSchema, rootNode, |
1098 | 33.7k | propertiesItr != object.end() ? &propertiesItr->second : nullptr, |
1099 | 33.7k | patternPropertiesItr != object.end() ? &patternPropertiesItr->second : nullptr, |
1100 | 33.7k | additionalPropertiesItr != object.end() ? &additionalPropertiesItr->second : nullptr, |
1101 | 33.7k | updatedScope, nodePath + "/properties", |
1102 | 33.7k | nodePath + "/patternProperties", |
1103 | 33.7k | nodePath + "/additionalProperties", |
1104 | 33.7k | fetchDoc, &subschema, docCache, schemaRegistry), |
1105 | 33.7k | &subschema); |
1106 | 33.7k | } |
1107 | 492k | } |
1108 | | |
1109 | 492k | if ((itr = object.find("propertyNames")) != object.end()) { |
1110 | 4.42k | if (m_version == kDraft7) { |
1111 | 3.70k | rootSchema.addConstraintToSubschema( |
1112 | 3.70k | makePropertyNamesConstraint(rootSchema, rootNode, itr->second, updatedScope, |
1113 | 3.70k | nodePath, fetchDoc, docCache, schemaRegistry), |
1114 | 3.70k | &subschema); |
1115 | 3.70k | } else { |
1116 | 727 | throwRuntimeError("Not supported"); |
1117 | 727 | } |
1118 | 4.42k | } |
1119 | | |
1120 | 492k | if ((itr = object.find("required")) != object.end()) { |
1121 | 9.24k | if (m_version == kDraft3) { |
1122 | 0 | if (parentSubschema && ownName) { |
1123 | 0 | std::optional<constraints::RequiredConstraint> constraint = |
1124 | 0 | makeRequiredConstraintForSelf(itr->second, *ownName); |
1125 | 0 | if (constraint) { |
1126 | 0 | rootSchema.addConstraintToSubschema(*constraint, parentSubschema); |
1127 | 0 | } |
1128 | 0 | } else { |
1129 | 0 | throwRuntimeError("'required' constraint not valid here"); |
1130 | 0 | } |
1131 | 9.24k | } else { |
1132 | 9.24k | rootSchema.addConstraintToSubschema(makeRequiredConstraint(itr->second), &subschema); |
1133 | 9.24k | } |
1134 | 9.24k | } |
1135 | | |
1136 | 492k | if ((itr = object.find("title")) != object.end()) { |
1137 | 4.25k | if (itr->second.maybeString()) { |
1138 | 4.17k | rootSchema.setSubschemaTitle(&subschema, itr->second.asString()); |
1139 | 4.17k | } else { |
1140 | 75 | throwRuntimeError("'title' attribute should have a string value"); |
1141 | 75 | } |
1142 | 4.25k | } |
1143 | | |
1144 | 492k | if ((itr = object.find("uniqueItems")) != object.end()) { |
1145 | 1.69k | std::optional<constraints::UniqueItemsConstraint> constraint = makeUniqueItemsConstraint(itr->second); |
1146 | 1.69k | if (constraint) { |
1147 | 1.54k | rootSchema.addConstraintToSubschema(*constraint, &subschema); |
1148 | 1.54k | } |
1149 | 1.69k | } |
1150 | | |
1151 | 492k | for (const auto & constraintBuilder : constraintBuilders) { |
1152 | 0 | if ((itr = object.find(constraintBuilder.first)) != object.end()) { |
1153 | 0 | constraints::Constraint *constraint = nullptr; |
1154 | 0 | #if VALIJSON_USE_EXCEPTIONS |
1155 | 0 | try { |
1156 | 0 | #endif |
1157 | 0 | constraint = constraintBuilder.second->make(itr->second); |
1158 | 0 | rootSchema.addConstraintToSubschema(*constraint, &subschema); |
1159 | 0 | delete constraint; |
1160 | 0 | #if VALIJSON_USE_EXCEPTIONS |
1161 | 0 | } catch (...) { |
1162 | 0 | delete constraint; |
1163 | 0 | throw; |
1164 | 0 | } |
1165 | 0 | #endif |
1166 | 0 | } |
1167 | 0 | } |
1168 | 492k | } |
1169 | | |
1170 | | /** |
1171 | | * @brief Resolves a chain of JSON References before populating a schema |
1172 | | * |
1173 | | * This helper function is used directly by the publicly visible |
1174 | | * populateSchema function. It ensures that the node being parsed is a |
1175 | | * concrete node, and not a JSON Reference. This function will call itself |
1176 | | * recursively to resolve references until a concrete node is found. |
1177 | | * |
1178 | | * @param rootSchema The Schema instance, and root subschema, through |
1179 | | * which other subschemas can be created and modified |
1180 | | * @param rootNode Reference to the node from which JSON References |
1181 | | * will be resolved when they refer to the current |
1182 | | * document |
1183 | | * @param node Reference to node to parse |
1184 | | * @param subschema Reference to Schema to populate |
1185 | | * @param currentScope URI for current resolution scope |
1186 | | * @param nodePath JSON Pointer representing path to current node |
1187 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
1188 | | * @param parentSchema Optional pointer to the parent schema, used to |
1189 | | * support required keyword in Draft 3 |
1190 | | * @param ownName Optional pointer to a node name, used to support |
1191 | | * the 'required' keyword in Draft 3 |
1192 | | * @param docCache Cache of resolved and fetched remote documents |
1193 | | * @param schemaRegistry Registry of populated schemas |
1194 | | */ |
1195 | | template<typename AdapterType> |
1196 | | void resolveThenPopulateSchema( |
1197 | | Schema &rootSchema, |
1198 | | const AdapterType &rootNode, |
1199 | | const AdapterType &node, |
1200 | | const Subschema &subschema, |
1201 | | const std::optional<std::string> currentScope, |
1202 | | const std::string &nodePath, |
1203 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1204 | | const Subschema *parentSchema, |
1205 | | const std::string *ownName, |
1206 | | typename DocumentCache<AdapterType>::Type &docCache, |
1207 | | SchemaRegistry &schemaRegistry) |
1208 | 57.9k | { |
1209 | 57.9k | std::string jsonRef; |
1210 | 57.9k | if (!extractJsonReference(node, jsonRef)) { |
1211 | 47.5k | populateSchema(rootSchema, rootNode, node, subschema, currentScope, nodePath, fetchDoc, parentSchema, |
1212 | 47.5k | ownName, docCache, schemaRegistry); |
1213 | 47.5k | return; |
1214 | 47.5k | } |
1215 | | |
1216 | | // Returns a document URI if the reference points somewhere |
1217 | | // other than the current document |
1218 | 10.3k | const std::optional<std::string> documentUri = internal::json_reference::getJsonReferenceUri(jsonRef); |
1219 | | |
1220 | | // Extract JSON Pointer from JSON Reference |
1221 | 10.3k | const std::string actualJsonPointer = sanitiseJsonPointer( |
1222 | 10.3k | internal::json_reference::getJsonReferencePointer(jsonRef)); |
1223 | | |
1224 | 10.3k | const std::optional<std::string> actualDocumentUri = |
1225 | 10.3k | resolveDocumentUri(currentScope, documentUri); |
1226 | | |
1227 | 10.3k | if (!actualJsonPointer.empty() && actualJsonPointer[0] != '/') { |
1228 | 2.39k | const std::string idRef = actualDocumentUri ? |
1229 | 1.88k | (*actualDocumentUri + "#" + actualJsonPointer) : |
1230 | 2.39k | ("#" + actualJsonPointer); |
1231 | 2.39k | std::string registeredPath; |
1232 | 2.39k | std::optional<std::string> registeredScope; |
1233 | 2.39k | if (findSchemaById(rootNode, currentScope, idRef, |
1234 | 2.39k | "", registeredPath, registeredScope)) { |
1235 | 0 | const AdapterType registeredRoot = |
1236 | 0 | internal::json_pointer::resolveJsonPointerStrict( |
1237 | 0 | rootNode, registeredPath); |
1238 | |
|
1239 | 0 | resolveThenPopulateSchema(rootSchema, registeredRoot, |
1240 | 0 | registeredRoot, subschema, registeredScope, |
1241 | 0 | "", fetchDoc, parentSchema, ownName, |
1242 | 0 | docCache, schemaRegistry); |
1243 | 0 | return; |
1244 | 0 | } |
1245 | 2.39k | } |
1246 | | |
1247 | 10.3k | if (actualDocumentUri && (!currentScope || *actualDocumentUri != *currentScope)) { |
1248 | 5.07k | std::string registeredPath; |
1249 | 5.07k | std::optional<std::string> registeredScope; |
1250 | 5.07k | if (findSchemaById(rootNode, currentScope, *actualDocumentUri, |
1251 | 5.07k | "", registeredPath, registeredScope)) { |
1252 | 129 | const AdapterType registeredRoot = |
1253 | 129 | internal::json_pointer::resolveJsonPointerStrict( |
1254 | 129 | rootNode, registeredPath); |
1255 | 129 | const AdapterType referencedAdapter = actualJsonPointer.empty() |
1256 | 129 | ? registeredRoot |
1257 | 129 | : internal::json_pointer::resolveJsonPointerStrict( |
1258 | 18 | registeredRoot, actualJsonPointer); |
1259 | | |
1260 | 129 | resolveThenPopulateSchema(rootSchema, registeredRoot, |
1261 | 129 | referencedAdapter, subschema, registeredScope, |
1262 | 129 | actualJsonPointer, fetchDoc, |
1263 | 129 | parentSchema, ownName, docCache, schemaRegistry); |
1264 | 129 | return; |
1265 | 129 | } |
1266 | | |
1267 | | // Resolve reference against remote document |
1268 | 4.94k | if (!fetchDoc) { |
1269 | 4.94k | throwRuntimeError("Fetching of remote JSON References not enabled."); |
1270 | 4.94k | } |
1271 | | |
1272 | 4.94k | const typename DocumentCache<AdapterType>::DocumentType *newDoc = fetchDoc(*actualDocumentUri); |
1273 | | |
1274 | | // Can't proceed without the remote document |
1275 | 4.94k | if (!newDoc) { |
1276 | 0 | throwRuntimeError("Failed to fetch referenced schema document: " + *actualDocumentUri); |
1277 | 0 | } |
1278 | | |
1279 | | // Add to document cache |
1280 | 4.94k | typedef typename DocumentCache<AdapterType>::Type::value_type DocCacheValueType; |
1281 | | |
1282 | 4.94k | docCache.insert(DocCacheValueType(*actualDocumentUri, newDoc)); |
1283 | | |
1284 | 4.94k | const AdapterType newRootNode(*newDoc); |
1285 | | |
1286 | 4.94k | const AdapterType &referencedAdapter = |
1287 | 4.94k | internal::json_pointer::resolveJsonPointerStrict(newRootNode, actualJsonPointer); |
1288 | | |
1289 | | // TODO: Need to detect degenerate circular references |
1290 | 4.94k | resolveThenPopulateSchema(rootSchema, newRootNode, referencedAdapter, |
1291 | 4.94k | subschema, actualDocumentUri, actualJsonPointer, fetchDoc, |
1292 | 4.94k | parentSchema, ownName, docCache, schemaRegistry); |
1293 | | |
1294 | 5.27k | } else if (!actualJsonPointer.empty()) { |
1295 | 5.06k | const AdapterType &referencedAdapter = |
1296 | 5.06k | internal::json_pointer::resolveJsonPointerStrict(rootNode, actualJsonPointer); |
1297 | | |
1298 | 5.06k | std::optional<std::string> referencedScope = currentScope; |
1299 | 5.06k | findScopeForPath(rootNode, currentScope, actualJsonPointer, "", |
1300 | 5.06k | referencedScope); |
1301 | | |
1302 | 5.06k | resolveThenPopulateSchema(rootSchema, rootNode, referencedAdapter, |
1303 | 5.06k | subschema, referencedScope, actualJsonPointer, fetchDoc, |
1304 | 5.06k | parentSchema, ownName, docCache, schemaRegistry); |
1305 | 5.06k | } else { |
1306 | 212 | throwRuntimeError("Cannot resolve reference \"" + jsonRef + "\"."); |
1307 | 212 | } |
1308 | 10.3k | } |
1309 | | |
1310 | | /** |
1311 | | * @brief Make a new AllOfConstraint object |
1312 | | * |
1313 | | * @param rootSchema The Schema instance, and root subschema, through |
1314 | | * which other subschemas can be created and modified |
1315 | | * @param rootNode Reference to the node from which JSON References |
1316 | | * will be resolved when they refer to the current |
1317 | | * document; used for recursive parsing of schemas |
1318 | | * @param node JSON node containing an array of child schemas |
1319 | | * @param currentScope URI for current resolution scope |
1320 | | * @param nodePath JSON Pointer representing path to current node |
1321 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
1322 | | * @param docCache Cache of resolved and fetched remote documents |
1323 | | * @param schemaRegistry Registry of populated schemas |
1324 | | * |
1325 | | * @return pointer to a new AllOfConstraint object that belongs to the |
1326 | | * caller |
1327 | | */ |
1328 | | template<typename AdapterType> |
1329 | | constraints::AllOfConstraint makeAllOfConstraint( |
1330 | | Schema &rootSchema, |
1331 | | const AdapterType &rootNode, |
1332 | | const AdapterType &node, |
1333 | | const std::optional<std::string> currentScope, |
1334 | | const std::string &nodePath, |
1335 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1336 | | typename DocumentCache<AdapterType>::Type &docCache, |
1337 | | SchemaRegistry &schemaRegistry) |
1338 | 19.2k | { |
1339 | 19.2k | if (!node.maybeArray()) { |
1340 | 72 | throwRuntimeError("Expected array value for 'allOf' constraint."); |
1341 | 72 | } |
1342 | | |
1343 | 19.2k | constraints::AllOfConstraint constraint; |
1344 | | |
1345 | 19.2k | int index = 0; |
1346 | 61.4k | for (const AdapterType schemaNode : node.asArray()) { |
1347 | 61.4k | if (schemaNode.maybeObject() || (m_version == kDraft7 && schemaNode.isBool())) { |
1348 | 61.2k | const std::string childPath = nodePath + "/" + std::to_string(index); |
1349 | 61.2k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1350 | 61.2k | rootSchema, rootNode, schemaNode, currentScope, |
1351 | 61.2k | childPath, fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
1352 | 61.2k | constraint.addSubschema(subschema); |
1353 | 61.2k | index++; |
1354 | 61.2k | } else { |
1355 | 206 | throwRuntimeError("Expected element to be a valid schema in 'allOf' constraint."); |
1356 | 206 | } |
1357 | 61.4k | } |
1358 | | |
1359 | 19.2k | return constraint; |
1360 | 19.2k | } |
1361 | | |
1362 | | /** |
1363 | | * @brief Make a new constraint object for Draft 3's 'extends' keyword. |
1364 | | * |
1365 | | * The value of 'extends' may be either a single schema or an array of |
1366 | | * schemas. The instance is required to validate against every schema |
1367 | | * listed, so the keyword is modelled as an AllOfConstraint. |
1368 | | */ |
1369 | | template<typename AdapterType> |
1370 | | constraints::AllOfConstraint makeExtendsConstraint( |
1371 | | Schema &rootSchema, |
1372 | | const AdapterType &rootNode, |
1373 | | const AdapterType &node, |
1374 | | const std::optional<std::string> currentScope, |
1375 | | const std::string &nodePath, |
1376 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1377 | | typename DocumentCache<AdapterType>::Type &docCache, |
1378 | | SchemaRegistry &schemaRegistry) |
1379 | 0 | { |
1380 | 0 | constraints::AllOfConstraint constraint; |
1381 | |
|
1382 | 0 | if (node.maybeArray()) { |
1383 | 0 | int index = 0; |
1384 | 0 | for (const AdapterType schemaNode : node.asArray()) { |
1385 | 0 | if (!schemaNode.maybeObject()) { |
1386 | 0 | throwRuntimeError("Expected element to be a valid schema " |
1387 | 0 | "in 'extends' constraint."); |
1388 | 0 | } |
1389 | 0 | const std::string childPath = nodePath + "/" |
1390 | 0 | + std::to_string(index); |
1391 | 0 | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1392 | 0 | rootSchema, rootNode, schemaNode, currentScope, |
1393 | 0 | childPath, fetchDoc, nullptr, nullptr, docCache, |
1394 | 0 | schemaRegistry); |
1395 | 0 | constraint.addSubschema(subschema); |
1396 | 0 | index++; |
1397 | 0 | } |
1398 | 0 | } else if (node.maybeObject()) { |
1399 | 0 | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1400 | 0 | rootSchema, rootNode, node, currentScope, nodePath, |
1401 | 0 | fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
1402 | 0 | constraint.addSubschema(subschema); |
1403 | 0 | } else { |
1404 | 0 | throwRuntimeError("Expected schema or array of schemas for " |
1405 | 0 | "'extends' constraint."); |
1406 | 0 | } |
1407 | |
|
1408 | 0 | return constraint; |
1409 | 0 | } |
1410 | | |
1411 | | /** |
1412 | | * @brief Make a new AnyOfConstraint object |
1413 | | * |
1414 | | * @param rootSchema The Schema instance, and root subschema, through |
1415 | | * which other subschemas can be created and modified |
1416 | | * @param rootNode Reference to the node from which JSON References |
1417 | | * will be resolved when they refer to the current |
1418 | | * document; used for recursive parsing of schemas |
1419 | | * @param node JSON node containing an array of child schemas |
1420 | | * @param currentScope URI for current resolution scope |
1421 | | * @param nodePath JSON Pointer representing path to current node |
1422 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
1423 | | * @param docCache Cache of resolved and fetched remote documents |
1424 | | * @param schemaRegistry Registry of populated schemas |
1425 | | * |
1426 | | * @return pointer to a new AnyOfConstraint object that belongs to the |
1427 | | * caller |
1428 | | */ |
1429 | | template<typename AdapterType> |
1430 | | constraints::AnyOfConstraint makeAnyOfConstraint( |
1431 | | Schema &rootSchema, |
1432 | | const AdapterType &rootNode, |
1433 | | const AdapterType &node, |
1434 | | const std::optional<std::string> currentScope, |
1435 | | const std::string &nodePath, |
1436 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1437 | | typename DocumentCache<AdapterType>::Type &docCache, |
1438 | | SchemaRegistry &schemaRegistry) |
1439 | 21.1k | { |
1440 | 21.1k | if (!node.maybeArray()) { |
1441 | 369 | throwRuntimeError("Expected array value for 'anyOf' constraint."); |
1442 | 369 | } |
1443 | | |
1444 | 21.1k | constraints::AnyOfConstraint constraint; |
1445 | | |
1446 | 21.1k | int index = 0; |
1447 | 42.6k | for (const AdapterType schemaNode : node.asArray()) { |
1448 | 42.6k | if (schemaNode.maybeObject() || (m_version == kDraft7 && schemaNode.isBool())) { |
1449 | 42.4k | const std::string childPath = nodePath + "/" + std::to_string(index); |
1450 | 42.4k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1451 | 42.4k | rootSchema, rootNode, schemaNode, currentScope, |
1452 | 42.4k | childPath, fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
1453 | 42.4k | constraint.addSubschema(subschema); |
1454 | 42.4k | index++; |
1455 | 42.4k | } else { |
1456 | 191 | throwRuntimeError("Expected array element to be a valid schema in 'anyOf' constraint."); |
1457 | 191 | } |
1458 | 42.6k | } |
1459 | | |
1460 | 21.1k | return constraint; |
1461 | 21.1k | } |
1462 | | |
1463 | | /** |
1464 | | * @brief Make a new ConditionalConstraint object. |
1465 | | * |
1466 | | * @param rootSchema The Schema instance, and root subschema, |
1467 | | * through which other subschemas can be |
1468 | | * created and modified |
1469 | | * @param rootNode Reference to the node from which JSON |
1470 | | * References will be resolved when they refer |
1471 | | * to the current document; used for recursive |
1472 | | * parsing of schemas |
1473 | | * @param ifNode Schema that will be used to evaluate the |
1474 | | * conditional. |
1475 | | * @param thenNode Optional pointer to a JSON node containing |
1476 | | * a schema that will be used when the conditional |
1477 | | * evaluates to true. |
1478 | | * @param elseNode Optional pointer to a JSON node containing |
1479 | | * a schema that will be used when the conditional |
1480 | | * evaluates to false. |
1481 | | * @param currentScope URI for current resolution scope |
1482 | | * @param nodePath JSON Pointer representing the path to |
1483 | | * the 'contains' node |
1484 | | * @param fetchDoc Function to fetch remote JSON documents |
1485 | | * (optional) |
1486 | | * @param docCache Cache of resolved and fetched remote |
1487 | | * documents |
1488 | | * @param schemaRegistry Registry of populated schemas |
1489 | | * |
1490 | | * @return pointer to a new ContainsConstraint that belongs to the caller |
1491 | | */ |
1492 | | template<typename AdapterType> |
1493 | | constraints::ConditionalConstraint makeConditionalConstraint( |
1494 | | Schema &rootSchema, |
1495 | | const AdapterType &rootNode, |
1496 | | const AdapterType &ifNode, |
1497 | | const AdapterType *thenNode, |
1498 | | const AdapterType *elseNode, |
1499 | | const std::optional<std::string> currentScope, |
1500 | | const std::string &nodePath, |
1501 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1502 | | typename DocumentCache<AdapterType>::Type &docCache, |
1503 | | SchemaRegistry &schemaRegistry) |
1504 | 4.30k | { |
1505 | 4.30k | constraints::ConditionalConstraint constraint; |
1506 | | |
1507 | 4.30k | const Subschema *ifSubschema = makeOrReuseSchema<AdapterType>( |
1508 | 4.30k | rootSchema, rootNode, ifNode, currentScope, |
1509 | 4.30k | nodePath + "/if", fetchDoc, nullptr, nullptr, docCache, |
1510 | 4.30k | schemaRegistry); |
1511 | 4.30k | constraint.setIfSubschema(ifSubschema); |
1512 | | |
1513 | 4.30k | if (thenNode) { |
1514 | 743 | const Subschema *thenSubschema = makeOrReuseSchema<AdapterType>( |
1515 | 743 | rootSchema, rootNode, *thenNode, currentScope, nodePath + "/then", fetchDoc, nullptr, |
1516 | 743 | nullptr, docCache, schemaRegistry); |
1517 | 743 | constraint.setThenSubschema(thenSubschema); |
1518 | 743 | } |
1519 | | |
1520 | 4.30k | if (elseNode) { |
1521 | 1.00k | const Subschema *elseSubschema = makeOrReuseSchema<AdapterType>( |
1522 | 1.00k | rootSchema, rootNode, *elseNode, currentScope, nodePath + "/else", fetchDoc, nullptr, |
1523 | 1.00k | nullptr, docCache, schemaRegistry); |
1524 | 1.00k | constraint.setElseSubschema(elseSubschema); |
1525 | 1.00k | } |
1526 | | |
1527 | 4.30k | return constraint; |
1528 | 4.30k | } |
1529 | | |
1530 | | /** |
1531 | | * @brief Make a new ConstConstraint object. |
1532 | | * |
1533 | | * @param node JSON node containing an arbitrary value |
1534 | | * |
1535 | | * @return pointer to a new MinimumConstraint that belongs to the caller |
1536 | | */ |
1537 | | template<typename AdapterType> |
1538 | | constraints::ConstConstraint makeConstConstraint(const AdapterType &node) |
1539 | 10.9k | { |
1540 | 10.9k | constraints::ConstConstraint constraint; |
1541 | 10.9k | constraint.setValue(node); |
1542 | 10.9k | return constraint; |
1543 | 10.9k | } |
1544 | | |
1545 | | /** |
1546 | | * @brief Make a new ContainsConstraint object. |
1547 | | * |
1548 | | * @param rootSchema The Schema instance, and root subschema, |
1549 | | * through which other subschemas can be |
1550 | | * created and modified |
1551 | | * @param rootNode Reference to the node from which JSON |
1552 | | * References will be resolved when they refer |
1553 | | * to the current document; used for recursive |
1554 | | * parsing of schemas |
1555 | | * @param contains Optional pointer to a JSON node containing |
1556 | | * an object mapping property names to |
1557 | | * schemas. |
1558 | | * @param currentScope URI for current resolution scope |
1559 | | * @param containsPath JSON Pointer representing the path to |
1560 | | * the 'contains' node |
1561 | | * @param fetchDoc Function to fetch remote JSON documents |
1562 | | * (optional) |
1563 | | * @param docCache Cache of resolved and fetched remote |
1564 | | * documents |
1565 | | * @param schemaRegistry Registry of populated schemas |
1566 | | * |
1567 | | * @return pointer to a new ContainsConstraint that belongs to the caller |
1568 | | */ |
1569 | | template<typename AdapterType> |
1570 | | constraints::ContainsConstraint makeContainsConstraint( |
1571 | | Schema &rootSchema, |
1572 | | const AdapterType &rootNode, |
1573 | | const AdapterType &contains, |
1574 | | const std::optional<std::string> currentScope, |
1575 | | const std::string &containsPath, |
1576 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1577 | | typename DocumentCache<AdapterType>::Type &docCache, |
1578 | | SchemaRegistry &schemaRegistry) |
1579 | 18.8k | { |
1580 | 18.8k | constraints::ContainsConstraint constraint; |
1581 | | |
1582 | 18.8k | if (contains.isObject() || (m_version == kDraft7 && contains.maybeBool())) { |
1583 | 12.9k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1584 | 12.9k | rootSchema, rootNode, contains, currentScope, containsPath, |
1585 | 12.9k | fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
1586 | 12.9k | constraint.setSubschema(subschema); |
1587 | | |
1588 | 12.9k | } else if (contains.maybeObject()) { |
1589 | | // If a loosely-typed Adapter type is being used, then we'll |
1590 | | // assume that an empty schema has been provided. |
1591 | 5.74k | constraint.setSubschema(rootSchema.emptySubschema()); |
1592 | | |
1593 | 5.74k | } else { |
1594 | | // All other formats will result in an exception being thrown. |
1595 | 114 | throwRuntimeError("Expected valid schema for 'contains' constraint."); |
1596 | 114 | } |
1597 | | |
1598 | 18.8k | return constraint; |
1599 | 18.8k | } |
1600 | | |
1601 | | /** |
1602 | | * @brief Make a new DependenciesConstraint object |
1603 | | * |
1604 | | * The dependencies for a property can be defined several ways. When parsing |
1605 | | * a Draft 4 schema, the following can be used: |
1606 | | * - an array that lists the name of each property that must be present |
1607 | | * if the dependent property is present |
1608 | | * - an object that specifies a schema which must be satisfied if the |
1609 | | * dependent property is present |
1610 | | * |
1611 | | * When parsing a Draft 3 schema, in addition to the formats above, the |
1612 | | * following format can be used: |
1613 | | * - a string that names a single property that must be present if the |
1614 | | * dependent property is present |
1615 | | * |
1616 | | * Multiple methods can be used in the same dependency constraint. |
1617 | | * |
1618 | | * If the format of any part of the dependency node does not match one |
1619 | | * of these formats, an exception will be thrown. |
1620 | | * |
1621 | | * @param rootSchema The Schema instance, and root subschema, through |
1622 | | * which other subschemas can be created and modified |
1623 | | * @param rootNode Reference to the node from which JSON References |
1624 | | * will be resolved when they refer to the current |
1625 | | * document; used for recursive parsing of schemas |
1626 | | * @param node JSON node containing an object that defines a |
1627 | | * mapping of properties to their dependencies. |
1628 | | * @param currentScope URI for current resolution scope |
1629 | | * @param nodePath JSON Pointer representing path to current node |
1630 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
1631 | | * @param docCache Cache of resolved and fetched remote documents |
1632 | | * @param schemaRegistry Registry of populated schemas |
1633 | | * |
1634 | | * @return pointer to a new DependencyConstraint that belongs to the |
1635 | | * caller |
1636 | | */ |
1637 | | template<typename AdapterType> |
1638 | | constraints::DependenciesConstraint makeDependenciesConstraint( |
1639 | | Schema &rootSchema, |
1640 | | const AdapterType &rootNode, |
1641 | | const AdapterType &node, |
1642 | | const std::optional<std::string> currentScope, |
1643 | | const std::string &nodePath, |
1644 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1645 | | typename DocumentCache<AdapterType>::Type &docCache, |
1646 | | SchemaRegistry &schemaRegistry) |
1647 | 17.0k | { |
1648 | 17.0k | if (!node.maybeObject()) { |
1649 | 33 | throwRuntimeError("Expected valid subschema for 'dependencies' constraint."); |
1650 | 33 | } |
1651 | | |
1652 | 17.0k | constraints::DependenciesConstraint dependenciesConstraint; |
1653 | | |
1654 | | // Process each of the dependency mappings defined by the object |
1655 | 70.4k | for (const typename AdapterType::ObjectMember member : node.asObject()) { |
1656 | | |
1657 | | // First, we attempt to parse the value of the dependency mapping |
1658 | | // as an array of strings. If the Adapter type does not support |
1659 | | // strict types, then an empty string or empty object will be cast |
1660 | | // to an array, and the resulting dependency list will be empty. |
1661 | | // This is equivalent to using an empty object, but does mean that |
1662 | | // if the user provides an actual string then this error will not |
1663 | | // be detected. |
1664 | 70.4k | if (member.second.maybeArray()) { |
1665 | | // Parse an array of dependency names |
1666 | 39.0k | std::vector<std::string> dependentPropertyNames; |
1667 | 39.0k | for (const AdapterType dependencyName : member.second.asArray()) { |
1668 | 15.8k | if (dependencyName.maybeString()) { |
1669 | 15.8k | dependentPropertyNames.push_back(dependencyName.getString()); |
1670 | 15.8k | } else { |
1671 | 12 | throwRuntimeError("Expected string value in dependency list of property '" + |
1672 | 12 | member.first + "' in 'dependencies' constraint."); |
1673 | 12 | } |
1674 | 15.8k | } |
1675 | | |
1676 | 39.0k | dependenciesConstraint.addPropertyDependencies(member.first, |
1677 | 39.0k | dependentPropertyNames); |
1678 | | |
1679 | | // If the value of dependency mapping could not be processed as an |
1680 | | // array, we'll try to process it as an object instead. Note that |
1681 | | // strict type comparison is used here, since we've already |
1682 | | // exercised the flexibility by loosely-typed Adapter types. If the |
1683 | | // value of the dependency mapping is an object, then we'll try to |
1684 | | // process it as a dependent schema. |
1685 | 39.0k | } else if (member.second.isObject() || (m_version == kDraft7 && member.second.maybeBool())) { |
1686 | | // Parse dependent subschema |
1687 | 28.8k | const std::string childPath = nodePath + "/" + |
1688 | 28.8k | escapeJsonPointerToken(member.first); |
1689 | 28.8k | const Subschema *childSubschema = |
1690 | 28.8k | makeOrReuseSchema<AdapterType>(rootSchema, rootNode, |
1691 | 28.8k | member.second, currentScope, childPath, |
1692 | 28.8k | fetchDoc, nullptr, nullptr, docCache, |
1693 | 28.8k | schemaRegistry); |
1694 | 28.8k | dependenciesConstraint.addSchemaDependency(member.first, |
1695 | 28.8k | childSubschema); |
1696 | | |
1697 | | // If we're supposed to be parsing a Draft3 schema, then the value |
1698 | | // of the dependency mapping can also be a string containing the |
1699 | | // name of a single dependency. |
1700 | 28.8k | } else if (m_version == kDraft3 && member.second.isString()) { |
1701 | 0 | dependenciesConstraint.addPropertyDependency(member.first, |
1702 | 0 | member.second.getString()); |
1703 | | |
1704 | | // All other types result in an exception being thrown. |
1705 | 2.44k | } else { |
1706 | 2.44k | throwRuntimeError("Invalid dependencies definition."); |
1707 | 2.44k | } |
1708 | 70.4k | } |
1709 | | |
1710 | 17.0k | return dependenciesConstraint; |
1711 | 17.0k | } |
1712 | | |
1713 | | /** |
1714 | | * @brief Make a new EnumConstraint object. |
1715 | | * |
1716 | | * @param node JSON node containing an array of values permitted by the |
1717 | | * constraint. |
1718 | | * |
1719 | | * @return pointer to a new EnumConstraint that belongs to the caller |
1720 | | */ |
1721 | | template<typename AdapterType> |
1722 | | constraints::EnumConstraint makeEnumConstraint( |
1723 | | const AdapterType &node) |
1724 | 8.82k | { |
1725 | | // Make a copy of each value in the enum array |
1726 | 8.82k | constraints::EnumConstraint constraint; |
1727 | 105k | for (const AdapterType value : node.getArray()) { |
1728 | 105k | constraint.addValue(value); |
1729 | 105k | } |
1730 | | |
1731 | | /// @todo This will make another copy of the values while constructing |
1732 | | /// the EnumConstraint. Move semantics in C++11 should make it possible |
1733 | | /// to avoid these copies without complicating the implementation of the |
1734 | | /// EnumConstraint class. |
1735 | 8.82k | return constraint; |
1736 | 8.82k | } |
1737 | | |
1738 | | /** |
1739 | | * @brief Make a new FormatConstraint object |
1740 | | * |
1741 | | * @param node JSON node containing the configuration for this constraint |
1742 | | * |
1743 | | * @return pointer to a new FormatConstraint that belongs to the caller |
1744 | | */ |
1745 | | template<typename AdapterType> |
1746 | | constraints::FormatConstraint makeFormatConstraint( |
1747 | | const AdapterType &node) |
1748 | 6.04k | { |
1749 | 6.04k | if (node.isString()) { |
1750 | 5.98k | const std::string value = node.asString(); |
1751 | 5.98k | if (!value.empty()) { |
1752 | 5.98k | constraints::FormatConstraint constraint; |
1753 | 5.98k | constraint.setFormat(value); |
1754 | 5.98k | return constraint; |
1755 | 5.98k | } |
1756 | 5.98k | } |
1757 | | |
1758 | 57 | throwRuntimeError("Expected a string value for 'format' constraint."); |
1759 | 57 | } |
1760 | | |
1761 | | /** |
1762 | | * @brief Make a new ItemsConstraint object. |
1763 | | * |
1764 | | * @param rootSchema The Schema instance, and root subschema, |
1765 | | * through which other subschemas can be |
1766 | | * created and modified |
1767 | | * @param rootNode Reference to the node from which JSON |
1768 | | * References will be resolved when they refer |
1769 | | * to the current document; used for recursive |
1770 | | * parsing of schemas |
1771 | | * @param items Optional pointer to a JSON node containing |
1772 | | * an object mapping property names to |
1773 | | * schemas. |
1774 | | * @param additionalItems Optional pointer to a JSON node containing |
1775 | | * an additional properties schema or a |
1776 | | * boolean value. |
1777 | | * @param currentScope URI for current resolution scope |
1778 | | * @param itemsPath JSON Pointer representing the path to |
1779 | | * the 'items' node |
1780 | | * @param additionalItemsPath JSON Pointer representing the path to |
1781 | | * the 'additionalItems' node |
1782 | | * @param fetchDoc Function to fetch remote JSON documents |
1783 | | * (optional) |
1784 | | * @param docCache Cache of resolved and fetched remote |
1785 | | * documents |
1786 | | * @param schemaRegistry Registry of populated schemas |
1787 | | * |
1788 | | * @return pointer to a new ItemsConstraint that belongs to the caller |
1789 | | */ |
1790 | | template<typename AdapterType> |
1791 | | constraints::LinearItemsConstraint makeLinearItemsConstraint( |
1792 | | Schema &rootSchema, |
1793 | | const AdapterType &rootNode, |
1794 | | const AdapterType *items, |
1795 | | const AdapterType *additionalItems, |
1796 | | const std::optional<std::string> currentScope, |
1797 | | const std::string &itemsPath, |
1798 | | const std::string &additionalItemsPath, |
1799 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1800 | | typename DocumentCache<AdapterType>::Type &docCache, |
1801 | | SchemaRegistry &schemaRegistry) |
1802 | 27.6k | { |
1803 | 27.6k | constraints::LinearItemsConstraint constraint; |
1804 | | |
1805 | | // Construct a Schema object for the additionalItems constraint, |
1806 | | // if the additionalItems property is present |
1807 | 27.6k | if (additionalItems) { |
1808 | 1.95k | if (additionalItems->maybeBool()) { |
1809 | | // If the value of the additionalItems property is a boolean |
1810 | | // and is set to true, then additional array items do not need |
1811 | | // to satisfy any constraints. |
1812 | 41 | if (additionalItems->asBool()) { |
1813 | 5 | constraint.setAdditionalItemsSubschema(rootSchema.emptySubschema()); |
1814 | 5 | } |
1815 | 1.91k | } else if (additionalItems->maybeObject()) { |
1816 | | // If the value of the additionalItems property is an object, |
1817 | | // then it should be parsed into a Schema object, which will be |
1818 | | // used to validate additional array items. |
1819 | 1.90k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1820 | 1.90k | rootSchema, rootNode, *additionalItems, currentScope, |
1821 | 1.90k | additionalItemsPath, fetchDoc, nullptr, nullptr, docCache, |
1822 | 1.90k | schemaRegistry); |
1823 | 1.90k | constraint.setAdditionalItemsSubschema(subschema); |
1824 | 1.90k | } else { |
1825 | | // Any other format for the additionalItems property will result |
1826 | | // in an exception being thrown. |
1827 | 10 | throwRuntimeError("Expected bool or object value for 'additionalItems'"); |
1828 | 10 | } |
1829 | 25.6k | } else { |
1830 | | // The default value for the additionalItems property is an empty |
1831 | | // object, which means that additional array items do not need to |
1832 | | // satisfy any constraints. |
1833 | 25.6k | constraint.setAdditionalItemsSubschema(rootSchema.emptySubschema()); |
1834 | 25.6k | } |
1835 | | |
1836 | | // Construct a Schema object for each item in the items array. |
1837 | | // If the items constraint is not provided, then array items |
1838 | | // will be validated against the additionalItems schema. |
1839 | 27.6k | if (items) { |
1840 | 27.2k | if (items->isArray()) { |
1841 | | // If the items constraint contains an array, then it should |
1842 | | // contain a list of child schemas which will be used to |
1843 | | // validate the values at the corresponding indexes in a target |
1844 | | // array. |
1845 | 27.2k | int index = 0; |
1846 | 250k | for (const AdapterType v : items->getArray()) { |
1847 | 250k | const std::string childPath = itemsPath + "/" + |
1848 | 250k | std::to_string(index); |
1849 | 250k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1850 | 250k | rootSchema, rootNode, v, currentScope, childPath, |
1851 | 250k | fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
1852 | 250k | constraint.addItemSubschema(subschema); |
1853 | 250k | index++; |
1854 | 250k | } |
1855 | 27.2k | } else { |
1856 | 0 | throwRuntimeError("Expected array value for non-singular 'items' constraint."); |
1857 | 0 | } |
1858 | 27.2k | } |
1859 | | |
1860 | 27.6k | return constraint; |
1861 | 27.6k | } |
1862 | | |
1863 | | /** |
1864 | | * @brief Make a new ItemsConstraint object. |
1865 | | * |
1866 | | * @param rootSchema The Schema instance, and root subschema, |
1867 | | * through which other subschemas can be |
1868 | | * created and modified |
1869 | | * @param rootNode Reference to the node from which JSON |
1870 | | * References will be resolved when they refer |
1871 | | * to the current document; used for recursive |
1872 | | * parsing of schemas |
1873 | | * @param items Optional pointer to a JSON node containing |
1874 | | * an object mapping property names to |
1875 | | * schemas. |
1876 | | * @param currentScope URI for current resolution scope |
1877 | | * @param itemsPath JSON Pointer representing the path to |
1878 | | * the 'items' node |
1879 | | * @param fetchDoc Function to fetch remote JSON documents |
1880 | | * (optional) |
1881 | | * @param docCache Cache of resolved and fetched remote |
1882 | | * documents |
1883 | | * @param schemaRegistry Registry of populated schemas |
1884 | | * |
1885 | | * @return pointer to a new ItemsConstraint that belongs to the caller |
1886 | | */ |
1887 | | template<typename AdapterType> |
1888 | | constraints::SingularItemsConstraint makeSingularItemsConstraint( |
1889 | | Schema &rootSchema, |
1890 | | const AdapterType &rootNode, |
1891 | | const AdapterType &items, |
1892 | | const std::optional<std::string> currentScope, |
1893 | | const std::string &itemsPath, |
1894 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
1895 | | typename DocumentCache<AdapterType>::Type &docCache, |
1896 | | SchemaRegistry &schemaRegistry) |
1897 | 15.9k | { |
1898 | 15.9k | constraints::SingularItemsConstraint constraint; |
1899 | | |
1900 | | // Construct a Schema object for each item in the items array, if an |
1901 | | // array is provided, or a single Schema object, in an object value is |
1902 | | // provided. If the items constraint is not provided, then array items |
1903 | | // will be validated against the additionalItems schema. |
1904 | 15.9k | if (items.isObject() || (m_version == kDraft7 && items.maybeBool())) { |
1905 | | // If the items constraint contains an object value, then it |
1906 | | // should contain a Schema that will be used to validate all |
1907 | | // items in a target array. Any schema defined by the |
1908 | | // additionalItems constraint will be ignored. |
1909 | 15.1k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
1910 | 15.1k | rootSchema, rootNode, items, currentScope, itemsPath, |
1911 | 15.1k | fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
1912 | 15.1k | constraint.setItemsSubschema(subschema); |
1913 | | |
1914 | 15.1k | } else if (items.maybeObject()) { |
1915 | | // If a loosely-typed Adapter type is being used, then we'll |
1916 | | // assume that an empty schema has been provided. |
1917 | 0 | constraint.setItemsSubschema(rootSchema.emptySubschema()); |
1918 | |
|
1919 | 823 | } else { |
1920 | | // All other formats will result in an exception being thrown. |
1921 | 823 | throwRuntimeError("Expected valid schema for singular 'items' constraint."); |
1922 | 823 | } |
1923 | | |
1924 | 15.9k | return constraint; |
1925 | 15.9k | } |
1926 | | |
1927 | | /** |
1928 | | * @brief Make a new MaximumConstraint object (draft 3 and 4). |
1929 | | * |
1930 | | * @param node JSON node containing the maximum value. |
1931 | | * @param exclusiveMaximum Optional pointer to a JSON boolean value that |
1932 | | * indicates whether maximum value is excluded |
1933 | | * from the range of permitted values. |
1934 | | * |
1935 | | * @return pointer to a new MaximumConstraint that belongs to the caller |
1936 | | */ |
1937 | | template<typename AdapterType> |
1938 | | constraints::MaximumConstraint makeMaximumConstraint( |
1939 | | const AdapterType &node, |
1940 | | const AdapterType *exclusiveMaximum) |
1941 | 2.84k | { |
1942 | 2.84k | if (!node.maybeDouble()) { |
1943 | 456 | throwRuntimeError("Expected numeric value for maximum constraint."); |
1944 | 456 | } |
1945 | | |
1946 | 2.84k | constraints::MaximumConstraint constraint; |
1947 | 2.84k | constraint.setMaximum(node.asDouble()); |
1948 | | |
1949 | 2.84k | if (exclusiveMaximum) { |
1950 | 154 | if (!exclusiveMaximum->maybeBool()) { |
1951 | 71 | throwRuntimeError("Expected boolean value for exclusiveMaximum constraint."); |
1952 | 71 | } |
1953 | | |
1954 | 154 | constraint.setExclusiveMaximum(exclusiveMaximum->asBool()); |
1955 | 154 | } |
1956 | | |
1957 | 2.84k | return constraint; |
1958 | 2.84k | } |
1959 | | |
1960 | | /** |
1961 | | * @brief Make a new MaximumConstraint object that is always exclusive (draft 7). |
1962 | | * |
1963 | | * @param node JSON node containing an integer, representing the maximum value. |
1964 | | * |
1965 | | * @return pointer to a new Maximum that belongs to the caller |
1966 | | */ |
1967 | | template<typename AdapterType> |
1968 | | constraints::MaximumConstraint makeMaximumConstraintExclusive(const AdapterType &node) |
1969 | 776 | { |
1970 | 776 | if (!node.maybeDouble()) { |
1971 | 198 | throwRuntimeError("Expected numeric value for exclusiveMaximum constraint."); |
1972 | 198 | } |
1973 | | |
1974 | 776 | constraints::MaximumConstraint constraint; |
1975 | 776 | constraint.setMaximum(node.asDouble()); |
1976 | 776 | constraint.setExclusiveMaximum(true); |
1977 | 776 | return constraint; |
1978 | 776 | } |
1979 | | |
1980 | | /** |
1981 | | * @brief Make a new MaxItemsConstraint object. |
1982 | | * |
1983 | | * @param node JSON node containing an integer value representing the |
1984 | | * maximum number of items that may be contained by an array. |
1985 | | * |
1986 | | * @return pointer to a new MaxItemsConstraint that belongs to the caller. |
1987 | | */ |
1988 | | template<typename AdapterType> |
1989 | | constraints::MaxItemsConstraint makeMaxItemsConstraint( |
1990 | | const AdapterType &node) |
1991 | 3.00k | { |
1992 | 3.00k | if (node.maybeInteger()) { |
1993 | 2.90k | const int64_t value = node.asInteger(); |
1994 | 2.90k | if (value >= 0) { |
1995 | 2.76k | constraints::MaxItemsConstraint constraint; |
1996 | 2.76k | constraint.setMaxItems(value); |
1997 | 2.76k | return constraint; |
1998 | 2.76k | } |
1999 | 2.90k | } |
2000 | | |
2001 | 231 | throwRuntimeError("Expected non-negative integer value for 'maxItems' constraint."); |
2002 | 231 | } |
2003 | | |
2004 | | /** |
2005 | | * @brief Make a new MaxLengthConstraint object. |
2006 | | * |
2007 | | * @param node JSON node containing an integer value representing the |
2008 | | * maximum length of a string. |
2009 | | * |
2010 | | * @return pointer to a new MaxLengthConstraint that belongs to the caller |
2011 | | */ |
2012 | | template<typename AdapterType> |
2013 | | constraints::MaxLengthConstraint makeMaxLengthConstraint( |
2014 | | const AdapterType &node) |
2015 | 3.66k | { |
2016 | 3.66k | if (node.maybeInteger()) { |
2017 | 3.24k | const int64_t value = node.asInteger(); |
2018 | 3.24k | if (value >= 0) { |
2019 | 3.11k | constraints::MaxLengthConstraint constraint; |
2020 | 3.11k | constraint.setMaxLength(value); |
2021 | 3.11k | return constraint; |
2022 | 3.11k | } |
2023 | 3.24k | } |
2024 | | |
2025 | 555 | throwRuntimeError("Expected a non-negative integer value for 'maxLength' constraint."); |
2026 | 555 | } |
2027 | | |
2028 | | /** |
2029 | | * @brief Make a new MaxPropertiesConstraint object. |
2030 | | * |
2031 | | * @param node JSON node containing an integer value representing the |
2032 | | * maximum number of properties that may be contained by an |
2033 | | * object. |
2034 | | * |
2035 | | * @return pointer to a new MaxPropertiesConstraint that belongs to the |
2036 | | * caller |
2037 | | */ |
2038 | | template<typename AdapterType> |
2039 | | constraints::MaxPropertiesConstraint makeMaxPropertiesConstraint( |
2040 | | const AdapterType &node) |
2041 | 2.32k | { |
2042 | 2.32k | if (node.maybeInteger()) { |
2043 | 2.18k | int64_t value = node.asInteger(); |
2044 | 2.18k | if (value >= 0) { |
2045 | 2.07k | constraints::MaxPropertiesConstraint constraint; |
2046 | 2.07k | constraint.setMaxProperties(value); |
2047 | 2.07k | return constraint; |
2048 | 2.07k | } |
2049 | 2.18k | } |
2050 | | |
2051 | 248 | throwRuntimeError("Expected a non-negative integer for 'maxProperties' constraint."); |
2052 | 248 | } |
2053 | | |
2054 | | /** |
2055 | | * @brief Make a new MinimumConstraint object (draft 3 and 4). |
2056 | | * |
2057 | | * @param node JSON node containing an integer, representing |
2058 | | * the minimum value. |
2059 | | * |
2060 | | * @param exclusiveMinimum Optional pointer to a JSON boolean value that |
2061 | | * indicates whether the minimum value is |
2062 | | * excluded from the range of permitted values. |
2063 | | * |
2064 | | * @return pointer to a new MinimumConstraint that belongs to the caller |
2065 | | */ |
2066 | | template<typename AdapterType> |
2067 | | constraints::MinimumConstraint makeMinimumConstraint( |
2068 | | const AdapterType &node, |
2069 | | const AdapterType *exclusiveMinimum) |
2070 | 6.52k | { |
2071 | 6.52k | if (!node.maybeDouble()) { |
2072 | 136 | throwRuntimeError("Expected numeric value for minimum constraint."); |
2073 | 136 | } |
2074 | | |
2075 | 6.52k | constraints::MinimumConstraint constraint; |
2076 | 6.52k | constraint.setMinimum(node.asDouble()); |
2077 | | |
2078 | 6.52k | if (exclusiveMinimum) { |
2079 | 12 | if (!exclusiveMinimum->maybeBool()) { |
2080 | 1 | throwRuntimeError("Expected boolean value for 'exclusiveMinimum' constraint."); |
2081 | 1 | } |
2082 | | |
2083 | 12 | constraint.setExclusiveMinimum(exclusiveMinimum->asBool()); |
2084 | 12 | } |
2085 | | |
2086 | 6.52k | return constraint; |
2087 | 6.52k | } |
2088 | | |
2089 | | /** |
2090 | | * @brief Make a new MinimumConstraint object that is always exclusive (draft 7). |
2091 | | * |
2092 | | * @param node JSON node containing an integer, representing the minimum value. |
2093 | | * |
2094 | | * @return pointer to a new MinimumConstraint that belongs to the caller |
2095 | | */ |
2096 | | template<typename AdapterType> |
2097 | | constraints::MinimumConstraint makeMinimumConstraintExclusive(const AdapterType &node) |
2098 | 1.02k | { |
2099 | 1.02k | if (!node.maybeDouble()) { |
2100 | 72 | throwRuntimeError("Expected numeric value for exclusiveMinimum constraint."); |
2101 | 72 | } |
2102 | | |
2103 | 1.02k | constraints::MinimumConstraint constraint; |
2104 | 1.02k | constraint.setMinimum(node.asDouble()); |
2105 | 1.02k | constraint.setExclusiveMinimum(true); |
2106 | 1.02k | return constraint; |
2107 | 1.02k | } |
2108 | | |
2109 | | /** |
2110 | | * @brief Make a new MinItemsConstraint object. |
2111 | | * |
2112 | | * @param node JSON node containing an integer value representing the |
2113 | | * minimum number of items that may be contained by an array. |
2114 | | * |
2115 | | * @return pointer to a new MinItemsConstraint that belongs to the caller |
2116 | | */ |
2117 | | template<typename AdapterType> |
2118 | | constraints::MinItemsConstraint makeMinItemsConstraint(const AdapterType &node) |
2119 | 3.46k | { |
2120 | 3.46k | if (node.maybeInteger()) { |
2121 | 3.24k | const int64_t value = node.asInteger(); |
2122 | 3.24k | if (value >= 0) { |
2123 | 3.15k | constraints::MinItemsConstraint constraint; |
2124 | 3.15k | constraint.setMinItems(value); |
2125 | 3.15k | return constraint; |
2126 | 3.15k | } |
2127 | 3.24k | } |
2128 | | |
2129 | 310 | throwRuntimeError("Expected a non-negative integer value for 'minItems' constraint."); |
2130 | 310 | } |
2131 | | |
2132 | | /** |
2133 | | * @brief Make a new MinLengthConstraint object. |
2134 | | * |
2135 | | * @param node JSON node containing an integer value representing the |
2136 | | * minimum length of a string. |
2137 | | * |
2138 | | * @return pointer to a new MinLengthConstraint that belongs to the caller |
2139 | | */ |
2140 | | template<typename AdapterType> |
2141 | | constraints::MinLengthConstraint makeMinLengthConstraint(const AdapterType &node) |
2142 | 4.80k | { |
2143 | 4.80k | if (node.maybeInteger()) { |
2144 | 4.44k | const int64_t value = node.asInteger(); |
2145 | 4.44k | if (value >= 0) { |
2146 | 4.24k | constraints::MinLengthConstraint constraint; |
2147 | 4.24k | constraint.setMinLength(value); |
2148 | 4.24k | return constraint; |
2149 | 4.24k | } |
2150 | 4.44k | } |
2151 | | |
2152 | 560 | throwRuntimeError("Expected a non-negative integer value for 'minLength' constraint."); |
2153 | 560 | } |
2154 | | |
2155 | | |
2156 | | /** |
2157 | | * @brief Make a new MaxPropertiesConstraint object. |
2158 | | * |
2159 | | * @param node JSON node containing an integer value representing the |
2160 | | * minimum number of properties that may be contained by an |
2161 | | * object. |
2162 | | * |
2163 | | * @return pointer to a new MinPropertiesConstraint that belongs to the |
2164 | | * caller |
2165 | | */ |
2166 | | template<typename AdapterType> |
2167 | | constraints::MinPropertiesConstraint makeMinPropertiesConstraint(const AdapterType &node) |
2168 | 2.75k | { |
2169 | 2.75k | if (node.maybeInteger()) { |
2170 | 2.68k | int64_t value = node.asInteger(); |
2171 | 2.68k | if (value >= 0) { |
2172 | 2.58k | constraints::MinPropertiesConstraint constraint; |
2173 | 2.58k | constraint.setMinProperties(value); |
2174 | 2.58k | return constraint; |
2175 | 2.58k | } |
2176 | 2.68k | } |
2177 | | |
2178 | 167 | throwRuntimeError("Expected a non-negative integer for 'minProperties' constraint."); |
2179 | 167 | } |
2180 | | |
2181 | | /** |
2182 | | * @brief Make a new MultipleOfDoubleConstraint object |
2183 | | * |
2184 | | * @param node JSON node containing an numeric value that a target value |
2185 | | * must divide by in order to satisfy this constraint |
2186 | | * |
2187 | | * @return a MultipleOfConstraint |
2188 | | */ |
2189 | | template<typename AdapterType> |
2190 | | constraints::MultipleOfDoubleConstraint makeMultipleOfDoubleConstraint(const AdapterType &node) |
2191 | 1.92k | { |
2192 | 1.92k | constraints::MultipleOfDoubleConstraint constraint; |
2193 | 1.92k | constraint.setDivisor(node.asDouble()); |
2194 | 1.92k | return constraint; |
2195 | 1.92k | } |
2196 | | |
2197 | | /** |
2198 | | * @brief Make a new MultipleOfIntConstraint object |
2199 | | * |
2200 | | * @param node JSON node containing a numeric value that a target value |
2201 | | * must divide by in order to satisfy this constraint |
2202 | | * |
2203 | | * @return a MultipleOfIntConstraint |
2204 | | */ |
2205 | | template<typename AdapterType> |
2206 | | constraints::MultipleOfIntConstraint makeMultipleOfIntConstraint(const AdapterType &node) |
2207 | 6.16k | { |
2208 | 6.16k | constraints::MultipleOfIntConstraint constraint; |
2209 | 6.16k | constraint.setDivisor(node.asInteger()); |
2210 | 6.16k | return constraint; |
2211 | 6.16k | } |
2212 | | |
2213 | | /** |
2214 | | * @brief Make a new NotConstraint object |
2215 | | * |
2216 | | * @param rootSchema The Schema instance, and root subschema, through |
2217 | | * which other subschemas can be created and modified |
2218 | | * @param rootNode Reference to the node from which JSON References |
2219 | | * will be resolved when they refer to the current |
2220 | | * document; used for recursive parsing of schemas |
2221 | | * @param node JSON node containing a schema |
2222 | | * @param currentScope URI for current resolution scope |
2223 | | * @param nodePath JSON Pointer representing path to current node |
2224 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
2225 | | * @param docCache Cache of resolved and fetched remote documents |
2226 | | * @param schemaRegistry Registry of populated schemas |
2227 | | * |
2228 | | * @return pointer to a new NotConstraint object that belongs to the caller |
2229 | | */ |
2230 | | template<typename AdapterType> |
2231 | | constraints::NotConstraint makeNotConstraint( |
2232 | | Schema &rootSchema, |
2233 | | const AdapterType &rootNode, |
2234 | | const AdapterType &node, |
2235 | | const std::optional<std::string> currentScope, |
2236 | | const std::string &nodePath, |
2237 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
2238 | | typename DocumentCache<AdapterType>::Type &docCache, |
2239 | | SchemaRegistry &schemaRegistry) |
2240 | 13.3k | { |
2241 | 13.3k | if (node.maybeObject() || (m_version == kDraft7 && node.maybeBool())) { |
2242 | 12.6k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
2243 | 12.6k | rootSchema, rootNode, node, currentScope, nodePath, |
2244 | 12.6k | fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
2245 | 12.6k | constraints::NotConstraint constraint; |
2246 | 12.6k | constraint.setSubschema(subschema); |
2247 | 12.6k | return constraint; |
2248 | 12.6k | } |
2249 | | |
2250 | 723 | throwRuntimeError("Expected object value for 'not' constraint."); |
2251 | 723 | } |
2252 | | |
2253 | | /** |
2254 | | * @brief Make a new OneOfConstraint object |
2255 | | * |
2256 | | * @param rootSchema The Schema instance, and root subschema, through |
2257 | | * which other subschemas can be created and modified |
2258 | | * @param rootNode Reference to the node from which JSON References |
2259 | | * will be resolved when they refer to the current |
2260 | | * document; used for recursive parsing of schemas |
2261 | | * @param node JSON node containing an array of child schemas |
2262 | | * @param currentScope URI for current resolution scope |
2263 | | * @param nodePath JSON Pointer representing path to current node |
2264 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
2265 | | * @param docCache Cache of resolved and fetched remote documents |
2266 | | * @param schemaRegistry Registry of populated schemas |
2267 | | * |
2268 | | * @return pointer to a new OneOfConstraint that belongs to the caller |
2269 | | */ |
2270 | | template<typename AdapterType> |
2271 | | constraints::OneOfConstraint makeOneOfConstraint( |
2272 | | Schema &rootSchema, |
2273 | | const AdapterType &rootNode, |
2274 | | const AdapterType &node, |
2275 | | const std::optional<std::string> currentScope, |
2276 | | const std::string &nodePath, |
2277 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
2278 | | typename DocumentCache<AdapterType>::Type &docCache, |
2279 | | SchemaRegistry &schemaRegistry) |
2280 | 14.3k | { |
2281 | 14.3k | constraints::OneOfConstraint constraint; |
2282 | | |
2283 | 14.3k | int index = 0; |
2284 | 64.1k | for (const AdapterType schemaNode : node.getArray()) { |
2285 | 64.1k | const std::string childPath = nodePath + "/" + std::to_string(index); |
2286 | 64.1k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
2287 | 64.1k | rootSchema, rootNode, schemaNode, currentScope, childPath, |
2288 | 64.1k | fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
2289 | 64.1k | constraint.addSubschema(subschema); |
2290 | 64.1k | index++; |
2291 | 64.1k | } |
2292 | | |
2293 | 14.3k | return constraint; |
2294 | 14.3k | } |
2295 | | |
2296 | | /** |
2297 | | * @brief Make a new PatternConstraint object. |
2298 | | * |
2299 | | * @param node JSON node containing a pattern string |
2300 | | * |
2301 | | * @return pointer to a new PatternConstraint object that belongs to the |
2302 | | * caller |
2303 | | */ |
2304 | | template<typename AdapterType> |
2305 | | constraints::PatternConstraint makePatternConstraint( |
2306 | | const AdapterType &node) |
2307 | 18.6k | { |
2308 | 18.6k | constraints::PatternConstraint constraint; |
2309 | 18.6k | constraint.setPattern(node.getString()); |
2310 | 18.6k | return constraint; |
2311 | 18.6k | } |
2312 | | |
2313 | | /** |
2314 | | * @brief Make a new Properties object. |
2315 | | * |
2316 | | * @param rootSchema The Schema instance, and root |
2317 | | * subschema, through which other |
2318 | | * subschemas can be created and modified |
2319 | | * @param rootNode Reference to the node from which JSON |
2320 | | * References will be resolved when they |
2321 | | * refer to the current document; used |
2322 | | * for recursive parsing of schemas |
2323 | | * @param properties Optional pointer to a JSON node |
2324 | | * containing an object mapping property |
2325 | | * names to schemas. |
2326 | | * @param patternProperties Optional pointer to a JSON node |
2327 | | * containing an object mapping pattern |
2328 | | * property names to schemas. |
2329 | | * @param additionalProperties Optional pointer to a JSON node |
2330 | | * containing an additional properties |
2331 | | * schema or a boolean value. |
2332 | | * @param currentScope URI for current resolution scope |
2333 | | * @param propertiesPath JSON Pointer representing the path to |
2334 | | * the 'properties' node |
2335 | | * @param patternPropertiesPath JSON Pointer representing the path to |
2336 | | * the 'patternProperties' node |
2337 | | * @param additionalPropertiesPath JSON Pointer representing the path to |
2338 | | * the 'additionalProperties' node |
2339 | | * @param fetchDoc Function to fetch remote JSON |
2340 | | * documents (optional) |
2341 | | * @param parentSubschema Optional pointer to the Schema of the |
2342 | | * parent object, needed to support the |
2343 | | * 'required' keyword in Draft 3 |
2344 | | * @param docCache Cache of resolved and fetched remote |
2345 | | * documents |
2346 | | * @param schemaRegistry Registry of populated schemas |
2347 | | * |
2348 | | * @return pointer to a new Properties that belongs to the caller |
2349 | | */ |
2350 | | template<typename AdapterType> |
2351 | | constraints::PropertiesConstraint makePropertiesConstraint( |
2352 | | Schema &rootSchema, |
2353 | | const AdapterType &rootNode, |
2354 | | const AdapterType *properties, |
2355 | | const AdapterType *patternProperties, |
2356 | | const AdapterType *additionalProperties, |
2357 | | const std::optional<std::string> currentScope, |
2358 | | const std::string &propertiesPath, |
2359 | | const std::string &patternPropertiesPath, |
2360 | | const std::string &additionalPropertiesPath, |
2361 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
2362 | | const Subschema *parentSubschema, |
2363 | | typename DocumentCache<AdapterType>::Type &docCache, |
2364 | | SchemaRegistry &schemaRegistry) |
2365 | 33.7k | { |
2366 | 33.7k | typedef typename AdapterType::ObjectMember Member; |
2367 | | |
2368 | 33.7k | constraints::PropertiesConstraint constraint; |
2369 | | |
2370 | | // Create subschemas for 'properties' constraint |
2371 | 33.7k | if (properties) { |
2372 | 41.6k | for (const Member m : properties->getObject()) { |
2373 | 41.6k | const std::string &property = m.first; |
2374 | 41.6k | const std::string childPath = propertiesPath + "/" + property; |
2375 | 41.6k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
2376 | 41.6k | rootSchema, rootNode, m.second, currentScope, childPath, |
2377 | 41.6k | fetchDoc, parentSubschema, &property, docCache, |
2378 | 41.6k | schemaRegistry); |
2379 | 41.6k | constraint.addPropertySubschema(property, subschema); |
2380 | 41.6k | } |
2381 | 10.9k | } |
2382 | | |
2383 | | // Create subschemas for 'patternProperties' constraint |
2384 | 33.7k | if (patternProperties) { |
2385 | 176k | for (const Member m : patternProperties->getObject()) { |
2386 | 176k | const std::string &pattern = m.first; |
2387 | 176k | const std::string childPath = patternPropertiesPath + "/" + pattern; |
2388 | 176k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
2389 | 176k | rootSchema, rootNode, m.second, currentScope, childPath, |
2390 | 176k | fetchDoc, parentSubschema, &pattern, docCache, |
2391 | 176k | schemaRegistry); |
2392 | 176k | constraint.addPatternPropertySubschema(pattern, subschema); |
2393 | 176k | } |
2394 | 18.9k | } |
2395 | | |
2396 | | // Create an additionalItems subschema if required |
2397 | 33.7k | if (additionalProperties) { |
2398 | | // If additionalProperties has been set, check for a boolean value. |
2399 | | // Setting 'additionalProperties' to true allows the values of |
2400 | | // additional properties to take any form. Setting it false |
2401 | | // prohibits the use of additional properties. |
2402 | | // If additionalProperties is instead an object, it should be |
2403 | | // parsed as a schema. If additionalProperties has any other type, |
2404 | | // then the schema is not valid. |
2405 | 4.22k | if (additionalProperties->isBool() || |
2406 | 3.70k | additionalProperties->maybeBool()) { |
2407 | | // If it has a boolean value that is 'true', then an empty |
2408 | | // schema should be used. |
2409 | 830 | if (additionalProperties->asBool()) { |
2410 | 681 | constraint.setAdditionalPropertiesSubschema(rootSchema.emptySubschema()); |
2411 | 681 | } |
2412 | 3.39k | } else if (additionalProperties->isObject()) { |
2413 | | // If additionalProperties is an object, it should be used as |
2414 | | // a child schema. |
2415 | 3.32k | const Subschema *subschema = makeOrReuseSchema<AdapterType>( |
2416 | 3.32k | rootSchema, rootNode, *additionalProperties, |
2417 | 3.32k | currentScope, additionalPropertiesPath, fetchDoc, nullptr, |
2418 | 3.32k | nullptr, docCache, schemaRegistry); |
2419 | 3.32k | constraint.setAdditionalPropertiesSubschema(subschema); |
2420 | 3.32k | } else { |
2421 | | // All other types are invalid |
2422 | 74 | throwRuntimeError("Invalid type for 'additionalProperties' constraint."); |
2423 | 74 | } |
2424 | 29.5k | } else { |
2425 | | // If an additionalProperties constraint is not provided, then the |
2426 | | // default value is an empty schema. |
2427 | 29.5k | constraint.setAdditionalPropertiesSubschema(rootSchema.emptySubschema()); |
2428 | 29.5k | } |
2429 | | |
2430 | 33.7k | return constraint; |
2431 | 33.7k | } |
2432 | | |
2433 | | template<typename AdapterType> |
2434 | | constraints::PropertyNamesConstraint makePropertyNamesConstraint( |
2435 | | Schema &rootSchema, |
2436 | | const AdapterType &rootNode, |
2437 | | const AdapterType ¤tNode, |
2438 | | const std::optional<std::string> currentScope, |
2439 | | const std::string &nodePath, |
2440 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
2441 | | typename DocumentCache<AdapterType>::Type &docCache, |
2442 | | SchemaRegistry &schemaRegistry) |
2443 | 3.70k | { |
2444 | 3.70k | const Subschema *subschema = makeOrReuseSchema<AdapterType>(rootSchema, rootNode, currentNode, currentScope, |
2445 | 3.70k | nodePath, fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
2446 | 3.70k | constraints::PropertyNamesConstraint constraint; |
2447 | 3.70k | constraint.setSubschema(subschema); |
2448 | 3.70k | return constraint; |
2449 | 3.70k | } |
2450 | | |
2451 | | /** |
2452 | | * @brief Make a new RequiredConstraint. |
2453 | | * |
2454 | | * This function is used to create new RequiredContraint objects for |
2455 | | * Draft 3 schemas. |
2456 | | * |
2457 | | * @param node Node containing a boolean value. |
2458 | | * @param name Name of the required attribute. |
2459 | | * |
2460 | | * @return pointer to a new RequiredConstraint object that belongs to the |
2461 | | * caller |
2462 | | */ |
2463 | | template<typename AdapterType> |
2464 | | std::optional<constraints::RequiredConstraint> |
2465 | | makeRequiredConstraintForSelf(const AdapterType &node, |
2466 | | const std::string &name) |
2467 | 0 | { |
2468 | 0 | if (!node.maybeBool()) { |
2469 | 0 | throwRuntimeError("Expected boolean value for 'required' attribute."); |
2470 | 0 | } |
2471 | |
|
2472 | 0 | if (node.asBool()) { |
2473 | 0 | constraints::RequiredConstraint constraint; |
2474 | 0 | constraint.addRequiredProperty(name); |
2475 | 0 | return constraint; |
2476 | 0 | } |
2477 | | |
2478 | 0 | return std::optional<constraints::RequiredConstraint>(); |
2479 | 0 | } |
2480 | | |
2481 | | /** |
2482 | | * @brief Make a new RequiredConstraint. |
2483 | | * |
2484 | | * This function is used to create new RequiredContraint objects for |
2485 | | * Draft 4 schemas. |
2486 | | * |
2487 | | * @param node Node containing an array of strings. |
2488 | | * |
2489 | | * @return pointer to a new RequiredConstraint object that belongs to the |
2490 | | * caller |
2491 | | */ |
2492 | | template<typename AdapterType> |
2493 | | constraints::RequiredConstraint makeRequiredConstraint( |
2494 | | const AdapterType &node) |
2495 | 9.24k | { |
2496 | 9.24k | constraints::RequiredConstraint constraint; |
2497 | | |
2498 | 30.9k | for (const AdapterType v : node.getArray()) { |
2499 | 30.9k | if (!v.maybeString()) { |
2500 | 68 | throwRuntimeError("Expected required property name to be a string value"); |
2501 | 68 | } |
2502 | | |
2503 | 30.9k | constraint.addRequiredProperty(v.getString()); |
2504 | 30.9k | } |
2505 | | |
2506 | 9.24k | return constraint; |
2507 | 9.24k | } |
2508 | | |
2509 | | /** |
2510 | | * @brief Make a new TypeConstraint object |
2511 | | * |
2512 | | * @param rootSchema The Schema instance, and root subschema, through |
2513 | | * which other subschemas can be created and modified |
2514 | | * @param rootNode Reference to the node from which JSON References |
2515 | | * will be resolved when they refer to the current |
2516 | | * document; used for recursive parsing of schemas |
2517 | | * @param node Node containing the name of a JSON type |
2518 | | * @param currentScope URI for current resolution scope |
2519 | | * @param nodePath JSON Pointer representing path to current node |
2520 | | * @param fetchDoc Function to fetch remote JSON documents (optional) |
2521 | | * @param docCache Cache of resolved and fetched remote documents |
2522 | | * @param schemaRegistry Registry of populated schemas |
2523 | | * |
2524 | | * @return pointer to a new TypeConstraint object. |
2525 | | */ |
2526 | | template<typename AdapterType> |
2527 | | constraints::TypeConstraint makeTypeConstraint( |
2528 | | Schema &rootSchema, |
2529 | | const AdapterType &rootNode, |
2530 | | const AdapterType &node, |
2531 | | const std::optional<std::string> currentScope, |
2532 | | const std::string &nodePath, |
2533 | | const typename FunctionPtrs<AdapterType>::FetchDoc fetchDoc, |
2534 | | typename DocumentCache<AdapterType>::Type &docCache, |
2535 | | SchemaRegistry &schemaRegistry) |
2536 | 27.3k | { |
2537 | 27.3k | typedef constraints::TypeConstraint TypeConstraint; |
2538 | | |
2539 | 27.3k | TypeConstraint constraint; |
2540 | | |
2541 | 27.3k | if (node.maybeString()) { |
2542 | 25.2k | const TypeConstraint::JsonType type = TypeConstraint::jsonTypeFromString(node.getString()); |
2543 | 25.2k | if (type == TypeConstraint::kAny && m_version == kDraft4) { |
2544 | 877 | throwRuntimeError("'any' type is not supported in version 4 schemas."); |
2545 | 877 | } |
2546 | | |
2547 | 25.2k | constraint.addNamedType(type); |
2548 | | |
2549 | 25.2k | } else if (node.maybeArray()) { |
2550 | 2.03k | int index = 0; |
2551 | 2.69k | for (const AdapterType v : node.getArray()) { |
2552 | 2.69k | if (v.maybeString()) { |
2553 | 2.53k | const TypeConstraint::JsonType type = TypeConstraint::jsonTypeFromString(v.getString()); |
2554 | 2.53k | if (type == TypeConstraint::kAny && m_version == kDraft4) { |
2555 | 262 | throwRuntimeError("'any' type is not supported in version 4 schemas."); |
2556 | 262 | } |
2557 | | |
2558 | 2.53k | constraint.addNamedType(type); |
2559 | | |
2560 | 2.53k | } else if (v.maybeObject() && m_version == kDraft3) { |
2561 | 0 | const std::string childPath = nodePath + "/" + std::to_string(index); |
2562 | 0 | const Subschema *subschema = makeOrReuseSchema<AdapterType>(rootSchema, rootNode, v, currentScope, |
2563 | 0 | childPath, fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
2564 | 0 | constraint.addSchemaType(subschema); |
2565 | |
|
2566 | 154 | } else { |
2567 | 154 | throwRuntimeError("Type name should be a string."); |
2568 | 154 | } |
2569 | | |
2570 | 2.69k | index++; |
2571 | 2.69k | } |
2572 | | |
2573 | 2.03k | } else if (node.maybeObject() && m_version == kDraft3) { |
2574 | 0 | const Subschema *subschema = makeOrReuseSchema<AdapterType>(rootSchema, rootNode, node, currentScope, |
2575 | 0 | nodePath, fetchDoc, nullptr, nullptr, docCache, schemaRegistry); |
2576 | 0 | constraint.addSchemaType(subschema); |
2577 | |
|
2578 | 86 | } else { |
2579 | 86 | throwRuntimeError("Type name should be a string."); |
2580 | 86 | } |
2581 | | |
2582 | 27.3k | return constraint; |
2583 | 27.3k | } |
2584 | | |
2585 | | /** |
2586 | | * @brief Make a new UniqueItemsConstraint object. |
2587 | | * |
2588 | | * @param node Node containing a boolean value. |
2589 | | * |
2590 | | * @return pointer to a new UniqueItemsConstraint object that belongs to |
2591 | | * the caller, or nullptr if the boolean value is false. |
2592 | | */ |
2593 | | template<typename AdapterType> |
2594 | | std::optional<constraints::UniqueItemsConstraint> makeUniqueItemsConstraint(const AdapterType &node) |
2595 | 1.69k | { |
2596 | 1.69k | if (node.isBool() || node.maybeBool()) { |
2597 | | // If the boolean value is true, this function will return a pointer |
2598 | | // to a new UniqueItemsConstraint object. If it is value, then the |
2599 | | // constraint is redundant, so nullptr is returned instead. |
2600 | 1.56k | if (node.asBool()) { |
2601 | 1.54k | return constraints::UniqueItemsConstraint(); |
2602 | 1.54k | } else { |
2603 | 21 | return std::optional<constraints::UniqueItemsConstraint>(); |
2604 | 21 | } |
2605 | 1.56k | } |
2606 | | |
2607 | 122 | throwRuntimeError("Expected boolean value for 'uniqueItems' constraint."); |
2608 | 122 | } |
2609 | | |
2610 | | private: |
2611 | | |
2612 | | /// Version of JSON Schema that should be expected when parsing |
2613 | | Version m_version; |
2614 | | }; |
2615 | | |
2616 | | } // namespace valijson |