Get *=default*ed default constructors
[folly.git] / folly / experimental / JSONSchema.cpp
1 /*
2  * Copyright 2015 Facebook, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <folly/experimental/JSONSchema.h>
18
19 #include <boost/algorithm/string/replace.hpp>
20 #include <boost/regex.hpp>
21 #include <folly/Conv.h>
22 #include <folly/Memory.h>
23 #include <folly/Optional.h>
24 #include <folly/String.h>
25 #include <folly/Singleton.h>
26 #include <folly/json.h>
27
28 namespace folly {
29 namespace jsonschema {
30
31 namespace {
32
33 /**
34  * We throw this exception when schema validation fails.
35  */
36 struct SchemaError : std::runtime_error {
37
38   SchemaError(SchemaError&&) = default;
39   SchemaError(const SchemaError&) = default;
40
41   SchemaError(folly::StringPiece expected, const dynamic& value)
42       : std::runtime_error(to<std::string>(
43             "Expected to get ", expected, " for value ", toJson(value))) {}
44   SchemaError(folly::StringPiece expected,
45               const dynamic& schema,
46               const dynamic& value)
47       : std::runtime_error(to<std::string>("Expected to get ",
48                                            expected,
49                                            toJson(schema),
50                                            " for value ",
51                                            toJson(value))) {}
52 };
53
54 template <class... Args>
55 Optional<SchemaError> makeError(Args&&... args) {
56   return Optional<SchemaError>(SchemaError(std::forward<Args>(args)...));
57 }
58
59 struct ValidationContext;
60
61 struct IValidator {
62   virtual ~IValidator() = default;
63
64  private:
65   friend struct ValidationContext;
66
67   virtual Optional<SchemaError> validate(ValidationContext&,
68                                          const dynamic& value) const = 0;
69 };
70
71 /**
72  * This is a 'context' used only when executing the validators to validate some
73  * json. It keeps track of which validators have been executed on which json so
74  * we can detect infinite recursion.
75  */
76 struct ValidationContext {
77   Optional<SchemaError> validate(IValidator* validator, const dynamic& value) {
78     auto ret = seen.insert(std::make_pair(validator, &value));
79     if (!ret.second) {
80       throw std::runtime_error("Infinite recursion detected");
81     }
82     return validator->validate(*this, value);
83   }
84
85  private:
86   std::unordered_set<std::pair<const IValidator*, const dynamic*>> seen;
87 };
88
89 /**
90  * This is a 'context' used only when building the schema validators from a
91  * piece of json. It stores the original schema and the set of refs, so that we
92  * can have parts of the schema refer to other parts.
93  */
94 struct SchemaValidatorContext final {
95   explicit SchemaValidatorContext(const dynamic& s) : schema(s) {}
96
97   const dynamic& schema;
98   std::unordered_map<fbstring, IValidator*> refs;
99 };
100
101 /**
102  * Root validator for a schema.
103  */
104 struct SchemaValidator final : IValidator, public Validator {
105   SchemaValidator() = default;
106   void loadSchema(SchemaValidatorContext& context, const dynamic& schema);
107
108   Optional<SchemaError> validate(ValidationContext&,
109                                  const dynamic& value) const override;
110
111   // Validator interface
112   void validate(const dynamic& value) const override;
113   exception_wrapper try_validate(const dynamic& value) const noexcept override;
114
115   static std::unique_ptr<SchemaValidator> make(SchemaValidatorContext& context,
116                                                const dynamic& schema) {
117     // We break apart the constructor and actually loading the schema so that
118     // we can handle the case where a schema refers to itself, e.g. via
119     // "$ref": "#".
120     auto v = make_unique<SchemaValidator>();
121     v->loadSchema(context, schema);
122     return v;
123   }
124
125  private:
126   std::vector<std::unique_ptr<IValidator>> validators_;
127 };
128
129 struct MultipleOfValidator final : IValidator {
130   explicit MultipleOfValidator(dynamic schema) : schema_(std::move(schema)) {}
131   Optional<SchemaError> validate(ValidationContext&,
132                                  const dynamic& value) const override {
133     if (!schema_.isNumber() || !value.isNumber()) {
134       return none;
135     }
136     if (schema_.isDouble() || value.isDouble()) {
137       const auto rem = std::remainder(value.asDouble(), schema_.asDouble());
138       if (std::abs(rem) > std::numeric_limits<double>::epsilon()) {
139         return makeError("a multiple of ", schema_, value);
140       }
141     } else { // both ints
142       if ((value.getInt() % schema_.getInt()) != 0) {
143         return makeError("a multiple of ", schema_, value);
144       }
145     }
146     return none;
147   }
148   dynamic schema_;
149 };
150
151 struct ComparisonValidator final : IValidator {
152   enum class Type { MIN, MAX };
153   ComparisonValidator(dynamic schema, const dynamic* exclusive, Type type)
154       : schema_(std::move(schema)), exclusive_(false), type_(type) {
155     if (exclusive && exclusive->isBool()) {
156       exclusive_ = exclusive->getBool();
157     }
158   }
159
160   template <typename Numeric>
161   Optional<SchemaError> validateHelper(const dynamic& value,
162                                        Numeric s,
163                                        Numeric v) const {
164     if (type_ == Type::MIN) {
165       if (exclusive_) {
166         if (v <= s) {
167           return makeError("greater than", schema_, value);
168         }
169       } else {
170         if (v < s) {
171           return makeError("greater than or equal to", schema_, value);
172         }
173       }
174     } else if (type_ == Type::MAX) {
175       if (exclusive_) {
176         if (v >= s) {
177           return makeError("less than", schema_, value);
178         }
179       } else {
180         if (v > s) {
181           return makeError("less than or equal to", schema_, value);
182         }
183       }
184     }
185     return none;
186   }
187
188   Optional<SchemaError> validate(ValidationContext&,
189                                  const dynamic& value) const override {
190     if (!schema_.isNumber() || !value.isNumber()) {
191       return none;
192     }
193     if (schema_.isDouble() || value.isDouble()) {
194       return validateHelper(value, schema_.asDouble(), value.asDouble());
195     } else { // both ints
196       return validateHelper(value, schema_.asInt(), value.asInt());
197     }
198   }
199
200   dynamic schema_;
201   bool exclusive_;
202   Type type_;
203 };
204
205 template <class Comparison>
206 struct SizeValidator final : IValidator {
207   explicit SizeValidator(const dynamic& schema, dynamic::Type type)
208       : length_(-1), type_(type) {
209     if (schema.isInt()) {
210       length_ = schema.getInt();
211     }
212   }
213
214   Optional<SchemaError> validate(ValidationContext&,
215                                  const dynamic& value) const override {
216     if (length_ < 0) {
217       return none;
218     }
219     if (value.type() != type_) {
220       return none;
221     }
222     if (!Comparison()(length_, value.size())) {
223       return makeError("different length string/array/object", value);
224     }
225     return none;
226   }
227   int64_t length_;
228   dynamic::Type type_;
229 };
230
231 struct StringPatternValidator final : IValidator {
232   explicit StringPatternValidator(const dynamic& schema) {
233     if (schema.isString()) {
234       regex_ = boost::regex(schema.getString().toStdString());
235     }
236   }
237
238   Optional<SchemaError> validate(ValidationContext&,
239                                  const dynamic& value) const override {
240     if (!value.isString() || regex_.empty()) {
241       return none;
242     }
243     if (!boost::regex_search(value.getString().toStdString(), regex_)) {
244       return makeError("string matching regex", value);
245     }
246     return none;
247   }
248   boost::regex regex_;
249 };
250
251 struct ArrayUniqueValidator final : IValidator {
252   explicit ArrayUniqueValidator(const dynamic& schema) : unique_(false) {
253     if (schema.isBool()) {
254       unique_ = schema.getBool();
255     }
256   }
257
258   Optional<SchemaError> validate(ValidationContext&,
259                                  const dynamic& value) const override {
260     if (!unique_ || !value.isArray()) {
261       return none;
262     }
263     for (const auto& i : value) {
264       for (const auto& j : value) {
265         if (&i != &j && i == j) {
266           return makeError("unique items in array", value);
267         }
268       }
269     }
270     return none;
271   }
272   bool unique_;
273 };
274
275 struct ArrayItemsValidator final : IValidator {
276   ArrayItemsValidator(SchemaValidatorContext& context,
277                       const dynamic* items,
278                       const dynamic* additionalItems)
279       : allowAdditionalItems_(true) {
280     if (items && items->isObject()) {
281       itemsValidator_ = SchemaValidator::make(context, *items);
282       return; // Additional items is ignored
283     } else if (items && items->isArray()) {
284       for (const auto& item : *items) {
285         itemsValidators_.emplace_back(SchemaValidator::make(context, item));
286       }
287     } else {
288       // If items isn't present or is invalid, it defaults to an empty schema.
289       itemsValidator_ = SchemaValidator::make(context, dynamic::object);
290     }
291     if (additionalItems) {
292       if (additionalItems->isBool()) {
293         allowAdditionalItems_ = additionalItems->getBool();
294       } else if (additionalItems->isObject()) {
295         additionalItemsValidator_ =
296             SchemaValidator::make(context, *additionalItems);
297       }
298     }
299   }
300
301   Optional<SchemaError> validate(ValidationContext& vc,
302                                  const dynamic& value) const override {
303     if (!value.isArray()) {
304       return none;
305     }
306     if (itemsValidator_) {
307       for (const auto& v : value) {
308         if (auto se = vc.validate(itemsValidator_.get(), v)) {
309           return se;
310         }
311       }
312       return none;
313     }
314     size_t pos = 0;
315     for (; pos < value.size() && pos < itemsValidators_.size(); ++pos) {
316       if (auto se = vc.validate(itemsValidators_[pos].get(), value[pos])) {
317         return se;
318       }
319     }
320     if (!allowAdditionalItems_ && pos < value.size()) {
321       return makeError("no more additional items", value);
322     }
323     if (additionalItemsValidator_) {
324       for (; pos < value.size(); ++pos) {
325         if (auto se =
326                 vc.validate(additionalItemsValidator_.get(), value[pos])) {
327           return se;
328         }
329       }
330     }
331     return none;
332   }
333   std::unique_ptr<IValidator> itemsValidator_;
334   std::vector<std::unique_ptr<IValidator>> itemsValidators_;
335   std::unique_ptr<IValidator> additionalItemsValidator_;
336   bool allowAdditionalItems_;
337 };
338
339 struct RequiredValidator final : IValidator {
340   explicit RequiredValidator(const dynamic& schema) {
341     if (schema.isArray()) {
342       for (const auto& item : schema) {
343         if (item.isString()) {
344           properties_.emplace_back(item.getString());
345         }
346       }
347     }
348   }
349
350   Optional<SchemaError> validate(ValidationContext&,
351                                  const dynamic& value) const override {
352     if (value.isObject()) {
353       for (const auto& prop : properties_) {
354         if (!value.get_ptr(prop)) {
355           return makeError("to have property", prop, value);
356         }
357       }
358     }
359     return none;
360   }
361
362  private:
363   std::vector<fbstring> properties_;
364 };
365
366 struct PropertiesValidator final : IValidator {
367   PropertiesValidator(SchemaValidatorContext& context,
368                       const dynamic* properties,
369                       const dynamic* patternProperties,
370                       const dynamic* additionalProperties)
371       : allowAdditionalProperties_(true) {
372     if (properties && properties->isObject()) {
373       for (const auto& pair : properties->items()) {
374         if (pair.first.isString()) {
375           propertyValidators_[pair.first.getString()] =
376               SchemaValidator::make(context, pair.second);
377         }
378       }
379     }
380     if (patternProperties && patternProperties->isObject()) {
381       for (const auto& pair : patternProperties->items()) {
382         if (pair.first.isString()) {
383           patternPropertyValidators_.emplace_back(
384               boost::regex(pair.first.getString().toStdString()),
385               SchemaValidator::make(context, pair.second));
386         }
387       }
388     }
389     if (additionalProperties) {
390       if (additionalProperties->isBool()) {
391         allowAdditionalProperties_ = additionalProperties->getBool();
392       } else if (additionalProperties->isObject()) {
393         additionalPropertyValidator_ =
394             SchemaValidator::make(context, *additionalProperties);
395       }
396     }
397   }
398
399   Optional<SchemaError> validate(ValidationContext& vc,
400                                  const dynamic& value) const override {
401     if (!value.isObject()) {
402       return none;
403     }
404     for (const auto& pair : value.items()) {
405       if (!pair.first.isString()) {
406         continue;
407       }
408       const fbstring& key = pair.first.getString();
409       auto it = propertyValidators_.find(key);
410       bool matched = false;
411       if (it != propertyValidators_.end()) {
412         if (auto se = vc.validate(it->second.get(), pair.second)) {
413           return se;
414         }
415         matched = true;
416       }
417
418       const std::string& strkey = key.toStdString();
419       for (const auto& ppv : patternPropertyValidators_) {
420         if (boost::regex_search(strkey, ppv.first)) {
421           if (auto se = vc.validate(ppv.second.get(), pair.second)) {
422             return se;
423           }
424           matched = true;
425         }
426       }
427       if (matched) {
428         continue;
429       }
430       if (!allowAdditionalProperties_) {
431         return makeError("no more additional properties", value);
432       }
433       if (additionalPropertyValidator_) {
434         if (auto se =
435                 vc.validate(additionalPropertyValidator_.get(), pair.second)) {
436           return se;
437         }
438       }
439     }
440     return none;
441   }
442
443   std::unordered_map<fbstring, std::unique_ptr<IValidator>> propertyValidators_;
444   std::vector<std::pair<boost::regex, std::unique_ptr<IValidator>>>
445       patternPropertyValidators_;
446   std::unique_ptr<IValidator> additionalPropertyValidator_;
447   bool allowAdditionalProperties_;
448 };
449
450 struct DependencyValidator final : IValidator {
451   DependencyValidator(SchemaValidatorContext& context, const dynamic& schema) {
452     if (!schema.isObject()) {
453       return;
454     }
455     for (const auto& pair : schema.items()) {
456       if (!pair.first.isString()) {
457         continue;
458       }
459       if (pair.second.isArray()) {
460         auto p = make_pair(pair.first.getString(), std::vector<fbstring>());
461         for (const auto& item : pair.second) {
462           if (item.isString()) {
463             p.second.push_back(item.getString());
464           }
465         }
466         propertyDep_.emplace_back(std::move(p));
467       }
468       if (pair.second.isObject()) {
469         schemaDep_.emplace_back(pair.first.getString(),
470                                 SchemaValidator::make(context, pair.second));
471       }
472     }
473   }
474
475   Optional<SchemaError> validate(ValidationContext& vc,
476                                  const dynamic& value) const override {
477     if (!value.isObject()) {
478       return none;
479     }
480     for (const auto& pair : propertyDep_) {
481       if (value.count(pair.first)) {
482         for (const auto& prop : pair.second) {
483           if (!value.count(prop)) {
484             return makeError("property", prop, value);
485           }
486         }
487       }
488     }
489     for (const auto& pair : schemaDep_) {
490       if (value.count(pair.first)) {
491         if (auto se = vc.validate(pair.second.get(), value)) {
492           return se;
493         }
494       }
495     }
496     return none;
497   }
498
499   std::vector<std::pair<fbstring, std::vector<fbstring>>> propertyDep_;
500   std::vector<std::pair<fbstring, std::unique_ptr<IValidator>>> schemaDep_;
501 };
502
503 struct EnumValidator final : IValidator {
504   explicit EnumValidator(dynamic schema) : schema_(std::move(schema)) {}
505
506   Optional<SchemaError> validate(ValidationContext&,
507                                  const dynamic& value) const override {
508     if (!schema_.isArray()) {
509       return none;
510     }
511     for (const auto& item : schema_) {
512       if (value == item) {
513         return none;
514       }
515     }
516     return makeError("one of enum values: ", schema_, value);
517   }
518   dynamic schema_;
519 };
520
521 struct TypeValidator final : IValidator {
522   explicit TypeValidator(const dynamic& schema) {
523     if (schema.isString()) {
524       addType(schema.stringPiece());
525     } else if (schema.isArray()) {
526       for (const auto& item : schema) {
527         if (item.isString()) {
528           addType(item.stringPiece());
529         }
530       }
531     }
532   }
533
534   Optional<SchemaError> validate(ValidationContext&,
535                                  const dynamic& value) const override {
536     auto it =
537         std::find(allowedTypes_.begin(), allowedTypes_.end(), value.type());
538     if (it == allowedTypes_.end()) {
539       return makeError("a value of type ", typeStr_, value);
540     }
541     return none;
542   }
543
544  private:
545   std::vector<dynamic::Type> allowedTypes_;
546   std::string typeStr_; // for errors
547
548   void addType(StringPiece value) {
549     if (value == "array") {
550       allowedTypes_.push_back(dynamic::Type::ARRAY);
551     } else if (value == "boolean") {
552       allowedTypes_.push_back(dynamic::Type::BOOL);
553     } else if (value == "integer") {
554       allowedTypes_.push_back(dynamic::Type::INT64);
555     } else if (value == "number") {
556       allowedTypes_.push_back(dynamic::Type::INT64);
557       allowedTypes_.push_back(dynamic::Type::DOUBLE);
558     } else if (value == "null") {
559       allowedTypes_.push_back(dynamic::Type::NULLT);
560     } else if (value == "object") {
561       allowedTypes_.push_back(dynamic::Type::OBJECT);
562     } else if (value == "string") {
563       allowedTypes_.push_back(dynamic::Type::STRING);
564     } else {
565       return;
566     }
567     if (!typeStr_.empty()) {
568       typeStr_ += ", ";
569     }
570     typeStr_ += value.str();
571   }
572 };
573
574 struct AllOfValidator final : IValidator {
575   AllOfValidator(SchemaValidatorContext& context, const dynamic& schema) {
576     if (schema.isArray()) {
577       for (const auto& item : schema) {
578         validators_.emplace_back(SchemaValidator::make(context, item));
579       }
580     }
581   }
582
583   Optional<SchemaError> validate(ValidationContext& vc,
584                                  const dynamic& value) const override {
585     for (const auto& val : validators_) {
586       if (auto se = vc.validate(val.get(), value)) {
587         return se;
588       }
589     }
590     return none;
591   }
592
593   std::vector<std::unique_ptr<IValidator>> validators_;
594 };
595
596 struct AnyOfValidator final : IValidator {
597   enum class Type { EXACTLY_ONE, ONE_OR_MORE };
598
599   AnyOfValidator(SchemaValidatorContext& context,
600                  const dynamic& schema,
601                  Type type)
602       : type_(type) {
603     if (schema.isArray()) {
604       for (const auto& item : schema) {
605         validators_.emplace_back(SchemaValidator::make(context, item));
606       }
607     }
608   }
609
610   Optional<SchemaError> validate(ValidationContext& vc,
611                                  const dynamic& value) const override {
612     std::vector<SchemaError> errors;
613     for (const auto& val : validators_) {
614       if (auto se = vc.validate(val.get(), value)) {
615         errors.emplace_back(*se);
616       }
617     }
618     const int success = validators_.size() - errors.size();
619     if (success == 0) {
620       return makeError("at least one valid schema", value);
621     } else if (success > 1 && type_ == Type::EXACTLY_ONE) {
622       return makeError("exactly one valid schema", value);
623     }
624     return none;
625   }
626
627   Type type_;
628   std::vector<std::unique_ptr<IValidator>> validators_;
629 };
630
631 struct RefValidator final : IValidator {
632   explicit RefValidator(IValidator* validator) : validator_(validator) {}
633
634   Optional<SchemaError> validate(ValidationContext& vc,
635                                  const dynamic& value) const override {
636     return vc.validate(validator_, value);
637   }
638   IValidator* validator_;
639 };
640
641 struct NotValidator final : IValidator {
642   NotValidator(SchemaValidatorContext& context, const dynamic& schema)
643       : validator_(SchemaValidator::make(context, schema)) {}
644
645   Optional<SchemaError> validate(ValidationContext& vc,
646                                  const dynamic& value) const override {
647     if (vc.validate(validator_.get(), value)) {
648       return none;
649     }
650     return makeError("Expected schema validation to fail", value);
651   }
652   std::unique_ptr<IValidator> validator_;
653 };
654
655 void SchemaValidator::loadSchema(SchemaValidatorContext& context,
656                                  const dynamic& schema) {
657   if (!schema.isObject() || schema.empty()) {
658     return;
659   }
660
661   // Check for $ref, if we have one we won't apply anything else. Refs are
662   // pointers to other parts of the json, e.g. #/foo/bar points to the schema
663   // located at root["foo"]["bar"].
664   if (const auto* p = schema.get_ptr("$ref")) {
665     // We only support absolute refs, i.e. those starting with '#'
666     if (p->isString() && p->stringPiece()[0] == '#') {
667       auto it = context.refs.find(p->getString());
668       if (it != context.refs.end()) {
669         validators_.emplace_back(make_unique<RefValidator>(it->second));
670         return;
671       }
672
673       // This is a ref, but we haven't loaded it yet. Find where it is based on
674       // the root schema.
675       std::vector<std::string> parts;
676       split("/", p->stringPiece(), parts);
677       const auto* s = &context.schema; // First part is '#'
678       for (size_t i = 1; s && i < parts.size(); ++i) {
679         // Per the standard, we must replace ~1 with / and then ~0 with ~
680         boost::replace_all(parts[i], "~1", "/");
681         boost::replace_all(parts[i], "~0", "~");
682         if (s->isObject()) {
683           s = s->get_ptr(parts[i]);
684           continue;
685         }
686         if (s->isArray()) {
687           try {
688             const size_t pos = to<size_t>(parts[i]);
689             if (pos < s->size()) {
690               s = s->get_ptr(pos);
691               continue;
692             }
693           } catch (const std::range_error& e) {
694             // ignore
695           }
696         }
697         break;
698       }
699       // If you have a self-recursive reference, this avoids getting into an
700       // infinite recursion, where we try to load a schema that just references
701       // itself, and then we try to load it again, and so on.
702       // Instead we load a pointer to the schema into the refs, so that any
703       // future references to it will just see that pointer and won't try to
704       // keep parsing further.
705       if (s) {
706         auto v = make_unique<SchemaValidator>();
707         context.refs[p->getString()] = v.get();
708         v->loadSchema(context, *s);
709         validators_.emplace_back(std::move(v));
710         return;
711       }
712     }
713   }
714
715   // Numeric validators
716   if (const auto* p = schema.get_ptr("multipleOf")) {
717     validators_.emplace_back(make_unique<MultipleOfValidator>(*p));
718   }
719   if (const auto* p = schema.get_ptr("maximum")) {
720     validators_.emplace_back(
721         make_unique<ComparisonValidator>(*p,
722                                          schema.get_ptr("exclusiveMaximum"),
723                                          ComparisonValidator::Type::MAX));
724   }
725   if (const auto* p = schema.get_ptr("minimum")) {
726     validators_.emplace_back(
727         make_unique<ComparisonValidator>(*p,
728                                          schema.get_ptr("exclusiveMinimum"),
729                                          ComparisonValidator::Type::MIN));
730   }
731
732   // String validators
733   if (const auto* p = schema.get_ptr("maxLength")) {
734     validators_.emplace_back(
735         make_unique<SizeValidator<std::greater_equal<int64_t>>>(
736             *p, dynamic::Type::STRING));
737   }
738   if (const auto* p = schema.get_ptr("minLength")) {
739     validators_.emplace_back(
740         make_unique<SizeValidator<std::less_equal<int64_t>>>(
741             *p, dynamic::Type::STRING));
742   }
743   if (const auto* p = schema.get_ptr("pattern")) {
744     validators_.emplace_back(make_unique<StringPatternValidator>(*p));
745   }
746
747   // Array validators
748   const auto* items = schema.get_ptr("items");
749   const auto* additionalItems = schema.get_ptr("additionalItems");
750   if (items || additionalItems) {
751     validators_.emplace_back(
752         make_unique<ArrayItemsValidator>(context, items, additionalItems));
753   }
754   if (const auto* p = schema.get_ptr("maxItems")) {
755     validators_.emplace_back(
756         make_unique<SizeValidator<std::greater_equal<int64_t>>>(
757             *p, dynamic::Type::ARRAY));
758   }
759   if (const auto* p = schema.get_ptr("minItems")) {
760     validators_.emplace_back(
761         make_unique<SizeValidator<std::less_equal<int64_t>>>(
762             *p, dynamic::Type::ARRAY));
763   }
764   if (const auto* p = schema.get_ptr("uniqueItems")) {
765     validators_.emplace_back(make_unique<ArrayUniqueValidator>(*p));
766   }
767
768   // Object validators
769   const auto* properties = schema.get_ptr("properties");
770   const auto* patternProperties = schema.get_ptr("patternProperties");
771   const auto* additionalProperties = schema.get_ptr("additionalProperties");
772   if (properties || patternProperties || additionalProperties) {
773     validators_.emplace_back(make_unique<PropertiesValidator>(
774         context, properties, patternProperties, additionalProperties));
775   }
776   if (const auto* p = schema.get_ptr("maxProperties")) {
777     validators_.emplace_back(
778         make_unique<SizeValidator<std::greater_equal<int64_t>>>(
779             *p, dynamic::Type::OBJECT));
780   }
781   if (const auto* p = schema.get_ptr("minProperties")) {
782     validators_.emplace_back(
783         make_unique<SizeValidator<std::less_equal<int64_t>>>(
784             *p, dynamic::Type::OBJECT));
785   }
786   if (const auto* p = schema.get_ptr("required")) {
787     validators_.emplace_back(make_unique<RequiredValidator>(*p));
788   }
789
790   // Misc validators
791   if (const auto* p = schema.get_ptr("dependencies")) {
792     validators_.emplace_back(make_unique<DependencyValidator>(context, *p));
793   }
794   if (const auto* p = schema.get_ptr("enum")) {
795     validators_.emplace_back(make_unique<EnumValidator>(*p));
796   }
797   if (const auto* p = schema.get_ptr("type")) {
798     validators_.emplace_back(make_unique<TypeValidator>(*p));
799   }
800   if (const auto* p = schema.get_ptr("allOf")) {
801     validators_.emplace_back(make_unique<AllOfValidator>(context, *p));
802   }
803   if (const auto* p = schema.get_ptr("anyOf")) {
804     validators_.emplace_back(make_unique<AnyOfValidator>(
805         context, *p, AnyOfValidator::Type::ONE_OR_MORE));
806   }
807   if (const auto* p = schema.get_ptr("oneOf")) {
808     validators_.emplace_back(make_unique<AnyOfValidator>(
809         context, *p, AnyOfValidator::Type::EXACTLY_ONE));
810   }
811   if (const auto* p = schema.get_ptr("not")) {
812     validators_.emplace_back(make_unique<NotValidator>(context, *p));
813   }
814 }
815
816 void SchemaValidator::validate(const dynamic& value) const {
817   ValidationContext vc;
818   if (auto se = validate(vc, value)) {
819     throw * se;
820   }
821 }
822
823 exception_wrapper SchemaValidator::try_validate(const dynamic& value) const
824     noexcept {
825   try {
826     ValidationContext vc;
827     if (auto se = validate(vc, value)) {
828       return make_exception_wrapper<SchemaError>(*se);
829     }
830   } catch (const std::exception& e) {
831     return exception_wrapper(std::current_exception(), e);
832   } catch (...) {
833     return exception_wrapper(std::current_exception());
834   }
835   return exception_wrapper();
836 }
837
838 Optional<SchemaError> SchemaValidator::validate(ValidationContext& vc,
839                                                 const dynamic& value) const {
840   for (const auto& validator : validators_) {
841     if (auto se = vc.validate(validator.get(), value)) {
842       return se;
843     }
844   }
845   return none;
846 }
847
848 /**
849  * Metaschema, i.e. schema for schema.
850  * Inlined from the $schema url
851  */
852 const char* metaschemaJson =
853     "\
854 { \
855     \"id\": \"http://json-schema.org/draft-04/schema#\", \
856     \"$schema\": \"http://json-schema.org/draft-04/schema#\", \
857     \"description\": \"Core schema meta-schema\", \
858     \"definitions\": { \
859         \"schemaArray\": { \
860             \"type\": \"array\", \
861             \"minItems\": 1, \
862             \"items\": { \"$ref\": \"#\" } \
863         }, \
864         \"positiveInteger\": { \
865             \"type\": \"integer\", \
866             \"minimum\": 0 \
867         }, \
868         \"positiveIntegerDefault0\": { \
869             \"allOf\": [ \
870           { \"$ref\": \"#/definitions/positiveInteger\" }, { \"default\": 0 } ]\
871         }, \
872         \"simpleTypes\": { \
873             \"enum\": [ \"array\", \"boolean\", \"integer\", \
874                         \"null\", \"number\", \"object\", \"string\" ] \
875         }, \
876         \"stringArray\": { \
877             \"type\": \"array\", \
878             \"items\": { \"type\": \"string\" }, \
879             \"minItems\": 1, \
880             \"uniqueItems\": true \
881         } \
882     }, \
883     \"type\": \"object\", \
884     \"properties\": { \
885         \"id\": { \
886             \"type\": \"string\", \
887             \"format\": \"uri\" \
888         }, \
889         \"$schema\": { \
890             \"type\": \"string\", \
891             \"format\": \"uri\" \
892         }, \
893         \"title\": { \
894             \"type\": \"string\" \
895         }, \
896         \"description\": { \
897             \"type\": \"string\" \
898         }, \
899         \"default\": {}, \
900         \"multipleOf\": { \
901             \"type\": \"number\", \
902             \"minimum\": 0, \
903             \"exclusiveMinimum\": true \
904         }, \
905         \"maximum\": { \
906             \"type\": \"number\" \
907         }, \
908         \"exclusiveMaximum\": { \
909             \"type\": \"boolean\", \
910             \"default\": false \
911         }, \
912         \"minimum\": { \
913             \"type\": \"number\" \
914         }, \
915         \"exclusiveMinimum\": { \
916             \"type\": \"boolean\", \
917             \"default\": false \
918         }, \
919         \"maxLength\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
920         \"minLength\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" },\
921         \"pattern\": { \
922             \"type\": \"string\", \
923             \"format\": \"regex\" \
924         }, \
925         \"additionalItems\": { \
926             \"anyOf\": [ \
927                 { \"type\": \"boolean\" }, \
928                 { \"$ref\": \"#\" } \
929             ], \
930             \"default\": {} \
931         }, \
932         \"items\": { \
933             \"anyOf\": [ \
934                 { \"$ref\": \"#\" }, \
935                 { \"$ref\": \"#/definitions/schemaArray\" } \
936             ], \
937             \"default\": {} \
938         }, \
939         \"maxItems\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
940         \"minItems\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
941         \"uniqueItems\": { \
942             \"type\": \"boolean\", \
943             \"default\": false \
944         }, \
945         \"maxProperties\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
946         \"minProperties\": { \
947         \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
948         \"required\": { \"$ref\": \"#/definitions/stringArray\" }, \
949         \"additionalProperties\": { \
950             \"anyOf\": [ \
951                 { \"type\": \"boolean\" }, \
952                 { \"$ref\": \"#\" } \
953             ], \
954             \"default\": {} \
955         }, \
956         \"definitions\": { \
957             \"type\": \"object\", \
958             \"additionalProperties\": { \"$ref\": \"#\" }, \
959             \"default\": {} \
960         }, \
961         \"properties\": { \
962             \"type\": \"object\", \
963             \"additionalProperties\": { \"$ref\": \"#\" }, \
964             \"default\": {} \
965         }, \
966         \"patternProperties\": { \
967             \"type\": \"object\", \
968             \"additionalProperties\": { \"$ref\": \"#\" }, \
969             \"default\": {} \
970         }, \
971         \"dependencies\": { \
972             \"type\": \"object\", \
973             \"additionalProperties\": { \
974                 \"anyOf\": [ \
975                     { \"$ref\": \"#\" }, \
976                     { \"$ref\": \"#/definitions/stringArray\" } \
977                 ] \
978             } \
979         }, \
980         \"enum\": { \
981             \"type\": \"array\", \
982             \"minItems\": 1, \
983             \"uniqueItems\": true \
984         }, \
985         \"type\": { \
986             \"anyOf\": [ \
987                 { \"$ref\": \"#/definitions/simpleTypes\" }, \
988                 { \
989                     \"type\": \"array\", \
990                     \"items\": { \"$ref\": \"#/definitions/simpleTypes\" }, \
991                     \"minItems\": 1, \
992                     \"uniqueItems\": true \
993                 } \
994             ] \
995         }, \
996         \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
997         \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
998         \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
999         \"not\": { \"$ref\": \"#\" } \
1000     }, \
1001     \"dependencies\": { \
1002         \"exclusiveMaximum\": [ \"maximum\" ], \
1003         \"exclusiveMinimum\": [ \"minimum\" ] \
1004     }, \
1005     \"default\": {} \
1006 }";
1007
1008 folly::Singleton<Validator> schemaValidator([]() {
1009   return makeValidator(parseJson(metaschemaJson)).release();
1010 });
1011 }
1012
1013 Validator::~Validator() = default;
1014
1015 std::unique_ptr<Validator> makeValidator(const dynamic& schema) {
1016   auto v = make_unique<SchemaValidator>();
1017   SchemaValidatorContext context(schema);
1018   context.refs["#"] = v.get();
1019   v->loadSchema(context, schema);
1020   return std::move(v);
1021 }
1022
1023 Validator* makeSchemaValidator() { return schemaValidator.get(); }
1024 }
1025 }