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