123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041 |
- /*
- * Copyright 2015-present Facebook, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- #include <folly/experimental/JSONSchema.h>
- #include <boost/algorithm/string/replace.hpp>
- #include <boost/regex.hpp>
- #include <folly/CPortability.h>
- #include <folly/Conv.h>
- #include <folly/Memory.h>
- #include <folly/Optional.h>
- #include <folly/Singleton.h>
- #include <folly/String.h>
- #include <folly/json.h>
- namespace folly {
- namespace jsonschema {
- namespace {
- /**
- * We throw this exception when schema validation fails.
- */
- struct FOLLY_EXPORT SchemaError : std::runtime_error {
- SchemaError(SchemaError&&) = default;
- SchemaError(const SchemaError&) = default;
- SchemaError(folly::StringPiece expected, const dynamic& value)
- : std::runtime_error(to<std::string>(
- "Expected to get ",
- expected,
- " for value ",
- toJson(value))) {}
- SchemaError(
- folly::StringPiece expected,
- const dynamic& schema,
- const dynamic& value)
- : std::runtime_error(to<std::string>(
- "Expected to get ",
- expected,
- toJson(schema),
- " for value ",
- toJson(value))) {}
- };
- template <class... Args>
- Optional<SchemaError> makeError(Args&&... args) {
- return Optional<SchemaError>(SchemaError(std::forward<Args>(args)...));
- }
- struct ValidationContext;
- struct IValidator {
- virtual ~IValidator() = default;
- private:
- friend struct ValidationContext;
- virtual Optional<SchemaError> validate(
- ValidationContext&,
- const dynamic& value) const = 0;
- };
- /**
- * This is a 'context' used only when executing the validators to validate some
- * json. It keeps track of which validators have been executed on which json so
- * we can detect infinite recursion.
- */
- struct ValidationContext {
- Optional<SchemaError> validate(IValidator* validator, const dynamic& value) {
- auto ret = seen.insert(std::make_pair(validator, &value));
- if (!ret.second) {
- throw std::runtime_error("Infinite recursion detected");
- }
- return validator->validate(*this, value);
- }
- private:
- std::unordered_set<std::pair<const IValidator*, const dynamic*>> seen;
- };
- /**
- * This is a 'context' used only when building the schema validators from a
- * piece of json. It stores the original schema and the set of refs, so that we
- * can have parts of the schema refer to other parts.
- */
- struct SchemaValidatorContext final {
- explicit SchemaValidatorContext(const dynamic& s) : schema(s) {}
- const dynamic& schema;
- std::unordered_map<std::string, IValidator*> refs;
- };
- /**
- * Root validator for a schema.
- */
- struct SchemaValidator final : IValidator, public Validator {
- SchemaValidator() = default;
- void loadSchema(SchemaValidatorContext& context, const dynamic& schema);
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override;
- // Validator interface
- void validate(const dynamic& value) const override;
- exception_wrapper try_validate(const dynamic& value) const noexcept override;
- static std::unique_ptr<SchemaValidator> make(
- SchemaValidatorContext& context,
- const dynamic& schema) {
- // We break apart the constructor and actually loading the schema so that
- // we can handle the case where a schema refers to itself, e.g. via
- // "$ref": "#".
- auto v = std::make_unique<SchemaValidator>();
- v->loadSchema(context, schema);
- return v;
- }
- private:
- std::vector<std::unique_ptr<IValidator>> validators_;
- };
- struct MultipleOfValidator final : IValidator {
- explicit MultipleOfValidator(dynamic schema) : schema_(std::move(schema)) {}
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- if (!schema_.isNumber() || !value.isNumber()) {
- return none;
- }
- if (schema_.isDouble() || value.isDouble()) {
- const auto rem = std::remainder(value.asDouble(), schema_.asDouble());
- if (std::abs(rem) > std::numeric_limits<double>::epsilon()) {
- return makeError("a multiple of ", schema_, value);
- }
- } else { // both ints
- if ((value.getInt() % schema_.getInt()) != 0) {
- return makeError("a multiple of ", schema_, value);
- }
- }
- return none;
- }
- dynamic schema_;
- };
- struct ComparisonValidator final : IValidator {
- enum class Type { MIN, MAX };
- ComparisonValidator(dynamic schema, const dynamic* exclusive, Type type)
- : schema_(std::move(schema)), exclusive_(false), type_(type) {
- if (exclusive && exclusive->isBool()) {
- exclusive_ = exclusive->getBool();
- }
- }
- template <typename Numeric>
- Optional<SchemaError>
- validateHelper(const dynamic& value, Numeric s, Numeric v) const {
- if (type_ == Type::MIN) {
- if (exclusive_) {
- if (v <= s) {
- return makeError("greater than ", schema_, value);
- }
- } else {
- if (v < s) {
- return makeError("greater than or equal to ", schema_, value);
- }
- }
- } else if (type_ == Type::MAX) {
- if (exclusive_) {
- if (v >= s) {
- return makeError("less than ", schema_, value);
- }
- } else {
- if (v > s) {
- return makeError("less than or equal to ", schema_, value);
- }
- }
- }
- return none;
- }
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- if (!schema_.isNumber() || !value.isNumber()) {
- return none;
- }
- if (schema_.isDouble() || value.isDouble()) {
- return validateHelper(value, schema_.asDouble(), value.asDouble());
- } else { // both ints
- return validateHelper(value, schema_.asInt(), value.asInt());
- }
- }
- dynamic schema_;
- bool exclusive_;
- Type type_;
- };
- template <class Comparison>
- struct SizeValidator final : IValidator {
- explicit SizeValidator(const dynamic& schema, dynamic::Type type)
- : length_(-1), type_(type) {
- if (schema.isInt()) {
- length_ = schema.getInt();
- }
- }
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- if (length_ < 0) {
- return none;
- }
- if (value.type() != type_) {
- return none;
- }
- if (!Comparison()(length_, int64_t(value.size()))) {
- return makeError("different length string/array/object", value);
- }
- return none;
- }
- int64_t length_;
- dynamic::Type type_;
- };
- struct StringPatternValidator final : IValidator {
- explicit StringPatternValidator(const dynamic& schema) {
- if (schema.isString()) {
- regex_ = boost::regex(schema.getString());
- }
- }
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- if (!value.isString() || regex_.empty()) {
- return none;
- }
- if (!boost::regex_search(value.getString(), regex_)) {
- return makeError("string matching regex", value);
- }
- return none;
- }
- boost::regex regex_;
- };
- struct ArrayUniqueValidator final : IValidator {
- explicit ArrayUniqueValidator(const dynamic& schema) : unique_(false) {
- if (schema.isBool()) {
- unique_ = schema.getBool();
- }
- }
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- if (!unique_ || !value.isArray()) {
- return none;
- }
- for (const auto& i : value) {
- for (const auto& j : value) {
- if (&i != &j && i == j) {
- return makeError("unique items in array", value);
- }
- }
- }
- return none;
- }
- bool unique_;
- };
- struct ArrayItemsValidator final : IValidator {
- ArrayItemsValidator(
- SchemaValidatorContext& context,
- const dynamic* items,
- const dynamic* additionalItems)
- : allowAdditionalItems_(true) {
- if (items && items->isObject()) {
- itemsValidator_ = SchemaValidator::make(context, *items);
- return; // Additional items is ignored
- } else if (items && items->isArray()) {
- for (const auto& item : *items) {
- itemsValidators_.emplace_back(SchemaValidator::make(context, item));
- }
- } else {
- // If items isn't present or is invalid, it defaults to an empty schema.
- itemsValidator_ = SchemaValidator::make(context, dynamic::object);
- }
- if (additionalItems) {
- if (additionalItems->isBool()) {
- allowAdditionalItems_ = additionalItems->getBool();
- } else if (additionalItems->isObject()) {
- additionalItemsValidator_ =
- SchemaValidator::make(context, *additionalItems);
- }
- }
- }
- Optional<SchemaError> validate(ValidationContext& vc, const dynamic& value)
- const override {
- if (!value.isArray()) {
- return none;
- }
- if (itemsValidator_) {
- for (const auto& v : value) {
- if (auto se = vc.validate(itemsValidator_.get(), v)) {
- return se;
- }
- }
- return none;
- }
- size_t pos = 0;
- for (; pos < value.size() && pos < itemsValidators_.size(); ++pos) {
- if (auto se = vc.validate(itemsValidators_[pos].get(), value[pos])) {
- return se;
- }
- }
- if (!allowAdditionalItems_ && pos < value.size()) {
- return makeError("no more additional items", value);
- }
- if (additionalItemsValidator_) {
- for (; pos < value.size(); ++pos) {
- if (auto se =
- vc.validate(additionalItemsValidator_.get(), value[pos])) {
- return se;
- }
- }
- }
- return none;
- }
- std::unique_ptr<IValidator> itemsValidator_;
- std::vector<std::unique_ptr<IValidator>> itemsValidators_;
- std::unique_ptr<IValidator> additionalItemsValidator_;
- bool allowAdditionalItems_;
- };
- struct RequiredValidator final : IValidator {
- explicit RequiredValidator(const dynamic& schema) {
- if (schema.isArray()) {
- for (const auto& item : schema) {
- if (item.isString()) {
- properties_.emplace_back(item.getString());
- }
- }
- }
- }
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- if (value.isObject()) {
- for (const auto& prop : properties_) {
- if (!value.get_ptr(prop)) {
- return makeError("property ", prop, value);
- }
- }
- }
- return none;
- }
- private:
- std::vector<std::string> properties_;
- };
- struct PropertiesValidator final : IValidator {
- PropertiesValidator(
- SchemaValidatorContext& context,
- const dynamic* properties,
- const dynamic* patternProperties,
- const dynamic* additionalProperties)
- : allowAdditionalProperties_(true) {
- if (properties && properties->isObject()) {
- for (const auto& pair : properties->items()) {
- if (pair.first.isString()) {
- propertyValidators_[pair.first.getString()] =
- SchemaValidator::make(context, pair.second);
- }
- }
- }
- if (patternProperties && patternProperties->isObject()) {
- for (const auto& pair : patternProperties->items()) {
- if (pair.first.isString()) {
- patternPropertyValidators_.emplace_back(
- boost::regex(pair.first.getString()),
- SchemaValidator::make(context, pair.second));
- }
- }
- }
- if (additionalProperties) {
- if (additionalProperties->isBool()) {
- allowAdditionalProperties_ = additionalProperties->getBool();
- } else if (additionalProperties->isObject()) {
- additionalPropertyValidator_ =
- SchemaValidator::make(context, *additionalProperties);
- }
- }
- }
- Optional<SchemaError> validate(ValidationContext& vc, const dynamic& value)
- const override {
- if (!value.isObject()) {
- return none;
- }
- for (const auto& pair : value.items()) {
- if (!pair.first.isString()) {
- continue;
- }
- const std::string& key = pair.first.getString();
- auto it = propertyValidators_.find(key);
- bool matched = false;
- if (it != propertyValidators_.end()) {
- if (auto se = vc.validate(it->second.get(), pair.second)) {
- return se;
- }
- matched = true;
- }
- const std::string& strkey = key;
- for (const auto& ppv : patternPropertyValidators_) {
- if (boost::regex_search(strkey, ppv.first)) {
- if (auto se = vc.validate(ppv.second.get(), pair.second)) {
- return se;
- }
- matched = true;
- }
- }
- if (matched) {
- continue;
- }
- if (!allowAdditionalProperties_) {
- return makeError("no more additional properties", value);
- }
- if (additionalPropertyValidator_) {
- if (auto se =
- vc.validate(additionalPropertyValidator_.get(), pair.second)) {
- return se;
- }
- }
- }
- return none;
- }
- std::unordered_map<std::string, std::unique_ptr<IValidator>>
- propertyValidators_;
- std::vector<std::pair<boost::regex, std::unique_ptr<IValidator>>>
- patternPropertyValidators_;
- std::unique_ptr<IValidator> additionalPropertyValidator_;
- bool allowAdditionalProperties_;
- };
- struct DependencyValidator final : IValidator {
- DependencyValidator(SchemaValidatorContext& context, const dynamic& schema) {
- if (!schema.isObject()) {
- return;
- }
- for (const auto& pair : schema.items()) {
- if (!pair.first.isString()) {
- continue;
- }
- if (pair.second.isArray()) {
- auto p = make_pair(pair.first.getString(), std::vector<std::string>());
- for (const auto& item : pair.second) {
- if (item.isString()) {
- p.second.push_back(item.getString());
- }
- }
- propertyDep_.emplace_back(std::move(p));
- }
- if (pair.second.isObject()) {
- schemaDep_.emplace_back(
- pair.first.getString(),
- SchemaValidator::make(context, pair.second));
- }
- }
- }
- Optional<SchemaError> validate(ValidationContext& vc, const dynamic& value)
- const override {
- if (!value.isObject()) {
- return none;
- }
- for (const auto& pair : propertyDep_) {
- if (value.count(pair.first)) {
- for (const auto& prop : pair.second) {
- if (!value.count(prop)) {
- return makeError("property ", prop, value);
- }
- }
- }
- }
- for (const auto& pair : schemaDep_) {
- if (value.count(pair.first)) {
- if (auto se = vc.validate(pair.second.get(), value)) {
- return se;
- }
- }
- }
- return none;
- }
- std::vector<std::pair<std::string, std::vector<std::string>>> propertyDep_;
- std::vector<std::pair<std::string, std::unique_ptr<IValidator>>> schemaDep_;
- };
- struct EnumValidator final : IValidator {
- explicit EnumValidator(dynamic schema) : schema_(std::move(schema)) {}
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- if (!schema_.isArray()) {
- return none;
- }
- for (const auto& item : schema_) {
- if (value == item) {
- return none;
- }
- }
- return makeError("one of enum values: ", schema_, value);
- }
- dynamic schema_;
- };
- struct TypeValidator final : IValidator {
- explicit TypeValidator(const dynamic& schema) {
- if (schema.isString()) {
- addType(schema.stringPiece());
- } else if (schema.isArray()) {
- for (const auto& item : schema) {
- if (item.isString()) {
- addType(item.stringPiece());
- }
- }
- }
- }
- Optional<SchemaError> validate(ValidationContext&, const dynamic& value)
- const override {
- auto it =
- std::find(allowedTypes_.begin(), allowedTypes_.end(), value.type());
- if (it == allowedTypes_.end()) {
- return makeError("a value of type ", typeStr_, value);
- }
- return none;
- }
- private:
- std::vector<dynamic::Type> allowedTypes_;
- std::string typeStr_; // for errors
- void addType(StringPiece value) {
- if (value == "array") {
- allowedTypes_.push_back(dynamic::Type::ARRAY);
- } else if (value == "boolean") {
- allowedTypes_.push_back(dynamic::Type::BOOL);
- } else if (value == "integer") {
- allowedTypes_.push_back(dynamic::Type::INT64);
- } else if (value == "number") {
- allowedTypes_.push_back(dynamic::Type::INT64);
- allowedTypes_.push_back(dynamic::Type::DOUBLE);
- } else if (value == "null") {
- allowedTypes_.push_back(dynamic::Type::NULLT);
- } else if (value == "object") {
- allowedTypes_.push_back(dynamic::Type::OBJECT);
- } else if (value == "string") {
- allowedTypes_.push_back(dynamic::Type::STRING);
- } else {
- return;
- }
- if (!typeStr_.empty()) {
- typeStr_ += ", ";
- }
- typeStr_ += value.str();
- }
- };
- struct AllOfValidator final : IValidator {
- AllOfValidator(SchemaValidatorContext& context, const dynamic& schema) {
- if (schema.isArray()) {
- for (const auto& item : schema) {
- validators_.emplace_back(SchemaValidator::make(context, item));
- }
- }
- }
- Optional<SchemaError> validate(ValidationContext& vc, const dynamic& value)
- const override {
- for (const auto& val : validators_) {
- if (auto se = vc.validate(val.get(), value)) {
- return se;
- }
- }
- return none;
- }
- std::vector<std::unique_ptr<IValidator>> validators_;
- };
- struct AnyOfValidator final : IValidator {
- enum class Type { EXACTLY_ONE, ONE_OR_MORE };
- AnyOfValidator(
- SchemaValidatorContext& context,
- const dynamic& schema,
- Type type)
- : type_(type) {
- if (schema.isArray()) {
- for (const auto& item : schema) {
- validators_.emplace_back(SchemaValidator::make(context, item));
- }
- }
- }
- Optional<SchemaError> validate(ValidationContext& vc, const dynamic& value)
- const override {
- std::vector<SchemaError> errors;
- for (const auto& val : validators_) {
- if (auto se = vc.validate(val.get(), value)) {
- errors.emplace_back(*se);
- }
- }
- const auto success = validators_.size() - errors.size();
- if (success == 0) {
- return makeError("at least one valid schema", value);
- } else if (success > 1 && type_ == Type::EXACTLY_ONE) {
- return makeError("exactly one valid schema", value);
- }
- return none;
- }
- Type type_;
- std::vector<std::unique_ptr<IValidator>> validators_;
- };
- struct RefValidator final : IValidator {
- explicit RefValidator(IValidator* validator) : validator_(validator) {}
- Optional<SchemaError> validate(ValidationContext& vc, const dynamic& value)
- const override {
- return vc.validate(validator_, value);
- }
- IValidator* validator_;
- };
- struct NotValidator final : IValidator {
- NotValidator(SchemaValidatorContext& context, const dynamic& schema)
- : validator_(SchemaValidator::make(context, schema)) {}
- Optional<SchemaError> validate(ValidationContext& vc, const dynamic& value)
- const override {
- if (vc.validate(validator_.get(), value)) {
- return none;
- }
- return makeError("Expected schema validation to fail", value);
- }
- std::unique_ptr<IValidator> validator_;
- };
- void SchemaValidator::loadSchema(
- SchemaValidatorContext& context,
- const dynamic& schema) {
- if (!schema.isObject() || schema.empty()) {
- return;
- }
- // Check for $ref, if we have one we won't apply anything else. Refs are
- // pointers to other parts of the json, e.g. #/foo/bar points to the schema
- // located at root["foo"]["bar"].
- if (const auto* p = schema.get_ptr("$ref")) {
- // We only support absolute refs, i.e. those starting with '#'
- if (p->isString() && p->stringPiece()[0] == '#') {
- auto it = context.refs.find(p->getString());
- if (it != context.refs.end()) {
- validators_.emplace_back(std::make_unique<RefValidator>(it->second));
- return;
- }
- // This is a ref, but we haven't loaded it yet. Find where it is based on
- // the root schema.
- std::vector<std::string> parts;
- split("/", p->stringPiece(), parts);
- const auto* s = &context.schema; // First part is '#'
- for (size_t i = 1; s && i < parts.size(); ++i) {
- // Per the standard, we must replace ~1 with / and then ~0 with ~
- boost::replace_all(parts[i], "~1", "/");
- boost::replace_all(parts[i], "~0", "~");
- if (s->isObject()) {
- s = s->get_ptr(parts[i]);
- continue;
- }
- if (s->isArray()) {
- try {
- const size_t pos = to<size_t>(parts[i]);
- if (pos < s->size()) {
- s = s->get_ptr(pos);
- continue;
- }
- } catch (const std::range_error&) {
- // ignore
- }
- }
- break;
- }
- // If you have a self-recursive reference, this avoids getting into an
- // infinite recursion, where we try to load a schema that just references
- // itself, and then we try to load it again, and so on.
- // Instead we load a pointer to the schema into the refs, so that any
- // future references to it will just see that pointer and won't try to
- // keep parsing further.
- if (s) {
- auto v = std::make_unique<SchemaValidator>();
- context.refs[p->getString()] = v.get();
- v->loadSchema(context, *s);
- validators_.emplace_back(std::move(v));
- return;
- }
- }
- }
- // Numeric validators
- if (const auto* p = schema.get_ptr("multipleOf")) {
- validators_.emplace_back(std::make_unique<MultipleOfValidator>(*p));
- }
- if (const auto* p = schema.get_ptr("maximum")) {
- validators_.emplace_back(std::make_unique<ComparisonValidator>(
- *p,
- schema.get_ptr("exclusiveMaximum"),
- ComparisonValidator::Type::MAX));
- }
- if (const auto* p = schema.get_ptr("minimum")) {
- validators_.emplace_back(std::make_unique<ComparisonValidator>(
- *p,
- schema.get_ptr("exclusiveMinimum"),
- ComparisonValidator::Type::MIN));
- }
- // String validators
- if (const auto* p = schema.get_ptr("maxLength")) {
- validators_.emplace_back(
- std::make_unique<SizeValidator<std::greater_equal<int64_t>>>(
- *p, dynamic::Type::STRING));
- }
- if (const auto* p = schema.get_ptr("minLength")) {
- validators_.emplace_back(
- std::make_unique<SizeValidator<std::less_equal<int64_t>>>(
- *p, dynamic::Type::STRING));
- }
- if (const auto* p = schema.get_ptr("pattern")) {
- validators_.emplace_back(std::make_unique<StringPatternValidator>(*p));
- }
- // Array validators
- const auto* items = schema.get_ptr("items");
- const auto* additionalItems = schema.get_ptr("additionalItems");
- if (items || additionalItems) {
- validators_.emplace_back(
- std::make_unique<ArrayItemsValidator>(context, items, additionalItems));
- }
- if (const auto* p = schema.get_ptr("maxItems")) {
- validators_.emplace_back(
- std::make_unique<SizeValidator<std::greater_equal<int64_t>>>(
- *p, dynamic::Type::ARRAY));
- }
- if (const auto* p = schema.get_ptr("minItems")) {
- validators_.emplace_back(
- std::make_unique<SizeValidator<std::less_equal<int64_t>>>(
- *p, dynamic::Type::ARRAY));
- }
- if (const auto* p = schema.get_ptr("uniqueItems")) {
- validators_.emplace_back(std::make_unique<ArrayUniqueValidator>(*p));
- }
- // Object validators
- const auto* properties = schema.get_ptr("properties");
- const auto* patternProperties = schema.get_ptr("patternProperties");
- const auto* additionalProperties = schema.get_ptr("additionalProperties");
- if (properties || patternProperties || additionalProperties) {
- validators_.emplace_back(std::make_unique<PropertiesValidator>(
- context, properties, patternProperties, additionalProperties));
- }
- if (const auto* p = schema.get_ptr("maxProperties")) {
- validators_.emplace_back(
- std::make_unique<SizeValidator<std::greater_equal<int64_t>>>(
- *p, dynamic::Type::OBJECT));
- }
- if (const auto* p = schema.get_ptr("minProperties")) {
- validators_.emplace_back(
- std::make_unique<SizeValidator<std::less_equal<int64_t>>>(
- *p, dynamic::Type::OBJECT));
- }
- if (const auto* p = schema.get_ptr("required")) {
- validators_.emplace_back(std::make_unique<RequiredValidator>(*p));
- }
- // Misc validators
- if (const auto* p = schema.get_ptr("dependencies")) {
- validators_.emplace_back(
- std::make_unique<DependencyValidator>(context, *p));
- }
- if (const auto* p = schema.get_ptr("enum")) {
- validators_.emplace_back(std::make_unique<EnumValidator>(*p));
- }
- if (const auto* p = schema.get_ptr("type")) {
- validators_.emplace_back(std::make_unique<TypeValidator>(*p));
- }
- if (const auto* p = schema.get_ptr("allOf")) {
- validators_.emplace_back(std::make_unique<AllOfValidator>(context, *p));
- }
- if (const auto* p = schema.get_ptr("anyOf")) {
- validators_.emplace_back(std::make_unique<AnyOfValidator>(
- context, *p, AnyOfValidator::Type::ONE_OR_MORE));
- }
- if (const auto* p = schema.get_ptr("oneOf")) {
- validators_.emplace_back(std::make_unique<AnyOfValidator>(
- context, *p, AnyOfValidator::Type::EXACTLY_ONE));
- }
- if (const auto* p = schema.get_ptr("not")) {
- validators_.emplace_back(std::make_unique<NotValidator>(context, *p));
- }
- }
- void SchemaValidator::validate(const dynamic& value) const {
- ValidationContext vc;
- if (auto se = validate(vc, value)) {
- throw *se;
- }
- }
- exception_wrapper SchemaValidator::try_validate(const dynamic& value) const
- noexcept {
- try {
- ValidationContext vc;
- if (auto se = validate(vc, value)) {
- return make_exception_wrapper<SchemaError>(*se);
- }
- } catch (const std::exception& e) {
- return exception_wrapper(std::current_exception(), e);
- } catch (...) {
- return exception_wrapper(std::current_exception());
- }
- return exception_wrapper();
- }
- Optional<SchemaError> SchemaValidator::validate(
- ValidationContext& vc,
- const dynamic& value) const {
- for (const auto& validator : validators_) {
- if (auto se = vc.validate(validator.get(), value)) {
- return se;
- }
- }
- return none;
- }
- /**
- * Metaschema, i.e. schema for schema.
- * Inlined from the $schema url
- */
- const char* metaschemaJson =
- "\
- { \
- \"id\": \"http://json-schema.org/draft-04/schema#\", \
- \"$schema\": \"http://json-schema.org/draft-04/schema#\", \
- \"description\": \"Core schema meta-schema\", \
- \"definitions\": { \
- \"schemaArray\": { \
- \"type\": \"array\", \
- \"minItems\": 1, \
- \"items\": { \"$ref\": \"#\" } \
- }, \
- \"positiveInteger\": { \
- \"type\": \"integer\", \
- \"minimum\": 0 \
- }, \
- \"positiveIntegerDefault0\": { \
- \"allOf\": [ \
- { \"$ref\": \"#/definitions/positiveInteger\" }, { \"default\": 0 } ]\
- }, \
- \"simpleTypes\": { \
- \"enum\": [ \"array\", \"boolean\", \"integer\", \
- \"null\", \"number\", \"object\", \"string\" ] \
- }, \
- \"stringArray\": { \
- \"type\": \"array\", \
- \"items\": { \"type\": \"string\" }, \
- \"minItems\": 1, \
- \"uniqueItems\": true \
- } \
- }, \
- \"type\": \"object\", \
- \"properties\": { \
- \"id\": { \
- \"type\": \"string\", \
- \"format\": \"uri\" \
- }, \
- \"$schema\": { \
- \"type\": \"string\", \
- \"format\": \"uri\" \
- }, \
- \"title\": { \
- \"type\": \"string\" \
- }, \
- \"description\": { \
- \"type\": \"string\" \
- }, \
- \"default\": {}, \
- \"multipleOf\": { \
- \"type\": \"number\", \
- \"minimum\": 0, \
- \"exclusiveMinimum\": true \
- }, \
- \"maximum\": { \
- \"type\": \"number\" \
- }, \
- \"exclusiveMaximum\": { \
- \"type\": \"boolean\", \
- \"default\": false \
- }, \
- \"minimum\": { \
- \"type\": \"number\" \
- }, \
- \"exclusiveMinimum\": { \
- \"type\": \"boolean\", \
- \"default\": false \
- }, \
- \"maxLength\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
- \"minLength\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" },\
- \"pattern\": { \
- \"type\": \"string\", \
- \"format\": \"regex\" \
- }, \
- \"additionalItems\": { \
- \"anyOf\": [ \
- { \"type\": \"boolean\" }, \
- { \"$ref\": \"#\" } \
- ], \
- \"default\": {} \
- }, \
- \"items\": { \
- \"anyOf\": [ \
- { \"$ref\": \"#\" }, \
- { \"$ref\": \"#/definitions/schemaArray\" } \
- ], \
- \"default\": {} \
- }, \
- \"maxItems\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
- \"minItems\": { \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
- \"uniqueItems\": { \
- \"type\": \"boolean\", \
- \"default\": false \
- }, \
- \"maxProperties\": { \"$ref\": \"#/definitions/positiveInteger\" }, \
- \"minProperties\": { \
- \"$ref\": \"#/definitions/positiveIntegerDefault0\" }, \
- \"required\": { \"$ref\": \"#/definitions/stringArray\" }, \
- \"additionalProperties\": { \
- \"anyOf\": [ \
- { \"type\": \"boolean\" }, \
- { \"$ref\": \"#\" } \
- ], \
- \"default\": {} \
- }, \
- \"definitions\": { \
- \"type\": \"object\", \
- \"additionalProperties\": { \"$ref\": \"#\" }, \
- \"default\": {} \
- }, \
- \"properties\": { \
- \"type\": \"object\", \
- \"additionalProperties\": { \"$ref\": \"#\" }, \
- \"default\": {} \
- }, \
- \"patternProperties\": { \
- \"type\": \"object\", \
- \"additionalProperties\": { \"$ref\": \"#\" }, \
- \"default\": {} \
- }, \
- \"dependencies\": { \
- \"type\": \"object\", \
- \"additionalProperties\": { \
- \"anyOf\": [ \
- { \"$ref\": \"#\" }, \
- { \"$ref\": \"#/definitions/stringArray\" } \
- ] \
- } \
- }, \
- \"enum\": { \
- \"type\": \"array\", \
- \"minItems\": 1, \
- \"uniqueItems\": true \
- }, \
- \"type\": { \
- \"anyOf\": [ \
- { \"$ref\": \"#/definitions/simpleTypes\" }, \
- { \
- \"type\": \"array\", \
- \"items\": { \"$ref\": \"#/definitions/simpleTypes\" }, \
- \"minItems\": 1, \
- \"uniqueItems\": true \
- } \
- ] \
- }, \
- \"allOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
- \"anyOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
- \"oneOf\": { \"$ref\": \"#/definitions/schemaArray\" }, \
- \"not\": { \"$ref\": \"#\" } \
- }, \
- \"dependencies\": { \
- \"exclusiveMaximum\": [ \"maximum\" ], \
- \"exclusiveMinimum\": [ \"minimum\" ] \
- }, \
- \"default\": {} \
- }";
- folly::Singleton<Validator> schemaValidator([]() {
- return makeValidator(parseJson(metaschemaJson)).release();
- });
- } // namespace
- Validator::~Validator() = default;
- std::unique_ptr<Validator> makeValidator(const dynamic& schema) {
- auto v = std::make_unique<SchemaValidator>();
- SchemaValidatorContext context(schema);
- context.refs["#"] = v.get();
- v->loadSchema(context, schema);
- return std::move(v);
- }
- std::shared_ptr<Validator> makeSchemaValidator() {
- return schemaValidator.try_get();
- }
- } // namespace jsonschema
- } // namespace folly
|