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