diff --git a/benchmark/e2e/runner.cc b/benchmark/e2e/runner.cc index 486bfcdb0..894b2e257 100644 --- a/benchmark/e2e/runner.cc +++ b/benchmark/e2e/runner.cc @@ -12,8 +12,9 @@ #include #include +// clang-format off #define REGISTER_E2E_COMPILER(name, directory_name) \ - static auto E2E_Compiler_##name(benchmark::State &state)->void { \ + static auto E2E_Compiler_##name(benchmark::State &state) -> void { \ const std::filesystem::path directory{CURRENT_DIRECTORY \ "/e2e/" directory_name}; \ const auto schema{sourcemeta::core::read_json(directory / "schema.json")}; \ @@ -30,7 +31,7 @@ BENCHMARK(E2E_Compiler_##name) #define REGISTER_E2E_EVALUATOR(name, directory_name) \ - static auto E2E_Evaluator_##name(benchmark::State &state)->void { \ + static auto E2E_Evaluator_##name(benchmark::State &state) -> void { \ const std::filesystem::path directory{CURRENT_DIRECTORY \ "/e2e/" directory_name}; \ const auto schema{sourcemeta::core::read_json(directory / "schema.json")}; \ @@ -56,6 +57,7 @@ } \ } \ BENCHMARK(E2E_Evaluator_##name) +// clang-format on REGISTER_E2E_COMPILER(adaptivecard, "adaptivecard"); REGISTER_E2E_COMPILER(ansible_meta, "ansible-meta"); diff --git a/src/compiler/compile.cc b/src/compiler/compile.cc index cc4723e43..c2713cb38 100644 --- a/src/compiler/compile.cc +++ b/src/compiler/compile.cc @@ -10,6 +10,7 @@ #include // std::move, std::pair #include "compile_helpers.h" +#include "vocabulary_lookup.h" namespace { @@ -33,6 +34,12 @@ auto compile_subschema(const sourcemeta::blaze::Context &context, } Instructions steps; + // Reserve capacity to avoid reallocations - typical schemas have 3-10 + // keywords + if (schema_context.schema.is_object()) { + steps.reserve(schema_context.schema.size()); + } + for (const auto &entry : sourcemeta::core::SchemaKeywordIterator{ schema_context.schema, context.walker, context.resolver, default_dialect}) { @@ -285,11 +292,14 @@ auto compile(const sourcemeta::core::JSON &schema, /////////////////////////////////////////////////////////////////// Instructions compiler_template; + // Reserve initial capacity for the template + compiler_template.reserve(32); + if (uses_dynamic_scopes && - (schema_context.vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/core") || - schema_context.vocabularies.contains( - "https://json-schema.org/draft/2020-12/vocab/core"))) { + (has_vocabulary(schema_context.vocabularies, + "https://json-schema.org/draft/2019-09/vocab/core") || + has_vocabulary(schema_context.vocabularies, + "https://json-schema.org/draft/2020-12/vocab/core"))) { for (const auto &entry : context.frame.locations()) { // We are only trying to find dynamic anchors if (entry.second.type != diff --git a/src/compiler/compile_helpers.h b/src/compiler/compile_helpers.h index c870a45ef..09f4ba794 100644 --- a/src/compiler/compile_helpers.h +++ b/src/compiler/compile_helpers.h @@ -11,22 +11,30 @@ #include // std::declval, std::move #include // std::visit +#include "vocabulary_lookup.h" + namespace sourcemeta::blaze { +// Static empty string for DynamicContext with no keyword +inline const std::string &empty_keyword() { + static const std::string value{""}; + return value; +} + inline auto relative_dynamic_context() -> DynamicContext { - return {"", sourcemeta::core::empty_pointer, sourcemeta::core::empty_pointer, - false}; + return {empty_keyword(), sourcemeta::core::empty_pointer, + sourcemeta::core::empty_pointer, false}; } inline auto relative_dynamic_context(const DynamicContext &dynamic_context) -> DynamicContext { - return {"", sourcemeta::core::empty_pointer, sourcemeta::core::empty_pointer, - dynamic_context.property_as_target}; + return {empty_keyword(), sourcemeta::core::empty_pointer, + sourcemeta::core::empty_pointer, dynamic_context.property_as_target}; } inline auto property_relative_dynamic_context() -> DynamicContext { - return {"", sourcemeta::core::empty_pointer, sourcemeta::core::empty_pointer, - true}; + return {empty_keyword(), sourcemeta::core::empty_pointer, + sourcemeta::core::empty_pointer, true}; } inline auto schema_resource_id(const std::vector &resources, @@ -222,7 +230,7 @@ inline auto find_adjacent(const Context &context, if (std::any_of(vocabularies.cbegin(), vocabularies.cend(), [&subschema_vocabularies](const auto &vocabulary) { - return subschema_vocabularies.contains(vocabulary); + return has_vocabulary(subschema_vocabularies, vocabulary); }) && subschema.type() == type) { result.emplace_back(subschema); diff --git a/src/compiler/default_compiler.cc b/src/compiler/default_compiler.cc index 57c5012ea..221923a36 100644 --- a/src/compiler/default_compiler.cc +++ b/src/compiler/default_compiler.cc @@ -5,6 +5,7 @@ #include "default_compiler_draft4.h" #include "default_compiler_draft6.h" #include "default_compiler_draft7.h" +#include "vocabulary_lookup.h" #include // assert #include // std::string @@ -39,7 +40,8 @@ auto sourcemeta::blaze::default_schema_compiler( "http://json-schema.org/draft-06/hyper-schema#", "http://json-schema.org/draft-04/schema#", "http://json-schema.org/draft-04/hyper-schema#"}; - for (const auto &vocabulary : schema_context.vocabularies) { + for (const auto &vocabulary : + schema_context.vocabularies.all_vocabularies()) { if (!SUPPORTED_VOCABULARIES.contains(vocabulary.first) && vocabulary.second) { throw sourcemeta::core::SchemaVocabularyError( @@ -50,22 +52,22 @@ auto sourcemeta::blaze::default_schema_compiler( using namespace sourcemeta::blaze; #define COMPILE(vocabulary, _keyword, handler) \ - if (schema_context.vocabularies.contains(vocabulary) && \ + if (has_vocabulary(schema_context.vocabularies, vocabulary) && \ dynamic_context.keyword == (_keyword)) { \ return internal::handler(context, schema_context, dynamic_context, \ current); \ } #define COMPILE_ANY(vocabulary_1, vocabulary_2, _keyword, handler) \ - if ((schema_context.vocabularies.contains(vocabulary_1) || \ - schema_context.vocabularies.contains(vocabulary_2)) && \ + if ((has_vocabulary(schema_context.vocabularies, vocabulary_1) || \ + has_vocabulary(schema_context.vocabularies, vocabulary_2)) && \ dynamic_context.keyword == (_keyword)) { \ return internal::handler(context, schema_context, dynamic_context, \ current); \ } #define STOP_IF_SIBLING_KEYWORD(vocabulary, _keyword) \ - if (schema_context.vocabularies.contains(vocabulary) && \ + if (has_vocabulary(schema_context.vocabularies, vocabulary) && \ schema_context.schema.is_object() && \ schema_context.schema.defines(_keyword)) { \ return {}; \ diff --git a/src/compiler/include/sourcemeta/blaze/compiler.h b/src/compiler/include/sourcemeta/blaze/compiler.h index b1a8d331e..4a74afe42 100644 --- a/src/compiler/include/sourcemeta/blaze/compiler.h +++ b/src/compiler/include/sourcemeta/blaze/compiler.h @@ -51,7 +51,7 @@ struct SchemaContext { /// disposal to implement a keyword struct DynamicContext { /// The schema keyword - const std::string keyword; + const std::string &keyword; /// The schema base keyword path const sourcemeta::core::Pointer &base_schema_location; /// The base instance location that the keyword must be evaluated to diff --git a/src/compiler/unevaluated.cc b/src/compiler/unevaluated.cc index 49be6e82a..e2251ba85 100644 --- a/src/compiler/unevaluated.cc +++ b/src/compiler/unevaluated.cc @@ -1,7 +1,10 @@ #include +#include "vocabulary_lookup.h" + namespace { using namespace sourcemeta::core; +using sourcemeta::blaze::has_vocabulary; auto find_adjacent_dependencies( const JSON::String ¤t, const JSON &schema, const SchemaFrame &frame, @@ -22,7 +25,8 @@ auto find_adjacent_dependencies( continue; } else if (keywords.contains(property.first)) { // In 2019-09, `additionalItems` takes no effect without `items` - if (subschema_vocabularies.contains( + if (has_vocabulary( + subschema_vocabularies, "https://json-schema.org/draft/2019-09/vocab/applicator") && property.first == "additionalItems" && !subschema.defines("items")) { continue; @@ -154,9 +158,11 @@ auto unevaluated(const JSON &schema, const SchemaFrame &frame, const auto keyword_uri{frame.uri(entry.second, {pair.first})}; SchemaUnevaluatedEntry unevaluated; - if ((subschema_vocabularies.contains( + if ((has_vocabulary( + subschema_vocabularies, "https://json-schema.org/draft/2020-12/vocab/unevaluated") && - subschema_vocabularies.contains( + has_vocabulary( + subschema_vocabularies, "https://json-schema.org/draft/2020-12/vocab/applicator")) && // NOLINTNEXTLINE(bugprone-branch-clone) pair.first == "unevaluatedProperties") { @@ -167,9 +173,11 @@ auto unevaluated(const JSON &schema, const SchemaFrame &frame, entry.second, entry.second, true, unevaluated); result.emplace(keyword_uri, std::move(unevaluated)); } else if ( - (subschema_vocabularies.contains( + (has_vocabulary( + subschema_vocabularies, "https://json-schema.org/draft/2020-12/vocab/unevaluated") && - subschema_vocabularies.contains( + has_vocabulary( + subschema_vocabularies, "https://json-schema.org/draft/2020-12/vocab/applicator")) && pair.first == "unevaluatedItems") { find_adjacent_dependencies( @@ -177,9 +185,9 @@ auto unevaluated(const JSON &schema, const SchemaFrame &frame, {"prefixItems", "items", "contains", "unevaluatedItems"}, entry.second, entry.second, true, unevaluated); result.emplace(keyword_uri, std::move(unevaluated)); - } else if (subschema_vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/" - "applicator") && + } else if (has_vocabulary(subschema_vocabularies, + "https://json-schema.org/draft/2019-09/vocab/" + "applicator") && pair.first == "unevaluatedProperties") { find_adjacent_dependencies( pair.first, schema, frame, walker, resolver, @@ -187,9 +195,9 @@ auto unevaluated(const JSON &schema, const SchemaFrame &frame, "unevaluatedProperties"}, entry.second, entry.second, true, unevaluated); result.emplace(keyword_uri, std::move(unevaluated)); - } else if (subschema_vocabularies.contains( - "https://json-schema.org/draft/2019-09/vocab/" - "applicator") && + } else if (has_vocabulary(subschema_vocabularies, + "https://json-schema.org/draft/2019-09/vocab/" + "applicator") && pair.first == "unevaluatedItems") { find_adjacent_dependencies( pair.first, schema, frame, walker, resolver, diff --git a/src/compiler/vocabulary_lookup.h b/src/compiler/vocabulary_lookup.h new file mode 100644 index 000000000..4384ae3ca --- /dev/null +++ b/src/compiler/vocabulary_lookup.h @@ -0,0 +1,20 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_VOCABULARY_LOOKUP_H_ +#define SOURCEMETA_BLAZE_COMPILER_VOCABULARY_LOOKUP_H_ + +#include + +#include // std::string_view + +namespace sourcemeta::blaze { + +// Optimized vocabulary lookup using bitwise operations +// This directly uses the Vocabularies::contains() method which performs +// O(1) bitwise AND operations for known vocabularies +inline auto has_vocabulary(const sourcemeta::core::Vocabularies &vocabularies, + std::string_view uri) -> bool { + return vocabularies.contains(uri); +} + +} // namespace sourcemeta::blaze + +#endif diff --git a/src/evaluator/evaluator.cc b/src/evaluator/evaluator.cc index 011ba6769..1a503a852 100644 --- a/src/evaluator/evaluator.cc +++ b/src/evaluator/evaluator.cc @@ -65,16 +65,18 @@ auto Evaluator::validate(const Template &schema, assert(this->instance_location.empty()); assert(this->resources.empty()); this->labels.clear(); + // Reserve capacity to avoid rehashing during evaluation + this->labels.reserve(16); - if (schema.track && schema.dynamic) { + if (schema.track && schema.dynamic) [[unlikely]] { this->evaluated_.clear(); return complete::evaluate(instance, *this, schema, nullptr); - } else if (schema.track) { + } else if (schema.track) [[unlikely]] { this->evaluated_.clear(); return track::evaluate(instance, *this, schema); - } else if (schema.dynamic) { + } else if (schema.dynamic) [[unlikely]] { return dynamic::evaluate(instance, *this, schema); - } else { + } else [[likely]] { return fast::evaluate(instance, *this, schema); } } @@ -87,6 +89,7 @@ auto Evaluator::validate(const Template &schema, assert(this->instance_location.empty()); assert(this->resources.empty()); this->labels.clear(); + this->labels.reserve(16); this->evaluated_.clear(); return complete::evaluate(instance, *this, schema, callback); diff --git a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h index a28adad7e..c9a570d1b 100644 --- a/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h +++ b/vendor/core/src/core/jsonschema/include/sourcemeta/core/jsonschema_types.h @@ -11,12 +11,284 @@ #include // std::string #include // std::string_view #include // std::unordered_map +#include // std::vector namespace sourcemeta::core { /// @ingroup jsonschema -/// A set of vocabularies -using Vocabularies = std::unordered_map; +/// Vocabulary enumeration for known JSON Schema vocabularies +enum class KnownVocabulary : uint32_t { + // Pre-vocabulary dialects (treated as vocabularies) + Draft00 = 1u << 0, + Draft00Hyper = 1u << 1, + Draft01 = 1u << 2, + Draft01Hyper = 1u << 3, + Draft02 = 1u << 4, + Draft02Hyper = 1u << 5, + Draft03 = 1u << 6, + Draft03Hyper = 1u << 7, + Draft04 = 1u << 8, + Draft04Hyper = 1u << 9, + Draft06 = 1u << 10, + Draft06Hyper = 1u << 11, + Draft07 = 1u << 12, + Draft07Hyper = 1u << 13, + // 2019-09 vocabularies + Draft201909Core = 1u << 14, + Draft201909Applicator = 1u << 15, + Draft201909Validation = 1u << 16, + Draft201909MetaData = 1u << 17, + Draft201909Format = 1u << 18, + Draft201909Content = 1u << 19, + Draft201909HyperSchema = 1u << 20, + // 2020-12 vocabularies + Draft202012Core = 1u << 21, + Draft202012Applicator = 1u << 22, + Draft202012Unevaluated = 1u << 23, + Draft202012Validation = 1u << 24, + Draft202012MetaData = 1u << 25, + Draft202012FormatAnnotation = 1u << 26, + Draft202012FormatAssertion = 1u << 27, + Draft202012Content = 1u << 28 +}; + +namespace detail { +inline auto uri_to_known_vocabulary(std::string_view uri) + -> std::optional { + using sourcemeta::core::KnownVocabulary; + + // Pre-vocabulary dialects + if (uri == "http://json-schema.org/draft-00/schema#") { + return KnownVocabulary::Draft00; + } + if (uri == "http://json-schema.org/draft-00/hyper-schema#") { + return KnownVocabulary::Draft00Hyper; + } + if (uri == "http://json-schema.org/draft-01/schema#") { + return KnownVocabulary::Draft01; + } + if (uri == "http://json-schema.org/draft-01/hyper-schema#") { + return KnownVocabulary::Draft01Hyper; + } + if (uri == "http://json-schema.org/draft-02/schema#") { + return KnownVocabulary::Draft02; + } + if (uri == "http://json-schema.org/draft-02/hyper-schema#") { + return KnownVocabulary::Draft02Hyper; + } + if (uri == "http://json-schema.org/draft-03/schema#") { + return KnownVocabulary::Draft03; + } + if (uri == "http://json-schema.org/draft-03/hyper-schema#") { + return KnownVocabulary::Draft03Hyper; + } + if (uri == "http://json-schema.org/draft-04/schema#") { + return KnownVocabulary::Draft04; + } + if (uri == "http://json-schema.org/draft-04/hyper-schema#") { + return KnownVocabulary::Draft04Hyper; + } + if (uri == "http://json-schema.org/draft-06/schema#") { + return KnownVocabulary::Draft06; + } + if (uri == "http://json-schema.org/draft-06/hyper-schema#") { + return KnownVocabulary::Draft06Hyper; + } + if (uri == "http://json-schema.org/draft-07/schema#") { + return KnownVocabulary::Draft07; + } + if (uri == "http://json-schema.org/draft-07/hyper-schema#") { + return KnownVocabulary::Draft07Hyper; + } + + // 2019-09 vocabularies + if (uri == "https://json-schema.org/draft/2019-09/vocab/core") { + return KnownVocabulary::Draft201909Core; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/applicator") { + return KnownVocabulary::Draft201909Applicator; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/validation") { + return KnownVocabulary::Draft201909Validation; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/meta-data") { + return KnownVocabulary::Draft201909MetaData; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/format") { + return KnownVocabulary::Draft201909Format; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/content") { + return KnownVocabulary::Draft201909Content; + } + if (uri == "https://json-schema.org/draft/2019-09/vocab/hyper-schema") { + return KnownVocabulary::Draft201909HyperSchema; + } + + // 2020-12 vocabularies + if (uri == "https://json-schema.org/draft/2020-12/vocab/core") { + return KnownVocabulary::Draft202012Core; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/applicator") { + return KnownVocabulary::Draft202012Applicator; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/unevaluated") { + return KnownVocabulary::Draft202012Unevaluated; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/validation") { + return KnownVocabulary::Draft202012Validation; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/meta-data") { + return KnownVocabulary::Draft202012MetaData; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/format-annotation") { + return KnownVocabulary::Draft202012FormatAnnotation; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/format-assertion") { + return KnownVocabulary::Draft202012FormatAssertion; + } + if (uri == "https://json-schema.org/draft/2020-12/vocab/content") { + return KnownVocabulary::Draft202012Content; + } + + return std::nullopt; +} +} // namespace detail + +/// @ingroup jsonschema +/// Optimized vocabulary set using bitflags for known vocabularies +struct Vocabularies { + /// Bitflags for enabled known vocabularies + uint32_t enabled_known = 0; + + /// Bitflags for disabled known vocabularies (for explicit false values) + uint32_t disabled_known = 0; + + /// Fallback storage for custom/unknown vocabularies + std::unordered_map custom; + + /// Check if a vocabulary is enabled + [[nodiscard]] auto contains(std::string_view uri) const -> bool { + const auto maybe_known = detail::uri_to_known_vocabulary(uri); + if (maybe_known.has_value()) { + const auto flag = static_cast(maybe_known.value()); + return (enabled_known & flag) != 0 || (disabled_known & flag) != 0; + } + + const auto key{std::string{uri}}; + const auto iterator{custom.find(key)}; + return iterator != custom.end() && iterator->second; + } + + /// Insert a vocabulary + auto insert(const std::pair &entry) -> void { + const auto maybe_known = detail::uri_to_known_vocabulary(entry.first); + if (maybe_known.has_value()) { + const auto flag = static_cast(maybe_known.value()); + if (entry.second) { + enabled_known |= flag; + disabled_known &= ~flag; + } else { + disabled_known |= flag; + enabled_known &= ~flag; + } + } else { + custom.insert(entry); + } + } + + /// Get vocabulary status by URI + [[nodiscard]] auto find(std::string_view uri) const -> std::optional { + const auto maybe_known = detail::uri_to_known_vocabulary(uri); + if (maybe_known.has_value()) { + const auto flag = static_cast(maybe_known.value()); + if ((enabled_known & flag) != 0) { + return true; + } + if ((disabled_known & flag) != 0) { + return false; + } + return std::nullopt; + } + + const auto key{std::string{uri}}; + const auto iterator{custom.find(key)}; + if (iterator != custom.end()) { + return iterator->second; + } + return std::nullopt; + } + + /// Merge another Vocabularies into this one + auto merge(const Vocabularies &other) -> void { + const uint32_t already_set = enabled_known | disabled_known; + const uint32_t to_enable = other.enabled_known & ~already_set; + const uint32_t to_disable = other.disabled_known & ~already_set; + enabled_known |= to_enable; + disabled_known |= to_disable; + + for (const auto &entry : other.custom) { + custom.insert(entry); + } + } + + /// Get all active (enabled or disabled) vocabularies as key-value pairs + [[nodiscard]] auto all_vocabularies() const + -> std::vector> { + std::vector> result; + result.reserve(32); + + const auto check_and_add = [&](KnownVocabulary vocab, + std::string_view uri) { + const auto flag = static_cast(vocab); + if ((enabled_known & flag) != 0) { + result.emplace_back(std::string{uri}, true); + } else if ((disabled_known & flag) != 0) { + result.emplace_back(std::string{uri}, false); + } + }; + + // Pre-vocabulary dialects + check_and_add(KnownVocabulary::Draft00, "http://json-schema.org/draft-00/schema#"); + check_and_add(KnownVocabulary::Draft00Hyper, "http://json-schema.org/draft-00/hyper-schema#"); + check_and_add(KnownVocabulary::Draft01, "http://json-schema.org/draft-01/schema#"); + check_and_add(KnownVocabulary::Draft01Hyper, "http://json-schema.org/draft-01/hyper-schema#"); + check_and_add(KnownVocabulary::Draft02, "http://json-schema.org/draft-02/schema#"); + check_and_add(KnownVocabulary::Draft02Hyper, "http://json-schema.org/draft-02/hyper-schema#"); + check_and_add(KnownVocabulary::Draft03, "http://json-schema.org/draft-03/schema#"); + check_and_add(KnownVocabulary::Draft03Hyper, "http://json-schema.org/draft-03/hyper-schema#"); + check_and_add(KnownVocabulary::Draft04, "http://json-schema.org/draft-04/schema#"); + check_and_add(KnownVocabulary::Draft04Hyper, "http://json-schema.org/draft-04/hyper-schema#"); + check_and_add(KnownVocabulary::Draft06, "http://json-schema.org/draft-06/schema#"); + check_and_add(KnownVocabulary::Draft06Hyper, "http://json-schema.org/draft-06/hyper-schema#"); + check_and_add(KnownVocabulary::Draft07, "http://json-schema.org/draft-07/schema#"); + check_and_add(KnownVocabulary::Draft07Hyper, "http://json-schema.org/draft-07/hyper-schema#"); + + // 2019-09 vocabularies + check_and_add(KnownVocabulary::Draft201909Core, "https://json-schema.org/draft/2019-09/vocab/core"); + check_and_add(KnownVocabulary::Draft201909Applicator, "https://json-schema.org/draft/2019-09/vocab/applicator"); + check_and_add(KnownVocabulary::Draft201909Validation, "https://json-schema.org/draft/2019-09/vocab/validation"); + check_and_add(KnownVocabulary::Draft201909MetaData, "https://json-schema.org/draft/2019-09/vocab/meta-data"); + check_and_add(KnownVocabulary::Draft201909Format, "https://json-schema.org/draft/2019-09/vocab/format"); + check_and_add(KnownVocabulary::Draft201909Content, "https://json-schema.org/draft/2019-09/vocab/content"); + check_and_add(KnownVocabulary::Draft201909HyperSchema, "https://json-schema.org/draft/2019-09/vocab/hyper-schema"); + + // 2020-12 vocabularies + check_and_add(KnownVocabulary::Draft202012Core, "https://json-schema.org/draft/2020-12/vocab/core"); + check_and_add(KnownVocabulary::Draft202012Applicator, "https://json-schema.org/draft/2020-12/vocab/applicator"); + check_and_add(KnownVocabulary::Draft202012Unevaluated, "https://json-schema.org/draft/2020-12/vocab/unevaluated"); + check_and_add(KnownVocabulary::Draft202012Validation, "https://json-schema.org/draft/2020-12/vocab/validation"); + check_and_add(KnownVocabulary::Draft202012MetaData, "https://json-schema.org/draft/2020-12/vocab/meta-data"); + check_and_add(KnownVocabulary::Draft202012FormatAnnotation, "https://json-schema.org/draft/2020-12/vocab/format-annotation"); + check_and_add(KnownVocabulary::Draft202012FormatAssertion, "https://json-schema.org/draft/2020-12/vocab/format-assertion"); + check_and_add(KnownVocabulary::Draft202012Content, "https://json-schema.org/draft/2020-12/vocab/content"); + + for (const auto &entry : custom) { + result.emplace_back(entry); + } + + return result; + } +}; // Take a URI and get back a schema /// @ingroup jsonschema diff --git a/vendor/core/src/core/jsonschema/jsonschema.cc b/vendor/core/src/core/jsonschema/jsonschema.cc index cf4140310..b0f6a0f92 100644 --- a/vendor/core/src/core/jsonschema/jsonschema.cc +++ b/vendor/core/src/core/jsonschema/jsonschema.cc @@ -4,6 +4,7 @@ #include // std::uint64_t #include // std::numeric_limits #include // std::accumulate +#include // std::optional #include // std::ostringstream #include // std::remove_reference_t #include // std::unordered_map @@ -342,21 +343,24 @@ 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}}; + Vocabularies result; + result.insert({"https://json-schema.org/draft/2020-12/vocab/core", true}); + result.insert({"https://json-schema.org/draft/2020-12/vocab/applicator", true}); + result.insert({"https://json-schema.org/draft/2020-12/vocab/unevaluated", true}); + result.insert({"https://json-schema.org/draft/2020-12/vocab/validation", true}); + result.insert({"https://json-schema.org/draft/2020-12/vocab/meta-data", true}); + result.insert({"https://json-schema.org/draft/2020-12/vocab/format-annotation", true}); + result.insert({"https://json-schema.org/draft/2020-12/vocab/content", true}); + return result; } 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}}; + Vocabularies result; + result.insert({"https://json-schema.org/draft/2019-09/vocab/core", true}); + result.insert({"https://json-schema.org/draft/2019-09/vocab/applicator", true}); + result.insert({"https://json-schema.org/draft/2019-09/vocab/validation", true}); + result.insert({"https://json-schema.org/draft/2019-09/vocab/meta-data", true}); + result.insert({"https://json-schema.org/draft/2019-09/vocab/format", false}); + result.insert({"https://json-schema.org/draft/2019-09/vocab/content", true}); + return result; } } @@ -374,7 +378,9 @@ 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}}; + Vocabularies result; + result.insert({dialect, true}); + return result; } /* @@ -394,7 +400,9 @@ 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}}; + Vocabularies result; + result.insert({base_dialect, true}); + return result; } /* @@ -438,9 +446,12 @@ auto sourcemeta::core::vocabularies(const SchemaResolver &resolver, 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_value = result.find(core); + if (core_value.has_value() && !core_value.value()) { + throw sourcemeta::core::SchemaError( + "The core vocabulary must always be required"); + } } return result; diff --git a/vendor/core/src/extension/alterschema/alterschema.cc b/vendor/core/src/extension/alterschema/alterschema.cc index 34010fa0d..a15c92e06 100644 --- a/vendor/core/src/extension/alterschema/alterschema.cc +++ b/vendor/core/src/extension/alterschema/alterschema.cc @@ -8,8 +8,9 @@ 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) { + const std::set &values) -> bool { + const auto all_vocabs = container.all_vocabularies(); + return std::ranges::any_of(all_vocabs, [&values](const auto &element) { return values.contains(element.first); }); }