Allow folly to compile cleanly with most of the rest of MSVC's sign mismatch warnings
[folly.git] / folly / experimental / JSONSchema.cpp
1 /*
2  * Copyright 2016 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<std::string, 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()(size_t(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());
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(), 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("property ", prop, value);
356         }
357       }
358     }
359     return none;
360   }
361
362  private:
363   std::vector<std::string> 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()),
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 std::string& 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;
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<std::string, std::unique_ptr<IValidator>>
444       propertyValidators_;
445   std::vector<std::pair<boost::regex, std::unique_ptr<IValidator>>>
446       patternPropertyValidators_;
447   std::unique_ptr<IValidator> additionalPropertyValidator_;
448   bool allowAdditionalProperties_;
449 };
450
451 struct DependencyValidator final : IValidator {
452   DependencyValidator(SchemaValidatorContext& context, const dynamic& schema) {
453     if (!schema.isObject()) {
454       return;
455     }
456     for (const auto& pair : schema.items()) {
457       if (!pair.first.isString()) {
458         continue;
459       }
460       if (pair.second.isArray()) {
461         auto p = make_pair(pair.first.getString(), std::vector<std::string>());
462         for (const auto& item : pair.second) {
463           if (item.isString()) {
464             p.second.push_back(item.getString());
465           }
466         }
467         propertyDep_.emplace_back(std::move(p));
468       }
469       if (pair.second.isObject()) {
470         schemaDep_.emplace_back(pair.first.getString(),
471                                 SchemaValidator::make(context, pair.second));
472       }
473     }
474   }
475
476   Optional<SchemaError> validate(ValidationContext& vc,
477                                  const dynamic& value) const override {
478     if (!value.isObject()) {
479       return none;
480     }
481     for (const auto& pair : propertyDep_) {
482       if (value.count(pair.first)) {
483         for (const auto& prop : pair.second) {
484           if (!value.count(prop)) {
485             return makeError("property ", prop, value);
486           }
487         }
488       }
489     }
490     for (const auto& pair : schemaDep_) {
491       if (value.count(pair.first)) {
492         if (auto se = vc.validate(pair.second.get(), value)) {
493           return se;
494         }
495       }
496     }
497     return none;
498   }
499
500   std::vector<std::pair<std::string, std::vector<std::string>>> propertyDep_;
501   std::vector<std::pair<std::string, std::unique_ptr<IValidator>>> schemaDep_;
502 };
503
504 struct EnumValidator final : IValidator {
505   explicit EnumValidator(dynamic schema) : schema_(std::move(schema)) {}
506
507   Optional<SchemaError> validate(ValidationContext&,
508                                  const dynamic& value) const override {
509     if (!schema_.isArray()) {
510       return none;
511     }
512     for (const auto& item : schema_) {
513       if (value == item) {
514         return none;
515       }
516     }
517     return makeError("one of enum values: ", schema_, value);
518   }
519   dynamic schema_;
520 };
521
522 struct TypeValidator final : IValidator {
523   explicit TypeValidator(const dynamic& schema) {
524     if (schema.isString()) {
525       addType(schema.stringPiece());
526     } else if (schema.isArray()) {
527       for (const auto& item : schema) {
528         if (item.isString()) {
529           addType(item.stringPiece());
530         }
531       }
532     }
533   }
534
535   Optional<SchemaError> validate(ValidationContext&,
536                                  const dynamic& value) const override {
537     auto it =
538         std::find(allowedTypes_.begin(), allowedTypes_.end(), value.type());
539     if (it == allowedTypes_.end()) {
540       return makeError("a value of type ", typeStr_, value);
541     }
542     return none;
543   }
544
545  private:
546   std::vector<dynamic::Type> allowedTypes_;
547   std::string typeStr_; // for errors
548
549   void addType(StringPiece value) {
550     if (value == "array") {
551       allowedTypes_.push_back(dynamic::Type::ARRAY);
552     } else if (value == "boolean") {
553       allowedTypes_.push_back(dynamic::Type::BOOL);
554     } else if (value == "integer") {
555       allowedTypes_.push_back(dynamic::Type::INT64);
556     } else if (value == "number") {
557       allowedTypes_.push_back(dynamic::Type::INT64);
558       allowedTypes_.push_back(dynamic::Type::DOUBLE);
559     } else if (value == "null") {
560       allowedTypes_.push_back(dynamic::Type::NULLT);
561     } else if (value == "object") {
562       allowedTypes_.push_back(dynamic::Type::OBJECT);
563     } else if (value == "string") {
564       allowedTypes_.push_back(dynamic::Type::STRING);
565     } else {
566       return;
567     }
568     if (!typeStr_.empty()) {
569       typeStr_ += ", ";
570     }
571     typeStr_ += value.str();
572   }
573 };
574
575 struct AllOfValidator final : IValidator {
576   AllOfValidator(SchemaValidatorContext& context, const dynamic& schema) {
577     if (schema.isArray()) {
578       for (const auto& item : schema) {
579         validators_.emplace_back(SchemaValidator::make(context, item));
580       }
581     }
582   }
583
584   Optional<SchemaError> validate(ValidationContext& vc,
585                                  const dynamic& value) const override {
586     for (const auto& val : validators_) {
587       if (auto se = vc.validate(val.get(), value)) {
588         return se;
589       }
590     }
591     return none;
592   }
593
594   std::vector<std::unique_ptr<IValidator>> validators_;
595 };
596
597 struct AnyOfValidator final : IValidator {
598   enum class Type { EXACTLY_ONE, ONE_OR_MORE };
599
600   AnyOfValidator(SchemaValidatorContext& context,
601                  const dynamic& schema,
602                  Type type)
603       : type_(type) {
604     if (schema.isArray()) {
605       for (const auto& item : schema) {
606         validators_.emplace_back(SchemaValidator::make(context, item));
607       }
608     }
609   }
610
611   Optional<SchemaError> validate(ValidationContext& vc,
612                                  const dynamic& value) const override {
613     std::vector<SchemaError> errors;
614     for (const auto& val : validators_) {
615       if (auto se = vc.validate(val.get(), value)) {
616         errors.emplace_back(*se);
617       }
618     }
619     const auto success = validators_.size() - errors.size();
620     if (success == 0) {
621       return makeError("at least one valid schema", value);
622     } else if (success > 1 && type_ == Type::EXACTLY_ONE) {
623       return makeError("exactly one valid schema", value);
624     }
625     return none;
626   }
627
628   Type type_;
629   std::vector<std::unique_ptr<IValidator>> validators_;
630 };
631
632 struct RefValidator final : IValidator {
633   explicit RefValidator(IValidator* validator) : validator_(validator) {}
634
635   Optional<SchemaError> validate(ValidationContext& vc,
636                                  const dynamic& value) const override {
637     return vc.validate(validator_, value);
638   }
639   IValidator* validator_;
640 };
641
642 struct NotValidator final : IValidator {
643   NotValidator(SchemaValidatorContext& context, const dynamic& schema)
644       : validator_(SchemaValidator::make(context, schema)) {}
645
646   Optional<SchemaError> validate(ValidationContext& vc,
647                                  const dynamic& value) const override {
648     if (vc.validate(validator_.get(), value)) {
649       return none;
650     }
651     return makeError("Expected schema validation to fail", value);
652   }
653   std::unique_ptr<IValidator> validator_;
654 };
655
656 void SchemaValidator::loadSchema(SchemaValidatorContext& context,
657                                  const dynamic& schema) {
658   if (!schema.isObject() || schema.empty()) {
659     return;
660   }
661
662   // Check for $ref, if we have one we won't apply anything else. Refs are
663   // pointers to other parts of the json, e.g. #/foo/bar points to the schema
664   // located at root["foo"]["bar"].
665   if (const auto* p = schema.get_ptr("$ref")) {
666     // We only support absolute refs, i.e. those starting with '#'
667     if (p->isString() && p->stringPiece()[0] == '#') {
668       auto it = context.refs.find(p->getString());
669       if (it != context.refs.end()) {
670         validators_.emplace_back(make_unique<RefValidator>(it->second));
671         return;
672       }
673
674       // This is a ref, but we haven't loaded it yet. Find where it is based on
675       // the root schema.
676       std::vector<std::string> parts;
677       split("/", p->stringPiece(), parts);
678       const auto* s = &context.schema; // First part is '#'
679       for (size_t i = 1; s && i < parts.size(); ++i) {
680         // Per the standard, we must replace ~1 with / and then ~0 with ~
681         boost::replace_all(parts[i], "~1", "/");
682         boost::replace_all(parts[i], "~0", "~");
683         if (s->isObject()) {
684           s = s->get_ptr(parts[i]);
685           continue;
686         }
687         if (s->isArray()) {
688           try {
689             const size_t pos = to<size_t>(parts[i]);
690             if (pos < s->size()) {
691               s = s->get_ptr(pos);
692               continue;
693             }
694           } catch (const std::range_error&) {
695             // ignore
696           }
697         }
698         break;
699       }
700       // If you have a self-recursive reference, this avoids getting into an
701       // infinite recursion, where we try to load a schema that just references
702       // itself, and then we try to load it again, and so on.
703       // Instead we load a pointer to the schema into the refs, so that any
704       // future references to it will just see that pointer and won't try to
705       // keep parsing further.
706       if (s) {
707         auto v = make_unique<SchemaValidator>();
708         context.refs[p->getString()] = v.get();
709         v->loadSchema(context, *s);
710         validators_.emplace_back(std::move(v));
711         return;
712       }
713     }
714   }
715
716   // Numeric validators
717   if (const auto* p = schema.get_ptr("multipleOf")) {
718     validators_.emplace_back(make_unique<MultipleOfValidator>(*p));
719   }
720   if (const auto* p = schema.get_ptr("maximum")) {
721     validators_.emplace_back(
722         make_unique<ComparisonValidator>(*p,
723                                          schema.get_ptr("exclusiveMaximum"),
724                                          ComparisonValidator::Type::MAX));
725   }
726   if (const auto* p = schema.get_ptr("minimum")) {
727     validators_.emplace_back(
728         make_unique<ComparisonValidator>(*p,
729                                          schema.get_ptr("exclusiveMinimum"),
730                                          ComparisonValidator::Type::MIN));
731   }
732
733   // String validators
734   if (const auto* p = schema.get_ptr("maxLength")) {
735     validators_.emplace_back(
736         make_unique<SizeValidator<std::greater_equal<int64_t>>>(
737             *p, dynamic::Type::STRING));
738   }
739   if (const auto* p = schema.get_ptr("minLength")) {
740     validators_.emplace_back(
741         make_unique<SizeValidator<std::less_equal<int64_t>>>(
742             *p, dynamic::Type::STRING));
743   }
744   if (const auto* p = schema.get_ptr("pattern")) {
745     validators_.emplace_back(make_unique<StringPatternValidator>(*p));
746   }
747
748   // Array validators
749   const auto* items = schema.get_ptr("items");
750   const auto* additionalItems = schema.get_ptr("additionalItems");
751   if (items || additionalItems) {
752     validators_.emplace_back(
753         make_unique<ArrayItemsValidator>(context, items, additionalItems));
754   }
755   if (const auto* p = schema.get_ptr("maxItems")) {
756     validators_.emplace_back(
757         make_unique<SizeValidator<std::greater_equal<int64_t>>>(
758             *p, dynamic::Type::ARRAY));
759   }
760   if (const auto* p = schema.get_ptr("minItems")) {
761     validators_.emplace_back(
762         make_unique<SizeValidator<std::less_equal<int64_t>>>(
763             *p, dynamic::Type::ARRAY));
764   }
765   if (const auto* p = schema.get_ptr("uniqueItems")) {
766     validators_.emplace_back(make_unique<ArrayUniqueValidator>(*p));
767   }
768
769   // Object validators
770   const auto* properties = schema.get_ptr("properties");
771   const auto* patternProperties = schema.get_ptr("patternProperties");
772   const auto* additionalProperties = schema.get_ptr("additionalProperties");
773   if (properties || patternProperties || additionalProperties) {
774     validators_.emplace_back(make_unique<PropertiesValidator>(
775         context, properties, patternProperties, additionalProperties));
776   }
777   if (const auto* p = schema.get_ptr("maxProperties")) {
778     validators_.emplace_back(
779         make_unique<SizeValidator<std::greater_equal<int64_t>>>(
780             *p, dynamic::Type::OBJECT));
781   }
782   if (const auto* p = schema.get_ptr("minProperties")) {
783     validators_.emplace_back(
784         make_unique<SizeValidator<std::less_equal<int64_t>>>(
785             *p, dynamic::Type::OBJECT));
786   }
787   if (const auto* p = schema.get_ptr("required")) {
788     validators_.emplace_back(make_unique<RequiredValidator>(*p));
789   }
790
791   // Misc validators
792   if (const auto* p = schema.get_ptr("dependencies")) {
793     validators_.emplace_back(make_unique<DependencyValidator>(context, *p));
794   }
795   if (const auto* p = schema.get_ptr("enum")) {
796     validators_.emplace_back(make_unique<EnumValidator>(*p));
797   }
798   if (const auto* p = schema.get_ptr("type")) {
799     validators_.emplace_back(make_unique<TypeValidator>(*p));
800   }
801   if (const auto* p = schema.get_ptr("allOf")) {
802     validators_.emplace_back(make_unique<AllOfValidator>(context, *p));
803   }
804   if (const auto* p = schema.get_ptr("anyOf")) {
805     validators_.emplace_back(make_unique<AnyOfValidator>(
806         context, *p, AnyOfValidator::Type::ONE_OR_MORE));
807   }
808   if (const auto* p = schema.get_ptr("oneOf")) {
809     validators_.emplace_back(make_unique<AnyOfValidator>(
810         context, *p, AnyOfValidator::Type::EXACTLY_ONE));
811   }
812   if (const auto* p = schema.get_ptr("not")) {
813     validators_.emplace_back(make_unique<NotValidator>(context, *p));
814   }
815 }
816
817 void SchemaValidator::validate(const dynamic& value) const {
818   ValidationContext vc;
819   if (auto se = validate(vc, value)) {
820     throw * se;
821   }
822 }
823
824 exception_wrapper SchemaValidator::try_validate(const dynamic& value) const
825     noexcept {
826   try {
827     ValidationContext vc;
828     if (auto se = validate(vc, value)) {
829       return make_exception_wrapper<SchemaError>(*se);
830     }
831   } catch (const std::exception& e) {
832     return exception_wrapper(std::current_exception(), e);
833   } catch (...) {
834     return exception_wrapper(std::current_exception());
835   }
836   return exception_wrapper();
837 }
838
839 Optional<SchemaError> SchemaValidator::validate(ValidationContext& vc,
840                                                 const dynamic& value) const {
841   for (const auto& validator : validators_) {
842     if (auto se = vc.validate(validator.get(), value)) {
843       return se;
844     }
845   }
846   return none;
847 }
848
849 /**
850  * Metaschema, i.e. schema for schema.
851  * Inlined from the $schema url
852  */
853 const char* metaschemaJson =
854     "\
855 { \
856     \"id\": \"http://json-schema.org/draft-04/schema#\", \
857     \"$schema\": \"http://json-schema.org/draft-04/schema#\", \
858     \"description\": \"Core schema meta-schema\", \
859     \"definitions\": { \
860         \"schemaArray\": { \
861             \"type\": \"array\", \
862             \"minItems\": 1, \
863             \"items\": { \"$ref\": \"#\" } \
864         }, \
865         \"positiveInteger\": { \
866             \"type\": \"integer\", \
867             \"minimum\": 0 \
868         }, \
869         \"positiveIntegerDefault0\": { \
870             \"allOf\": [ \
871           { \"$ref\": \"#/definitions/positiveInteger\" }, { \"default\": 0 } ]\
872         }, \
873         \"simpleTypes\": { \
874             \"enum\": [ \"array\", \"boolean\", \"integer\", \
875                         \"null\", \"number\", \"object\", \"string\" ] \
876         }, \
877         \"stringArray\": { \
878             \"type\": \"array\", \
879             \"items\": { \"type\": \"string\" }, \
880             \"minItems\": 1, \
881             \"uniqueItems\": true \
882         } \
883     }, \
884     \"type\": \"object\", \
885     \"properties\": { \
886         \"id\": { \
887             \"type\": \"string\", \
888             \"format\": \"uri\" \
889         }, \
890         \"$schema\": { \
891             \"type\": \"string\", \
892             \"format\": \"uri\" \
893         }, \
894         \"title\": { \
895             \"type\": \"string\" \
896         }, \
897         \"description\": { \
898             \"type\": \"string\" \
899         }, \
900         \"default\": {}, \
901         \"multipleOf\": { \
902             \"type\": \"number\", \
903             \"minimum\": 0, \
904             \"exclusiveMinimum\": true \
905         }, \
906         \"maximum\": { \
907             \"type\": \"number\" \
908         }, \
909         \"exclusiveMaximum\": { \
910             \"type\": \"boolean\", \
911             \"default\": false \
912         }, \
913         \"minimum\": { \
914             \"type\": \"number\" \
915         }, \
916         \"exclusiveMinimum\": { \
917             \"type\": \"boolean\", \
918             \"default\": false \
919         }, \
920         \"maxLength\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
921         \"minLength\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" },\
922         \"pattern\": { \
923             \"type\": \"string\", \
924             \"format\": \"regex\" \
925         }, \
926         \"additionalItems\": { \
927             \"anyOf\": [ \
928                 { \"type\": \"boolean\" }, \
929                 { \"$ref\": \"#\" } \
930             ], \
931             \"default\": {} \
932         }, \
933         \"items\": { \
934             \"anyOf\": [ \
935                 { \"$ref\": \"#\" }, \
936                 { \"$ref\": \"#/definitions/schemaArray\" } \
937             ], \
938             \"default\": {} \
939         }, \
940         \"maxItems\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
941         \"minItems\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
942         \"uniqueItems\": { \
943             \"type\": \"boolean\", \
944             \"default\": false \
945         }, \
946         \"maxProperties\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
947         \"minProperties\": { \
948         \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
949         \"required\": { \"$ref\": \"#/definitions/stringArray\" }, \
950         \"additionalProperties\": { \
951             \"anyOf\": [ \
952                 { \"type\": \"boolean\" }, \
953                 { \"$ref\": \"#\" } \
954             ], \
955             \"default\": {} \
956         }, \
957         \"definitions\": { \
958             \"type\": \"object\", \
959             \"additionalProperties\": { \"$ref\": \"#\" }, \
960             \"default\": {} \
961         }, \
962         \"properties\": { \
963             \"type\": \"object\", \
964             \"additionalProperties\": { \"$ref\": \"#\" }, \
965             \"default\": {} \
966         }, \
967         \"patternProperties\": { \
968             \"type\": \"object\", \
969             \"additionalProperties\": { \"$ref\": \"#\" }, \
970             \"default\": {} \
971         }, \
972         \"dependencies\": { \
973             \"type\": \"object\", \
974             \"additionalProperties\": { \
975                 \"anyOf\": [ \
976                     { \"$ref\": \"#\" }, \
977                     { \"$ref\": \"#/definitions/stringArray\" } \
978                 ] \
979             } \
980         }, \
981         \"enum\": { \
982             \"type\": \"array\", \
983             \"minItems\": 1, \
984             \"uniqueItems\": true \
985         }, \
986         \"type\": { \
987             \"anyOf\": [ \
988                 { \"$ref\": \"#/definitions/simpleTypes\" }, \
989                 { \
990                     \"type\": \"array\", \
991                     \"items\": { \"$ref\": \"#/definitions/simpleTypes\" }, \
992                     \"minItems\": 1, \
993                     \"uniqueItems\": true \
994                 } \
995             ] \
996         }, \
997         \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
998         \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
999         \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
1000         \"not\": { \"$ref\": \"#\" } \
1001     }, \
1002     \"dependencies\": { \
1003         \"exclusiveMaximum\": [ \"maximum\" ], \
1004         \"exclusiveMinimum\": [ \"minimum\" ] \
1005     }, \
1006     \"default\": {} \
1007 }";
1008
1009 folly::Singleton<Validator> schemaValidator([]() {
1010   return makeValidator(parseJson(metaschemaJson)).release();
1011 });
1012 }
1013
1014 Validator::~Validator() = default;
1015
1016 std::unique_ptr<Validator> makeValidator(const dynamic& schema) {
1017   auto v = make_unique<SchemaValidator>();
1018   SchemaValidatorContext context(schema);
1019   context.refs["#"] = v.get();
1020   v->loadSchema(context, schema);
1021   return std::move(v);
1022 }
1023
1024 std::shared_ptr<Validator> makeSchemaValidator() {
1025   return schemaValidator.try_get();
1026 }
1027 }
1028 }