diff --git a/src/core/jsonschema/CMakeLists.txt b/src/core/jsonschema/CMakeLists.txt index 216ba896f..a8538e6b2 100644 --- a/src/core/jsonschema/CMakeLists.txt +++ b/src/core/jsonschema/CMakeLists.txt @@ -4,9 +4,9 @@ include(./official_resolver.cmake) sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME jsonschema PRIVATE_HEADERS bundle.h resolver.h walker.h frame.h error.h - types.h transform.h - SOURCES jsonschema.cc official_walker.cc frame.cc resolver.cc - walker.cc bundle.cc transformer.cc format.cc + types.h transform.h vocabularies.h + SOURCES jsonschema.cc vocabularies.cc official_walker.cc + frame.cc resolver.cc walker.cc bundle.cc transformer.cc format.cc "${CMAKE_CURRENT_BINARY_DIR}/official_resolver.cc") if(SOURCEMETA_CORE_INSTALL) diff --git a/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h b/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h index a28adad7e..29c145cbf 100644 --- a/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h +++ b/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h @@ -3,21 +3,17 @@ #include #include +#include -#include // std::uint8_t -#include // std::function, std::reference_wrapper -#include // std::optional -#include // std::set -#include // std::string -#include // std::string_view -#include // std::unordered_map +#include // std::uint8_t +#include // std::function, std::reference_wrapper +#include // std::optional +#include // std::set +#include // std::string +#include // std::string_view namespace sourcemeta::core { -/// @ingroup jsonschema -/// A set of vocabularies -using Vocabularies = std::unordered_map; - // Take a URI and get back a schema /// @ingroup jsonschema /// diff --git a/src/core/jsonschema/include/sourcemeta/core/jsonschema_vocabularies.h b/src/core/jsonschema/include/sourcemeta/core/jsonschema_vocabularies.h new file mode 100644 index 000000000..ec1cef02e --- /dev/null +++ b/src/core/jsonschema/include/sourcemeta/core/jsonschema_vocabularies.h @@ -0,0 +1,138 @@ +#ifndef SOURCEMETA_CORE_JSONSCHEMA_VOCABULARIES_H_ +#define SOURCEMETA_CORE_JSONSCHEMA_VOCABULARIES_H_ + +#ifndef SOURCEMETA_CORE_JSONSCHEMA_EXPORT +#include +#endif + +#include + +#include // std::bitset +#include // assert +#include // std::uint32_t, std::size_t +#include // std::optional +#include // std::out_of_range +#include // std::string +#include // std::string_view +#include // std::unordered_map +#include // std::pair +#include // std::vector + +namespace sourcemeta::core { + +/// @ingroup jsonschema +/// Optimized vocabulary set using bitflags for known vocabularies +/// and a fallback `std::unordered_map` for custom vocabularies. +/// +/// TODO: To maximize performance gains, convert string-based vocabulary checks +/// throughout the codebase to use enum-based methods. +struct SOURCEMETA_CORE_JSONSCHEMA_EXPORT Vocabularies { + /// Vocabulary enumeration for known JSON Schema vocabularies. + /// Each vocabulary is represented as a bitflag for efficient storage and + /// lookup. + enum class Known : std::uint8_t { + // Pre-vocabulary dialects (treated as vocabularies) + JSON_Schema_Draft_0 = 0, + JSON_Schema_Draft_0_Hyper = 1, + JSON_Schema_Draft_1 = 2, + JSON_Schema_Draft_1_Hyper = 3, + JSON_Schema_Draft_2 = 4, + JSON_Schema_Draft_2_Hyper = 5, + JSON_Schema_Draft_3 = 6, + JSON_Schema_Draft_3_Hyper = 7, + JSON_Schema_Draft_4 = 8, + JSON_Schema_Draft_4_Hyper = 9, + JSON_Schema_Draft_6 = 10, + JSON_Schema_Draft_6_Hyper = 11, + JSON_Schema_Draft_7 = 12, + JSON_Schema_Draft_7_Hyper = 13, + // 2019-09 vocabularies + JSON_Schema_2019_09_Core = 14, + JSON_Schema_2019_09_Applicator = 15, + JSON_Schema_2019_09_Validation = 16, + JSON_Schema_2019_09_Meta_Data = 17, + JSON_Schema_2019_09_Format = 18, + JSON_Schema_2019_09_Content = 19, + JSON_Schema_2019_09_Hyper_Schema = 20, + // 2020-12 vocabularies + JSON_Schema_2020_12_Core = 21, + JSON_Schema_2020_12_Applicator = 22, + JSON_Schema_2020_12_Unevaluated = 23, + JSON_Schema_2020_12_Validation = 24, + JSON_Schema_2020_12_Meta_Data = 25, + JSON_Schema_2020_12_Format_Annotation = 26, + JSON_Schema_2020_12_Format_Assertion = 27, + JSON_Schema_2020_12_Content = 28, + // Sentinel value representing the total count of known vocabularies + COUNT + }; + +public: + /// Default constructor + Vocabularies() = default; + + /// Copy constructor + Vocabularies(const Vocabularies &) = default; + + /// Move constructor + Vocabularies(Vocabularies &&) noexcept = default; + + /// Copy assignment operator + auto operator=(const Vocabularies &) -> Vocabularies & = default; + + /// Move assignment operator + auto operator=(Vocabularies &&) noexcept -> Vocabularies & = default; + + /// Destructor + ~Vocabularies() = default; + + /// Construct from initializer list (for backward compatibility) + Vocabularies(std::initializer_list> init); + + /// Construct from initializer list using known vocabularies (optimized) + Vocabularies(std::initializer_list> init); + + /// Check if a vocabulary is enabled + [[nodiscard]] auto contains(const JSON::String &uri) const noexcept -> bool; + + /// Check if a known vocabulary is enabled (optimized) + [[nodiscard]] auto contains(Known vocabulary) const noexcept -> bool; + + /// Insert a vocabulary with its required/optional status + auto insert(const JSON::String &uri, bool required) noexcept -> void; + + /// Insert a known vocabulary with its required/optional status (optimized) + auto insert(Known vocabulary, bool required) noexcept -> void; + + /// Get vocabulary status by URI + [[nodiscard]] auto get(const JSON::String &uri) const noexcept + -> std::optional; + + /// Get known vocabulary status (optimized) + [[nodiscard]] auto get(Known vocabulary) const noexcept + -> std::optional; + + /// Get the number of vocabularies (required + optional + custom) + [[nodiscard]] auto size() const noexcept -> std::size_t; + + /// Check if there are no vocabularies + [[nodiscard]] auto empty() const noexcept -> bool; + +private: + // Invariant: required_known and optional_known must be mutually exclusive + // A vocabulary can be either required (true) OR optional (false), never both +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4251) +#endif + std::bitset(Known::COUNT)> required_known{}; + std::bitset(Known::COUNT)> optional_known{}; + std::unordered_map custom; +#ifdef _MSC_VER +#pragma warning(pop) +#endif +}; + +} // namespace sourcemeta::core + +#endif diff --git a/src/core/jsonschema/jsonschema.cc b/src/core/jsonschema/jsonschema.cc index cf4140310..579e866ad 100644 --- a/src/core/jsonschema/jsonschema.cc +++ b/src/core/jsonschema/jsonschema.cc @@ -295,20 +295,69 @@ auto sourcemeta::core::base_dialect( } namespace { -auto core_vocabulary(std::string_view base_dialect) -> std::string { +auto core_vocabulary_known(std::string_view base_dialect) + -> sourcemeta::core::Vocabularies::Known { if (base_dialect == "https://json-schema.org/draft/2020-12/schema" || base_dialect == "https://json-schema.org/draft/2020-12/hyper-schema") { - return "https://json-schema.org/draft/2020-12/vocab/core"; + return sourcemeta::core::Vocabularies::Known::JSON_Schema_2020_12_Core; } else if (base_dialect == "https://json-schema.org/draft/2019-09/schema" || base_dialect == "https://json-schema.org/draft/2019-09/hyper-schema") { - return "https://json-schema.org/draft/2019-09/vocab/core"; + return sourcemeta::core::Vocabularies::Known::JSON_Schema_2019_09_Core; } else { std::ostringstream error; error << "Unrecognized base dialect: " << base_dialect; throw sourcemeta::core::SchemaError(error.str()); } } + +auto dialect_to_known(std::string_view dialect) + -> std::optional { + using sourcemeta::core::Vocabularies; + if (dialect == "http://json-schema.org/draft-07/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_7; + } + if (dialect == "http://json-schema.org/draft-07/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_7_Hyper; + } + if (dialect == "http://json-schema.org/draft-06/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_6; + } + if (dialect == "http://json-schema.org/draft-06/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_6_Hyper; + } + if (dialect == "http://json-schema.org/draft-04/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_4; + } + if (dialect == "http://json-schema.org/draft-04/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_4_Hyper; + } + if (dialect == "http://json-schema.org/draft-03/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_3; + } + if (dialect == "http://json-schema.org/draft-03/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_3_Hyper; + } + if (dialect == "http://json-schema.org/draft-02/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_2; + } + if (dialect == "http://json-schema.org/draft-02/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_2_Hyper; + } + if (dialect == "http://json-schema.org/draft-01/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_1; + } + if (dialect == "http://json-schema.org/draft-01/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_1_Hyper; + } + if (dialect == "http://json-schema.org/draft-00/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_0; + } + if (dialect == "http://json-schema.org/draft-00/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_0_Hyper; + } + return std::nullopt; +} } // namespace auto sourcemeta::core::vocabularies( @@ -342,21 +391,22 @@ auto sourcemeta::core::vocabularies(const SchemaResolver &resolver, // As a performance optimization shortcut if (base_dialect == dialect) { if (dialect == "https://json-schema.org/draft/2020-12/schema") { - return {{"https://json-schema.org/draft/2020-12/vocab/core", true}, - {"https://json-schema.org/draft/2020-12/vocab/applicator", true}, - {"https://json-schema.org/draft/2020-12/vocab/unevaluated", true}, - {"https://json-schema.org/draft/2020-12/vocab/validation", true}, - {"https://json-schema.org/draft/2020-12/vocab/meta-data", true}, - {"https://json-schema.org/draft/2020-12/vocab/format-annotation", - true}, - {"https://json-schema.org/draft/2020-12/vocab/content", true}}; + return Vocabularies{ + {Vocabularies::Known::JSON_Schema_2020_12_Core, true}, + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, true}, + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, true}, + {Vocabularies::Known::JSON_Schema_2020_12_Validation, true}, + {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, true}, + {Vocabularies::Known::JSON_Schema_2020_12_Format_Annotation, true}, + {Vocabularies::Known::JSON_Schema_2020_12_Content, true}}; } else if (dialect == "https://json-schema.org/draft/2019-09/schema") { - return {{"https://json-schema.org/draft/2019-09/vocab/core", true}, - {"https://json-schema.org/draft/2019-09/vocab/applicator", true}, - {"https://json-schema.org/draft/2019-09/vocab/validation", true}, - {"https://json-schema.org/draft/2019-09/vocab/meta-data", true}, - {"https://json-schema.org/draft/2019-09/vocab/format", false}, - {"https://json-schema.org/draft/2019-09/vocab/content", true}}; + return Vocabularies{ + {Vocabularies::Known::JSON_Schema_2019_09_Core, true}, + {Vocabularies::Known::JSON_Schema_2019_09_Applicator, true}, + {Vocabularies::Known::JSON_Schema_2019_09_Validation, true}, + {Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, true}, + {Vocabularies::Known::JSON_Schema_2019_09_Format, false}, + {Vocabularies::Known::JSON_Schema_2019_09_Content, true}}; } } @@ -374,7 +424,11 @@ auto sourcemeta::core::vocabularies(const SchemaResolver &resolver, dialect == "http://json-schema.org/draft-02/schema#" || dialect == "http://json-schema.org/draft-01/schema#" || dialect == "http://json-schema.org/draft-00/schema#") { - return {{dialect, true}}; + const auto known = dialect_to_known(dialect); + if (known.has_value()) { + return Vocabularies{{known.value(), true}}; + } + return Vocabularies{{dialect, true}}; } /* @@ -394,7 +448,11 @@ auto sourcemeta::core::vocabularies(const SchemaResolver &resolver, base_dialect == "http://json-schema.org/draft-02/hyper-schema#" || base_dialect == "http://json-schema.org/draft-01/hyper-schema#" || base_dialect == "http://json-schema.org/draft-00/hyper-schema#") { - return {{base_dialect, true}}; + const auto known = dialect_to_known(base_dialect); + if (known.has_value()) { + return Vocabularies{{known.value(), true}}; + } + return Vocabularies{{base_dialect, true}}; } /* @@ -422,25 +480,28 @@ auto sourcemeta::core::vocabularies(const SchemaResolver &resolver, */ Vocabularies result; - const std::string core{core_vocabulary(base_dialect)}; + const auto core{core_vocabulary_known(base_dialect)}; if (schema_dialect.defines("$vocabulary")) { const sourcemeta::core::JSON &vocabularies{ schema_dialect.at("$vocabulary")}; assert(vocabularies.is_object()); for (const auto &entry : vocabularies.as_object()) { - result.insert({entry.first, entry.second.to_boolean()}); + result.insert(entry.first, entry.second.to_boolean()); } } else { - result.insert({core, true}); + result.insert(core, true); } // The specification recommends these checks if (!result.contains(core)) { throw sourcemeta::core::SchemaError( "The core vocabulary must always be present"); - } else if (!result.at(core)) { - throw sourcemeta::core::SchemaError( - "The core vocabulary must always be required"); + } else { + const auto core_status{result.get(core)}; + if (core_status.has_value() && !core_status.value()) { + throw sourcemeta::core::SchemaError( + "The core vocabulary must always be required"); + } } return result; diff --git a/src/core/jsonschema/vocabularies.cc b/src/core/jsonschema/vocabularies.cc new file mode 100644 index 000000000..6dc91f73f --- /dev/null +++ b/src/core/jsonschema/vocabularies.cc @@ -0,0 +1,203 @@ +#include + +#include // assert +#include // std::optional +#include // std::string +#include // std::pair +#include // std::vector + +namespace { +// Map vocabulary URI to its corresponding enum flag +// Ordered from most recent/common to oldest for faster short-circuiting +auto uri_to_known_vocabulary(std::string_view uri) + -> std::optional { + using sourcemeta::core::Vocabularies; + + // 2020-12 vocabularies (most recent/common) + if (uri == "https://json-schema.org/draft/2020-12/vocab/core") { + return Vocabularies::Known::JSON_Schema_2020_12_Core; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/applicator") { + return Vocabularies::Known::JSON_Schema_2020_12_Applicator; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/unevaluated") { + return Vocabularies::Known::JSON_Schema_2020_12_Unevaluated; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/validation") { + return Vocabularies::Known::JSON_Schema_2020_12_Validation; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/meta-data") { + return Vocabularies::Known::JSON_Schema_2020_12_Meta_Data; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/format-annotation") { + return Vocabularies::Known::JSON_Schema_2020_12_Format_Annotation; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/format-assertion") { + return Vocabularies::Known::JSON_Schema_2020_12_Format_Assertion; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/content") { + return Vocabularies::Known::JSON_Schema_2020_12_Content; + } + + // 2019-09 vocabularies + if (uri == "https://json-schema.org/draft/2019-09/vocab/core") { + return Vocabularies::Known::JSON_Schema_2019_09_Core; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/applicator") { + return Vocabularies::Known::JSON_Schema_2019_09_Applicator; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/validation") { + return Vocabularies::Known::JSON_Schema_2019_09_Validation; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/meta-data") { + return Vocabularies::Known::JSON_Schema_2019_09_Meta_Data; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/format") { + return Vocabularies::Known::JSON_Schema_2019_09_Format; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/content") { + return Vocabularies::Known::JSON_Schema_2019_09_Content; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/hyper-schema") { + return Vocabularies::Known::JSON_Schema_2019_09_Hyper_Schema; + } + + // Pre-vocabulary dialects (least common, checked last) + if (uri == "http://json-schema.org/draft-07/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_7; + } + if (uri == "http://json-schema.org/draft-07/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_7_Hyper; + } + if (uri == "http://json-schema.org/draft-06/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_6; + } + if (uri == "http://json-schema.org/draft-06/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_6_Hyper; + } + if (uri == "http://json-schema.org/draft-04/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_4; + } + if (uri == "http://json-schema.org/draft-04/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_4_Hyper; + } + if (uri == "http://json-schema.org/draft-03/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_3; + } + if (uri == "http://json-schema.org/draft-03/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_3_Hyper; + } + if (uri == "http://json-schema.org/draft-02/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_2; + } + if (uri == "http://json-schema.org/draft-02/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_2_Hyper; + } + if (uri == "http://json-schema.org/draft-01/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_1; + } + if (uri == "http://json-schema.org/draft-01/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_1_Hyper; + } + if (uri == "http://json-schema.org/draft-00/schema#") { + return Vocabularies::Known::JSON_Schema_Draft_0; + } + if (uri == "http://json-schema.org/draft-00/hyper-schema#") { + return Vocabularies::Known::JSON_Schema_Draft_0_Hyper; + } + + return std::nullopt; +} +} // anonymous namespace + +sourcemeta::core::Vocabularies::Vocabularies( + std::initializer_list> init) { + for (const auto &entry : init) { + this->insert(entry.first, entry.second); + } +} + +sourcemeta::core::Vocabularies::Vocabularies( + std::initializer_list> init) { + for (const auto &entry : init) { + this->insert(entry.first, entry.second); + } +} + +auto sourcemeta::core::Vocabularies::contains( + const JSON::String &uri) const noexcept -> bool { + const auto maybe_known = uri_to_known_vocabulary(uri); + if (maybe_known.has_value()) { + return this->contains(maybe_known.value()); + } + const auto iterator = this->custom.find(uri); + return iterator != this->custom.end(); +} + +auto sourcemeta::core::Vocabularies::contains(Known vocabulary) const noexcept + -> bool { + const auto index = static_cast(vocabulary); + // Use [] operator instead of test() to avoid exceptions in noexcept function + return this->required_known[index] || this->optional_known[index]; +} + +auto sourcemeta::core::Vocabularies::insert(const JSON::String &uri, + bool required) noexcept -> void { + const auto maybe_known = uri_to_known_vocabulary(uri); + if (maybe_known.has_value()) { + this->insert(maybe_known.value(), required); + } else { + this->custom.insert({uri, required}); + } +} + +auto sourcemeta::core::Vocabularies::insert(Known vocabulary, + bool required) noexcept -> void { + const auto index = static_cast(vocabulary); + if (required) { + this->required_known[index] = true; + this->optional_known[index] = false; + } else { + this->optional_known[index] = true; + this->required_known[index] = false; + } + // Verify invariant: vocabulary cannot be both required and optional + assert((this->required_known & this->optional_known).none()); +} + +auto sourcemeta::core::Vocabularies::get(const JSON::String &uri) const noexcept + -> std::optional { + const auto maybe_known = uri_to_known_vocabulary(uri); + if (maybe_known.has_value()) { + return this->get(maybe_known.value()); + } + const auto iterator = this->custom.find(uri); + if (iterator != this->custom.end()) { + return iterator->second; + } + return std::nullopt; +} + +auto sourcemeta::core::Vocabularies::get(Known vocabulary) const noexcept + -> std::optional { + const auto index = static_cast(vocabulary); + // Use [] operator instead of test() to avoid exceptions in noexcept function + assert(!this->required_known[index] || !this->optional_known[index]); + if (this->required_known[index]) { + return true; + } + if (this->optional_known[index]) { + return false; + } + return std::nullopt; +} + +auto sourcemeta::core::Vocabularies::size() const noexcept -> std::size_t { + return (this->required_known | this->optional_known).count() + + this->custom.size(); +} + +auto sourcemeta::core::Vocabularies::empty() const noexcept -> bool { + return this->required_known.none() && this->optional_known.none() && + this->custom.empty(); +} diff --git a/src/core/jsonschema/walker.cc b/src/core/jsonschema/walker.cc index 8531001df..48f7b94b4 100644 --- a/src/core/jsonschema/walker.cc +++ b/src/core/jsonschema/walker.cc @@ -490,11 +490,11 @@ sourcemeta::core::SchemaKeywordIterator::SchemaKeywordIterator( const std::optional base_dialect{ sourcemeta::core::base_dialect(schema, resolver, dialect)}; - Vocabularies vocabularies; - if (base_dialect.has_value() && dialect.has_value()) { - vocabularies.merge(sourcemeta::core::vocabularies( - resolver, base_dialect.value(), dialect.value())); - } + Vocabularies vocabularies{ + base_dialect.has_value() && dialect.has_value() + ? sourcemeta::core::vocabularies(resolver, base_dialect.value(), + dialect.value()) + : Vocabularies{}}; for (const auto &entry : schema.as_object()) { sourcemeta::core::SchemaIteratorEntry subschema_entry{ diff --git a/src/extension/alterschema/alterschema.cc b/src/extension/alterschema/alterschema.cc index 9619120c5..b442b4f95 100644 --- a/src/extension/alterschema/alterschema.cc +++ b/src/extension/alterschema/alterschema.cc @@ -6,12 +6,15 @@ #include #include namespace sourcemeta::core { -static auto -contains_any(const Vocabularies &container, - const std::set &values) -> bool { - return std::ranges::any_of(container, [&values](const auto &element) { - return values.contains(element.first); - }); +// TODO: Move this to `Vocabularies::contains_any` or something like that +static auto contains_any(const Vocabularies &container, + const std::set &values) -> bool { + for (const auto &value : values) { + if (container.contains(value)) { + return true; + } + } + return false; } template diff --git a/test/jsonschema/jsonschema_official_walker_2019_09_test.cc b/test/jsonschema/jsonschema_official_walker_2019_09_test.cc index cb2dcfbfb..29e263b73 100644 --- a/test/jsonschema/jsonschema_official_walker_2019_09_test.cc +++ b/test/jsonschema/jsonschema_official_walker_2019_09_test.cc @@ -17,6 +17,12 @@ static const sourcemeta::core::Vocabularies VOCABULARIES_2019_09_VALIDATION{ {"https://json-schema.org/draft/2019-09/vocab/core", true}, {"https://json-schema.org/draft/2019-09/vocab/validation", true}}; +static const sourcemeta::core::Vocabularies + VOCABULARIES_2019_09_APPLICATOR_AND_VALIDATION{ + {"https://json-schema.org/draft/2019-09/vocab/core", true}, + {"https://json-schema.org/draft/2019-09/vocab/applicator", true}, + {"https://json-schema.org/draft/2019-09/vocab/validation", true}}; + static const sourcemeta::core::Vocabularies VOCABULARIES_2019_09_FORMAT{ {"https://json-schema.org/draft/2019-09/vocab/core", true}, {"https://json-schema.org/draft/2019-09/vocab/format", true}}; @@ -280,14 +286,8 @@ TEST(JSONSchema_official_walker_2019_09, applicator_contains_only) { TEST(JSONSchema_official_walker_2019_09, applicator_contains_with_validation) { using namespace sourcemeta::core; - sourcemeta::core::Vocabularies vocabularies; - std::copy(VOCABULARIES_2019_09_APPLICATOR.cbegin(), - VOCABULARIES_2019_09_APPLICATOR.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2019_09_VALIDATION.cbegin(), - VOCABULARIES_2019_09_VALIDATION.cend(), - std::inserter(vocabularies, vocabularies.end())); - const auto result{schema_official_walker("contains", vocabularies)}; + const auto result{schema_official_walker( + "contains", VOCABULARIES_2019_09_APPLICATOR_AND_VALIDATION)}; EXPECT_EQ(result.type, SchemaKeywordType::ApplicatorValueTraverseAnyItem); EXPECT_TRUE(result.vocabulary.has_value()); EXPECT_EQ(result.vocabulary.value(), @@ -1637,14 +1637,7 @@ TEST(JSONSchema_official_walker_2019_09, } TEST(JSONSchema_official_walker_2019_09, schema_keyword_priority_array) { - sourcemeta::core::Vocabularies vocabularies; - std::copy(VOCABULARIES_2019_09_APPLICATOR.cbegin(), - VOCABULARIES_2019_09_APPLICATOR.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2019_09_VALIDATION.cbegin(), - VOCABULARIES_2019_09_VALIDATION.cend(), - std::inserter(vocabularies, vocabularies.end())); - + const auto &vocabularies = VOCABULARIES_2019_09_APPLICATOR_AND_VALIDATION; const auto &walker = sourcemeta::core::schema_official_walker; using namespace sourcemeta::core; EXPECT_EQ(schema_keyword_priority("items", vocabularies, walker), 0); diff --git a/test/jsonschema/jsonschema_official_walker_2020_12_test.cc b/test/jsonschema/jsonschema_official_walker_2020_12_test.cc index ba8429a72..b0166fbd0 100644 --- a/test/jsonschema/jsonschema_official_walker_2020_12_test.cc +++ b/test/jsonschema/jsonschema_official_walker_2020_12_test.cc @@ -36,6 +36,25 @@ static const sourcemeta::core::Vocabularies VOCABULARIES_2020_12_CONTENT{ {"https://json-schema.org/draft/2020-12/vocab/core", true}, {"https://json-schema.org/draft/2020-12/vocab/content", true}}; +static const sourcemeta::core::Vocabularies + VOCABULARIES_2020_12_APPLICATOR_AND_VALIDATION{ + {"https://json-schema.org/draft/2020-12/vocab/core", true}, + {"https://json-schema.org/draft/2020-12/vocab/applicator", true}, + {"https://json-schema.org/draft/2020-12/vocab/validation", true}}; + +static const sourcemeta::core::Vocabularies + VOCABULARIES_2020_12_UNEVALUATED_AND_APPLICATOR{ + {"https://json-schema.org/draft/2020-12/vocab/core", true}, + {"https://json-schema.org/draft/2020-12/vocab/unevaluated", true}, + {"https://json-schema.org/draft/2020-12/vocab/applicator", true}}; + +static const sourcemeta::core::Vocabularies + VOCABULARIES_2020_12_APPLICATOR_UNEVALUATED_AND_VALIDATION{ + {"https://json-schema.org/draft/2020-12/vocab/core", true}, + {"https://json-schema.org/draft/2020-12/vocab/applicator", true}, + {"https://json-schema.org/draft/2020-12/vocab/unevaluated", true}, + {"https://json-schema.org/draft/2020-12/vocab/validation", true}}; + static const sourcemeta::core::Vocabularies VOCABULARIES_2020_12_METADATA{ {"https://json-schema.org/draft/2020-12/vocab/core", true}, {"https://json-schema.org/draft/2020-12/vocab/meta-data", true}}; @@ -290,14 +309,8 @@ TEST(JSONSchema_official_walker_2020_12, applicator_contains_only) { TEST(JSONSchema_official_walker_2020_12, applicator_contains_with_validation) { using namespace sourcemeta::core; - sourcemeta::core::Vocabularies vocabularies; - std::copy(VOCABULARIES_2020_12_APPLICATOR.cbegin(), - VOCABULARIES_2020_12_APPLICATOR.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2020_12_VALIDATION.cbegin(), - VOCABULARIES_2020_12_VALIDATION.cend(), - std::inserter(vocabularies, vocabularies.end())); - const auto result{schema_official_walker("contains", vocabularies)}; + const auto result{schema_official_walker( + "contains", VOCABULARIES_2020_12_APPLICATOR_AND_VALIDATION)}; EXPECT_EQ(result.type, SchemaKeywordType::ApplicatorValueTraverseAnyItem); EXPECT_TRUE(result.vocabulary.has_value()); EXPECT_EQ(result.vocabulary.value(), @@ -381,14 +394,8 @@ TEST(JSONSchema_official_walker_2020_12, unevaluated_unevaluatedItems_only) { TEST(JSONSchema_official_walker_2020_12, unevaluated_unevaluatedItems_with_applicator) { using namespace sourcemeta::core; - sourcemeta::core::Vocabularies vocabularies; - std::copy(VOCABULARIES_2020_12_UNEVALUATED.cbegin(), - VOCABULARIES_2020_12_UNEVALUATED.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2020_12_APPLICATOR.cbegin(), - VOCABULARIES_2020_12_APPLICATOR.cend(), - std::inserter(vocabularies, vocabularies.end())); - const auto result{schema_official_walker("unevaluatedItems", vocabularies)}; + const auto result{schema_official_walker( + "unevaluatedItems", VOCABULARIES_2020_12_UNEVALUATED_AND_APPLICATOR)}; EXPECT_EQ(result.type, SchemaKeywordType::ApplicatorValueTraverseSomeItem); EXPECT_TRUE(result.vocabulary.has_value()); EXPECT_EQ(result.vocabulary.value(), @@ -417,15 +424,9 @@ TEST(JSONSchema_official_walker_2020_12, TEST(JSONSchema_official_walker_2020_12, unevaluated_unevaluatedProperties_with_applicator) { using namespace sourcemeta::core; - sourcemeta::core::Vocabularies vocabularies; - std::copy(VOCABULARIES_2020_12_UNEVALUATED.cbegin(), - VOCABULARIES_2020_12_UNEVALUATED.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2020_12_APPLICATOR.cbegin(), - VOCABULARIES_2020_12_APPLICATOR.cend(), - std::inserter(vocabularies, vocabularies.end())); const auto result{ - schema_official_walker("unevaluatedProperties", vocabularies)}; + schema_official_walker("unevaluatedProperties", + VOCABULARIES_2020_12_UNEVALUATED_AND_APPLICATOR)}; EXPECT_EQ(result.type, SchemaKeywordType::ApplicatorValueTraverseSomeProperty); EXPECT_TRUE(result.vocabulary.has_value()); @@ -1715,16 +1716,8 @@ TEST(JSONSchema_official_walker_2020_12, } TEST(JSONSchema_official_walker_2020_12, schema_keyword_priority_array) { - sourcemeta::core::Vocabularies vocabularies; - std::copy(VOCABULARIES_2020_12_APPLICATOR.cbegin(), - VOCABULARIES_2020_12_APPLICATOR.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2020_12_UNEVALUATED.cbegin(), - VOCABULARIES_2020_12_UNEVALUATED.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2020_12_VALIDATION.cbegin(), - VOCABULARIES_2020_12_VALIDATION.cend(), - std::inserter(vocabularies, vocabularies.end())); + const auto &vocabularies = + VOCABULARIES_2020_12_APPLICATOR_UNEVALUATED_AND_VALIDATION; const auto &walker = sourcemeta::core::schema_official_walker; using namespace sourcemeta::core; @@ -1738,13 +1731,7 @@ TEST(JSONSchema_official_walker_2020_12, schema_keyword_priority_array) { } TEST(JSONSchema_official_walker_2020_12, schema_keyword_priority_object) { - sourcemeta::core::Vocabularies vocabularies; - std::copy(VOCABULARIES_2020_12_APPLICATOR.cbegin(), - VOCABULARIES_2020_12_APPLICATOR.cend(), - std::inserter(vocabularies, vocabularies.end())); - std::copy(VOCABULARIES_2020_12_UNEVALUATED.cbegin(), - VOCABULARIES_2020_12_UNEVALUATED.cend(), - std::inserter(vocabularies, vocabularies.end())); + const auto &vocabularies = VOCABULARIES_2020_12_UNEVALUATED_AND_APPLICATOR; const auto &walker = sourcemeta::core::schema_official_walker; using namespace sourcemeta::core; diff --git a/test/jsonschema/jsonschema_vocabulary_2019_09_test.cc b/test/jsonschema/jsonschema_vocabulary_2019_09_test.cc index 23fc63c50..f7f8ebcdf 100644 --- a/test/jsonschema/jsonschema_vocabulary_2019_09_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_2019_09_test.cc @@ -35,11 +35,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_2019_09, no_vocabularies) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_vocabulary_2020_12_test.cc b/test/jsonschema/jsonschema_vocabulary_2020_12_test.cc index 60bbe34b3..37929365b 100644 --- a/test/jsonschema/jsonschema_vocabulary_2020_12_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_2020_12_test.cc @@ -53,11 +53,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_2020_12, core_vocabularies_boolean_with_default) { const sourcemeta::core::JSON document{true}; diff --git a/test/jsonschema/jsonschema_vocabulary_draft0_test.cc b/test/jsonschema/jsonschema_vocabulary_draft0_test.cc index f9aeff522..8742432a0 100644 --- a/test/jsonschema/jsonschema_vocabulary_draft0_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_draft0_test.cc @@ -29,11 +29,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_draft0, schema) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_vocabulary_draft1_test.cc b/test/jsonschema/jsonschema_vocabulary_draft1_test.cc index 38d4fecd1..c71c79e9d 100644 --- a/test/jsonschema/jsonschema_vocabulary_draft1_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_draft1_test.cc @@ -29,11 +29,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_draft1, schema) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_vocabulary_draft2_test.cc b/test/jsonschema/jsonschema_vocabulary_draft2_test.cc index 5bf1446b7..d2751c98c 100644 --- a/test/jsonschema/jsonschema_vocabulary_draft2_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_draft2_test.cc @@ -29,11 +29,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_draft2, schema) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_vocabulary_draft3_test.cc b/test/jsonschema/jsonschema_vocabulary_draft3_test.cc index 6e81f6c13..ba79d2fab 100644 --- a/test/jsonschema/jsonschema_vocabulary_draft3_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_draft3_test.cc @@ -29,11 +29,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_draft3, schema) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_vocabulary_draft4_test.cc b/test/jsonschema/jsonschema_vocabulary_draft4_test.cc index 1c5e8c1c6..ba968d7cb 100644 --- a/test/jsonschema/jsonschema_vocabulary_draft4_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_draft4_test.cc @@ -29,11 +29,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_draft4, schema) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_vocabulary_draft6_test.cc b/test/jsonschema/jsonschema_vocabulary_draft6_test.cc index 523346e0c..e871f26dc 100644 --- a/test/jsonschema/jsonschema_vocabulary_draft6_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_draft6_test.cc @@ -29,11 +29,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_draft6, schema) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_vocabulary_draft7_test.cc b/test/jsonschema/jsonschema_vocabulary_draft7_test.cc index bb1d82c90..fe9615310 100644 --- a/test/jsonschema/jsonschema_vocabulary_draft7_test.cc +++ b/test/jsonschema/jsonschema_vocabulary_draft7_test.cc @@ -29,11 +29,11 @@ static auto test_resolver(std::string_view identifier) #define EXPECT_VOCABULARY_REQUIRED(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_TRUE((vocabularies).at(vocabulary)); + EXPECT_TRUE((vocabularies).get(vocabulary).value()); #define EXPECT_VOCABULARY_OPTIONAL(vocabularies, vocabulary) \ EXPECT_TRUE((vocabularies).contains(vocabulary)); \ - EXPECT_FALSE((vocabularies).at(vocabulary)); + EXPECT_FALSE((vocabularies).get(vocabulary).value()); TEST(JSONSchema_vocabulary_draft7, schema) { const sourcemeta::core::JSON document = sourcemeta::core::parse_json(R"JSON({ diff --git a/test/jsonschema/jsonschema_walker_test.cc b/test/jsonschema/jsonschema_walker_test.cc index c267ffb2b..e3029eb75 100644 --- a/test/jsonschema/jsonschema_walker_test.cc +++ b/test/jsonschema/jsonschema_walker_test.cc @@ -34,8 +34,7 @@ static auto test_resolver(std::string_view identifier) static auto test_walker(std::string_view keyword, const sourcemeta::core::Vocabularies &vocabularies) -> sourcemeta::core::SchemaWalkerResult { - if (vocabularies.find("https://sourcemeta.com/vocab/test-1") != - vocabularies.end()) { + if (vocabularies.get("https://sourcemeta.com/vocab/test-1").has_value()) { if (keyword == "schema") { return {sourcemeta::core::SchemaKeywordType:: ApplicatorValueTraverseSomeProperty, @@ -69,8 +68,7 @@ static auto test_walker(std::string_view keyword, } } - if (vocabularies.find("https://sourcemeta.com/vocab/test-2") != - vocabularies.end()) { + if (vocabularies.get("https://sourcemeta.com/vocab/test-2").has_value()) { if (keyword == "custom") { return {sourcemeta::core::SchemaKeywordType:: ApplicatorValueTraverseSomeProperty,