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