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 | |
29 | namespace folly { |
30 | namespace jsonschema { |
31 | |
32 | namespace { |
33 | |
34 | /** |
35 | * We throw this exception when schema validation fails. |
36 | */ |
37 | struct 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 | |
59 | template <class... Args> |
60 | Optional<SchemaError> makeError(Args&&... args) { |
61 | return Optional<SchemaError>(SchemaError(std::forward<Args>(args)...)); |
62 | } |
63 | |
64 | struct ValidationContext; |
65 | |
66 | struct 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 | */ |
82 | struct 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 | */ |
100 | struct 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 | */ |
110 | struct 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 | |
136 | struct 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 | |
158 | struct 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 | |
211 | template <class Comparison> |
212 | struct 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 | |
237 | struct 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 | |
257 | struct 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 | |
281 | struct 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 | |
346 | struct 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 | |
373 | struct 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 | |
459 | struct 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 | |
513 | struct 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 | |
531 | struct 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 | |
584 | struct 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 | |
606 | struct 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 | |
642 | struct 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 | |
652 | struct 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 | |
666 | void 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 | |
829 | void SchemaValidator::validate(const dynamic& value) const { |
830 | ValidationContext vc; |
831 | if (auto se = validate(vc, value)) { |
832 | throw *se; |
833 | } |
834 | } |
835 | |
836 | exception_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 | |
851 | Optional<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 | */ |
866 | const 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 | |
1022 | folly::Singleton<Validator> schemaValidator([]() { |
1023 | return makeValidator(parseJson(metaschemaJson)).release(); |
1024 | }); |
1025 | } // namespace |
1026 | |
1027 | Validator::~Validator() = default; |
1028 | |
1029 | std::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 | |
1037 | std::shared_ptr<Validator> makeSchemaValidator() { |
1038 | return schemaValidator.try_get(); |
1039 | } |
1040 | } // namespace jsonschema |
1041 | } // namespace folly |
1042 | |