Coverage Report

Created: 2023-06-07 06:59

/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 &currentNode,
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