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