diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7494d36..c5536e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,6 +120,12 @@ jobs: - run: > cmake --install ./build --prefix ./build/dist --config Release --verbose --component sourcemeta_core_dev + - run: > + cmake --install ./build --prefix ./build/dist --config Release --verbose + --component sourcemeta_blaze + - run: > + cmake --install ./build --prefix ./build/dist --config Release --verbose + --component sourcemeta_blaze_dev - run: > cmake --install ./build --prefix ./build/dist --config Release --verbose --component sourcemeta_codegen diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e76300..dcaa5de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ if(CODEGEN_INSTALL) endif() find_package(Core REQUIRED) +find_package(Blaze REQUIRED) # Don't force downstream consumers on it if(PROJECT_IS_TOP_LEVEL) diff --git a/DEPENDENCIES b/DEPENDENCIES index b554af3..645994f 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,2 +1,3 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 -core https://github.com/sourcemeta/core b6640a840bf149edca223c9129a71d64474e12fc +core https://github.com/sourcemeta/core c43332629d71475f44d212f140effbf0a46c1492 +blaze https://github.com/sourcemeta/blaze b97dc7b6dca47917f40ff465efa5e068e16e8534 diff --git a/Makefile b/Makefile index 55b0eb3..a891470 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,10 @@ compile: .always --component sourcemeta_core $(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET) --verbose \ --component sourcemeta_core_dev + $(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET) --verbose \ + --component sourcemeta_blaze + $(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET) --verbose \ + --component sourcemeta_blaze_dev $(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET) --verbose \ --component sourcemeta_codegen $(CMAKE) --install ./build --prefix ./build/dist --config $(PRESET) --verbose \ diff --git a/cmake/FindBlaze.cmake b/cmake/FindBlaze.cmake new file mode 100644 index 0000000..58fc376 --- /dev/null +++ b/cmake/FindBlaze.cmake @@ -0,0 +1,11 @@ +if(NOT Blaze_FOUND) + if(CODEGEN_INSTALL) + set(SOURCEMETA_BLAZE_INSTALL ON CACHE BOOL "enable installation") + else() + set(SOURCEMETA_BLAZE_INSTALL OFF CACHE BOOL "disable installation") + endif() + + add_subdirectory("${PROJECT_SOURCE_DIR}/vendor/blaze") + include(Sourcemeta) + set(Blaze_FOUND ON) +endif() diff --git a/config.cmake.in b/config.cmake.in index 2e60452..90113cb 100644 --- a/config.cmake.in +++ b/config.cmake.in @@ -9,7 +9,8 @@ if(NOT CODEGEN_COMPONENTS) endif() include(CMakeFindDependencyMacro) -find_dependency(Core COMPONENTS regex json jsonschema alterschema) +find_dependency(Core COMPONENTS regex json jsonschema) +find_dependency(Blaze COMPONENTS alterschema) foreach(component ${CODEGEN_COMPONENTS}) if(component STREQUAL "ir") diff --git a/src/generator/include/sourcemeta/codegen/generator_typescript.h b/src/generator/include/sourcemeta/codegen/generator_typescript.h index d8fce4d..970b7f5 100644 --- a/src/generator/include/sourcemeta/codegen/generator_typescript.h +++ b/src/generator/include/sourcemeta/codegen/generator_typescript.h @@ -29,6 +29,7 @@ class SOURCEMETA_CODEGEN_GENERATOR_EXPORT TypeScript { auto operator()(const IRReference &entry) -> void; auto operator()(const IRTuple &entry) -> void; auto operator()(const IRUnion &entry) -> void; + auto operator()(const IRIntersection &entry) -> void; private: // Exporting symbols that depends on the standard C++ library is considered diff --git a/src/generator/typescript.cc b/src/generator/typescript.cc index 5105425..b221009 100644 --- a/src/generator/typescript.cc +++ b/src/generator/typescript.cc @@ -287,4 +287,20 @@ auto TypeScript::operator()(const IRUnion &entry) -> void { this->output << ";\n"; } +auto TypeScript::operator()(const IRIntersection &entry) -> void { + this->output << "export type " + << mangle(this->prefix, entry.pointer, entry.symbol, this->cache) + << " =\n"; + + const char *separator{""}; + for (const auto &value : entry.values) { + this->output << separator << " " + << mangle(this->prefix, value.pointer, value.symbol, + this->cache); + separator = " &\n"; + } + + this->output << ";\n"; +} + } // namespace sourcemeta::codegen diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 63f0b18..57983de 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -12,4 +12,4 @@ target_link_libraries(sourcemeta_codegen_ir PUBLIC target_link_libraries(sourcemeta_codegen_ir PUBLIC sourcemeta::core::jsonschema) target_link_libraries(sourcemeta_codegen_ir PRIVATE - sourcemeta::core::alterschema) + sourcemeta::blaze::alterschema) diff --git a/src/ir/include/sourcemeta/codegen/ir.h b/src/ir/include/sourcemeta/codegen/ir.h index ba3dfc2..31a1c2d 100644 --- a/src/ir/include/sourcemeta/codegen/ir.h +++ b/src/ir/include/sourcemeta/codegen/ir.h @@ -56,6 +56,11 @@ struct IRUnion : IRType { std::vector values; }; +/// @ingroup ir +struct IRIntersection : IRType { + std::vector values; +}; + /// @ingroup ir struct IRObjectValue : IRType { bool required; @@ -99,8 +104,8 @@ struct IRReference : IRType { /// @ingroup ir using IREntity = - std::variant; + std::variant; /// @ingroup ir using IRResult = std::vector; diff --git a/src/ir/ir.cc b/src/ir/ir.cc index faffeb5..e0684e5 100644 --- a/src/ir/ir.cc +++ b/src/ir/ir.cc @@ -1,5 +1,5 @@ +#include #include -#include #include // std::ranges::sort #include // assert @@ -7,6 +7,47 @@ #include "ir_default_compiler.h" +namespace { + +auto is_validation_subschema( + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) -> bool { + if (!location.parent.has_value()) { + return false; + } + + const auto &parent{location.parent.value()}; + if (parent.size() >= location.pointer.size()) { + return false; + } + + const auto &keyword_token{location.pointer.at(parent.size())}; + if (!keyword_token.is_property()) { + return false; + } + + const auto parent_location{frame.traverse(parent)}; + if (!parent_location.has_value()) { + return false; + } + + const auto vocabularies{ + frame.vocabularies(parent_location.value().get(), resolver)}; + const auto &walker_result{walker(keyword_token.to_property(), vocabularies)}; + using Type = sourcemeta::core::SchemaKeywordType; + if (walker_result.type == Type::ApplicatorValueTraverseAnyPropertyKey || + walker_result.type == Type::ApplicatorValueTraverseAnyItem) { + return true; + } + + return is_validation_subschema(frame, parent_location.value().get(), walker, + resolver); +} + +} // anonymous namespace + namespace sourcemeta::codegen { auto compile(const sourcemeta::core::JSON &input, @@ -25,9 +66,9 @@ auto compile(const sourcemeta::core::JSON &input, // (2) Canonicalize the schema for easier analysis // -------------------------------------------------------------------------- - sourcemeta::core::SchemaTransformer canonicalizer; - sourcemeta::core::add(canonicalizer, - sourcemeta::core::AlterSchemaMode::Canonicalizer); + sourcemeta::blaze::SchemaTransformer canonicalizer; + sourcemeta::blaze::add(canonicalizer, + sourcemeta::blaze::AlterSchemaMode::Canonicalizer); [[maybe_unused]] const auto canonicalized{canonicalizer.apply( schema, walker, resolver, [](const auto &, const auto, const auto, const auto &, @@ -67,6 +108,12 @@ auto compile(const sourcemeta::core::JSON &input, continue; } + // Skip subschemas under validation-only keywords that do not contribute + // to the type structure (like `contains`) + if (is_validation_subschema(frame, location, walker, resolver)) { + continue; + } + const auto &subschema{sourcemeta::core::get(schema, location.pointer)}; result.push_back(compiler(schema, frame, location, resolver, subschema)); } diff --git a/src/ir/ir_default_compiler.h b/src/ir/ir_default_compiler.h index 8d34866..6d186ac 100644 --- a/src/ir/ir_default_compiler.h +++ b/src/ir/ir_default_compiler.h @@ -56,9 +56,27 @@ auto handle_string(const sourcemeta::core::JSON &schema, const sourcemeta::core::SchemaResolver &, const sourcemeta::core::JSON &subschema) -> IRScalar { ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, - {"$schema", "$id", "$anchor", "$dynamicAnchor", - "$defs", "$vocabulary", "type", "minLength", - "maxLength", "pattern", "format"}); + {"$schema", + "$id", + "$anchor", + "$dynamicAnchor", + "$defs", + "$vocabulary", + "type", + "minLength", + "maxLength", + "pattern", + "format", + "title", + "description", + "default", + "deprecated", + "readOnly", + "writeOnly", + "examples", + "contentEncoding", + "contentMediaType", + "contentSchema"}); return IRScalar{{.pointer = sourcemeta::core::to_pointer(location.pointer), .symbol = symbol(frame, location)}, IRScalarType::String}; @@ -79,7 +97,8 @@ auto handle_object(const sourcemeta::core::JSON &schema, // other properties. Therefore, we whitelist this, but we consider it to // be the responsability of the validator "additionalProperties", "minProperties", "maxProperties", - "propertyNames", "patternProperties"}); + "propertyNames", "patternProperties", "title", "description", "default", + "deprecated", "readOnly", "writeOnly", "examples"}); std::vector> members; @@ -178,8 +197,9 @@ auto handle_integer(const sourcemeta::core::JSON &schema, ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, {"$schema", "$id", "$anchor", "$dynamicAnchor", "$defs", "$vocabulary", "type", "minimum", "maximum", - "exclusiveMinimum", "exclusiveMaximum", - "multipleOf"}); + "exclusiveMinimum", "exclusiveMaximum", "multipleOf", + "title", "description", "default", "deprecated", + "readOnly", "writeOnly", "examples"}); return IRScalar{{.pointer = sourcemeta::core::to_pointer(location.pointer), .symbol = symbol(frame, location)}, IRScalarType::Integer}; @@ -194,8 +214,9 @@ auto handle_number(const sourcemeta::core::JSON &schema, ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, {"$schema", "$id", "$anchor", "$dynamicAnchor", "$defs", "$vocabulary", "type", "minimum", "maximum", - "exclusiveMinimum", "exclusiveMaximum", - "multipleOf"}); + "exclusiveMinimum", "exclusiveMaximum", "multipleOf", + "title", "description", "default", "deprecated", + "readOnly", "writeOnly", "examples"}); return IRScalar{{.pointer = sourcemeta::core::to_pointer(location.pointer), .symbol = symbol(frame, location)}, IRScalarType::Number}; @@ -208,10 +229,14 @@ auto handle_array(const sourcemeta::core::JSON &schema, const sourcemeta::core::SchemaResolver &, const sourcemeta::core::JSON &subschema) -> IREntity { ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, - {"$schema", "$id", "$anchor", "$dynamicAnchor", - "$defs", "$vocabulary", "type", "items", "minItems", - "maxItems", "uniqueItems", "contains", "minContains", - "maxContains", "additionalItems", "prefixItems"}); + {"$schema", "$id", "$anchor", + "$dynamicAnchor", "$defs", "$vocabulary", + "type", "items", "minItems", + "maxItems", "uniqueItems", "contains", + "minContains", "maxContains", "additionalItems", + "prefixItems", "title", "description", + "default", "deprecated", "readOnly", + "writeOnly", "examples"}); using Vocabularies = sourcemeta::core::Vocabularies; @@ -326,7 +351,9 @@ auto handle_enum(const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &subschema) -> IREntity { ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, {"$schema", "$id", "$anchor", "$dynamicAnchor", - "$defs", "$vocabulary", "enum"}); + "$defs", "$vocabulary", "enum", "title", + "description", "default", "deprecated", "readOnly", + "writeOnly", "examples"}); const auto &enum_json{subschema.at("enum")}; // Boolean and null special cases @@ -360,9 +387,11 @@ auto handle_anyof(const sourcemeta::core::JSON &schema, const sourcemeta::core::Vocabularies &, const sourcemeta::core::SchemaResolver &, const sourcemeta::core::JSON &subschema) -> IREntity { - ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, - {"$schema", "$id", "$anchor", "$dynamicAnchor", - "$defs", "$vocabulary", "anyOf"}); + ONLY_WHITELIST_KEYWORDS( + schema, subschema, location.pointer, + {"$schema", "$id", "$anchor", "$dynamicAnchor", "$defs", "$vocabulary", + "anyOf", "title", "description", "default", "deprecated", "readOnly", + "writeOnly", "examples", "unevaluatedProperties", "unevaluatedItems"}); const auto &any_of{subschema.at("anyOf")}; assert(any_of.is_array()); @@ -394,9 +423,11 @@ auto handle_oneof(const sourcemeta::core::JSON &schema, const sourcemeta::core::Vocabularies &, const sourcemeta::core::SchemaResolver &, const sourcemeta::core::JSON &subschema) -> IREntity { - ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, - {"$schema", "$id", "$anchor", "$dynamicAnchor", - "$defs", "$vocabulary", "oneOf"}); + ONLY_WHITELIST_KEYWORDS( + schema, subschema, location.pointer, + {"$schema", "$id", "$anchor", "$dynamicAnchor", "$defs", "$vocabulary", + "oneOf", "title", "description", "default", "deprecated", "readOnly", + "writeOnly", "examples", "unevaluatedProperties", "unevaluatedItems"}); const auto &one_of{subschema.at("oneOf")}; assert(one_of.is_array()); @@ -430,7 +461,9 @@ auto handle_ref(const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &subschema) -> IREntity { ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, {"$schema", "$id", "$anchor", "$dynamicAnchor", - "$defs", "$vocabulary", "$ref"}); + "$defs", "$vocabulary", "$ref", "title", + "description", "default", "deprecated", "readOnly", + "writeOnly", "examples"}); auto ref_pointer{sourcemeta::core::to_pointer(location.pointer)}; ref_pointer.push_back("$ref"); @@ -465,7 +498,9 @@ auto handle_dynamic_ref(const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &subschema) -> IREntity { ONLY_WHITELIST_KEYWORDS(schema, subschema, location.pointer, {"$schema", "$id", "$anchor", "$dynamicAnchor", - "$defs", "$vocabulary", "$dynamicRef"}); + "$defs", "$vocabulary", "$dynamicRef", "title", + "description", "default", "deprecated", "readOnly", + "writeOnly", "examples"}); auto ref_pointer{sourcemeta::core::to_pointer(location.pointer)}; ref_pointer.push_back("$dynamicRef"); @@ -525,6 +560,58 @@ auto handle_dynamic_ref(const sourcemeta::core::JSON &schema, std::move(branches)}; } +auto handle_allof(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::Vocabularies &, + const sourcemeta::core::SchemaResolver &, + const sourcemeta::core::JSON &subschema) -> IREntity { + ONLY_WHITELIST_KEYWORDS( + schema, subschema, location.pointer, + {"$schema", "$id", "$anchor", "$dynamicAnchor", "$defs", "$vocabulary", + "allOf", "title", "description", "default", "deprecated", "readOnly", + "writeOnly", "examples", "unevaluatedProperties", "unevaluatedItems"}); + + const auto &all_of{subschema.at("allOf")}; + assert(all_of.is_array()); + + if (all_of.size() == 1) { + auto target_pointer{sourcemeta::core::to_pointer(location.pointer)}; + target_pointer.push_back("allOf"); + target_pointer.push_back(static_cast(0)); + + const auto target_location{ + frame.traverse(sourcemeta::core::to_weak_pointer(target_pointer))}; + assert(target_location.has_value()); + + return IRReference{ + {.pointer = sourcemeta::core::to_pointer(location.pointer), + .symbol = symbol(frame, location)}, + {.pointer = std::move(target_pointer), + .symbol = symbol(frame, target_location.value().get())}}; + } + + std::vector branches; + for (std::size_t index = 0; index < all_of.size(); ++index) { + auto branch_pointer{sourcemeta::core::to_pointer(location.pointer)}; + branch_pointer.push_back("allOf"); + branch_pointer.push_back(index); + + const auto branch_location{ + frame.traverse(sourcemeta::core::to_weak_pointer(branch_pointer))}; + assert(branch_location.has_value()); + + branches.push_back( + {.pointer = std::move(branch_pointer), + .symbol = symbol(frame, branch_location.value().get())}); + } + + return IRIntersection{ + {.pointer = sourcemeta::core::to_pointer(location.pointer), + .symbol = symbol(frame, location)}, + std::move(branches)}; +} + auto default_compiler(const sourcemeta::core::JSON &schema, const sourcemeta::core::SchemaFrame &frame, const sourcemeta::core::SchemaFrame::Location &location, @@ -607,12 +694,21 @@ auto default_compiler(const sourcemeta::core::JSON &schema, } else if (subschema.defines("oneOf")) { return handle_oneof(schema, frame, location, vocabularies, resolver, subschema); + } else if (subschema.defines("allOf")) { + return handle_allof(schema, frame, location, vocabularies, resolver, + subschema); } else if (subschema.defines("$dynamicRef")) { return handle_dynamic_ref(schema, frame, location, vocabularies, resolver, subschema); } else if (subschema.defines("$ref")) { return handle_ref(schema, frame, location, vocabularies, resolver, subschema); + } else if (subschema.defines("if")) { + throw UnsupportedKeywordError(schema, location.pointer, "if", + "Unsupported keyword in subschema"); + } else if (subschema.defines("not")) { + throw UnsupportedKeywordError(schema, location.pointer, "not", + "Unsupported keyword in subschema"); } else { throw UnexpectedSchemaError(schema, location.pointer, "Unsupported schema"); } diff --git a/test/e2e/typescript/2020-12/dynamic_ref_generic_list/expected.d.ts b/test/e2e/typescript/2020-12/dynamic_ref_generic_list/expected.d.ts index c86f551..d10c467 100644 --- a/test/e2e/typescript/2020-12/dynamic_ref_generic_list/expected.d.ts +++ b/test/e2e/typescript/2020-12/dynamic_ref_generic_list/expected.d.ts @@ -1,3 +1,5 @@ +export type StringList_0 = StringListGenericList; + export type StringListStringItem = string; export type StringListGenericListItems = @@ -28,4 +30,4 @@ export type StringListGenericListDefaultItem = export type StringListGenericList = StringListGenericListItems[]; -export type StringList = StringListGenericList; +export type StringList = StringList_0; diff --git a/test/e2e/typescript/2020-12/dynamic_ref_multiple_anchors/expected.d.ts b/test/e2e/typescript/2020-12/dynamic_ref_multiple_anchors/expected.d.ts index ed91cca..aff68af 100644 --- a/test/e2e/typescript/2020-12/dynamic_ref_multiple_anchors/expected.d.ts +++ b/test/e2e/typescript/2020-12/dynamic_ref_multiple_anchors/expected.d.ts @@ -1,3 +1,5 @@ +export type Root_0 = RootList; + export type RootStringItem = string; export type RootListItems = @@ -8,4 +10,4 @@ export type RootListDefaultItem = number; export type RootList = RootListItems[]; -export type Root = RootList; +export type Root = Root_0; diff --git a/test/ir/ir_2020_12_test.cc b/test/ir/ir_2020_12_test.cc index a7f048c..2d7101b 100644 --- a/test/ir/ir_2020_12_test.cc +++ b/test/ir/ir_2020_12_test.cc @@ -1364,15 +1364,16 @@ TEST(IR_2020_12, dynamic_ref_multiple_anchors) { using namespace sourcemeta::codegen; - ASSERT_EQ(result.size(), 5); - EXPECT_IR_SCALAR(result, 0, String, "/$defs/stringItem"); - EXPECT_TRUE(std::holds_alternative(result.at(1))); - EXPECT_AS_STRING(std::get(result.at(1)).pointer, + ASSERT_EQ(result.size(), 6); + EXPECT_IR_REFERENCE(result, 0, "/allOf/0", "/$defs/list"); + EXPECT_IR_SCALAR(result, 1, String, "/$defs/stringItem"); + EXPECT_TRUE(std::holds_alternative(result.at(2))); + EXPECT_AS_STRING(std::get(result.at(2)).pointer, "/$defs/list/items"); - EXPECT_EQ(std::get(result.at(1)).values.size(), 2); - EXPECT_IR_SCALAR(result, 2, Number, "/$defs/list/$defs/defaultItem"); - EXPECT_IR_ARRAY(result, 3, "/$defs/list", "/$defs/list/items"); - EXPECT_IR_REFERENCE(result, 4, "", "/$defs/list"); + EXPECT_EQ(std::get(result.at(2)).values.size(), 2); + EXPECT_IR_SCALAR(result, 3, Number, "/$defs/list/$defs/defaultItem"); + EXPECT_IR_ARRAY(result, 4, "/$defs/list", "/$defs/list/items"); + EXPECT_IR_REFERENCE(result, 5, "", "/allOf/0"); } TEST(IR_2020_12, dynamic_anchor_on_typed_schema) { diff --git a/test/ir/ir_test.cc b/test/ir/ir_test.cc index d7e7797..302b78c 100644 --- a/test/ir/ir_test.cc +++ b/test/ir/ir_test.cc @@ -67,5 +67,5 @@ TEST(IR, unexpected_schema_error_unsupported_shape) { sourcemeta::codegen::compile(schema, sourcemeta::core::schema_walker, sourcemeta::core::schema_resolver, sourcemeta::codegen::default_compiler), - sourcemeta::codegen::UnexpectedSchemaError); + sourcemeta::codegen::UnsupportedKeywordError); } diff --git a/vendor/blaze/CMakeLists.txt b/vendor/blaze/CMakeLists.txt new file mode 100644 index 0000000..8a4459f --- /dev/null +++ b/vendor/blaze/CMakeLists.txt @@ -0,0 +1,148 @@ +cmake_minimum_required(VERSION 3.16) +project(blaze VERSION 0.0.1 LANGUAGES CXX + DESCRIPTION "The ultra high-performance JSON Schema evaluator" + HOMEPAGE_URL "https://github.com/sourcemeta/blaze") +list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") + +# Options +option(BLAZE_COMPILER "Build the Blaze compiler library" ON) +option(BLAZE_EVALUATOR "Build the Blaze evaluator library" ON) +option(BLAZE_OUTPUT "Build the Blaze output formats library" ON) +option(BLAZE_TEST "Build the Blaze test runner library" ON) +option(BLAZE_CONFIGURATION "Build the Blaze configuration file library" ON) +option(BLAZE_ALTERSCHEMA "Build the Blaze alterschema rule library" ON) +option(BLAZE_TESTS "Build the Blaze tests" OFF) +option(BLAZE_BENCHMARK "Build the Blaze benchmarks" OFF) +option(BLAZE_CONTRIB "Build the Blaze contrib programs" OFF) +option(BLAZE_DOCS "Build the Blaze docs" OFF) +option(BLAZE_INSTALL "Install the Blaze library" ON) +option(BLAZE_ADDRESS_SANITIZER "Build Blaze with an address sanitizer" OFF) +option(BLAZE_UNDEFINED_SANITIZER "Build Blaze with an undefined behavior sanitizer" OFF) + +if(BLAZE_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + configure_package_config_file( + config.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" + COMPATIBILITY SameMajorVersion) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}" + COMPONENT sourcemeta_blaze_dev) +endif() + +find_package(Core REQUIRED) + +# Don't force downstream consumers on it +if(PROJECT_IS_TOP_LEVEL) + sourcemeta_enable_simd() +endif() + +if(BLAZE_COMPILER) + add_subdirectory(src/compiler) +endif() + +if(BLAZE_EVALUATOR) + add_subdirectory(src/evaluator) +endif() + +if(BLAZE_OUTPUT) + add_subdirectory(src/output) +endif() + +if(BLAZE_TEST) + add_subdirectory(src/test) +endif() + +if(BLAZE_CONFIGURATION) + add_subdirectory(src/configuration) +endif() + +if(BLAZE_ALTERSCHEMA) + add_subdirectory(src/alterschema) +endif() + +if(BLAZE_CONTRIB) + add_subdirectory(contrib) +endif() + +if(BLAZE_ADDRESS_SANITIZER) + sourcemeta_sanitizer(TYPE address) +elseif(BLAZE_UNDEFINED_SANITIZER) + sourcemeta_sanitizer(TYPE undefined) +endif() + +if(BLAZE_DOCS) + sourcemeta_target_doxygen(CONFIG "${PROJECT_SOURCE_DIR}/doxygen/Doxyfile.in" + OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/website") +endif() + +if(PROJECT_IS_TOP_LEVEL) + sourcemeta_target_clang_format(SOURCES + contrib/*.cc + src/*.h src/*.cc + benchmark/*.h benchmark/*.cc + test/*.h test/*.cc) + + find_package(Python3 COMPONENTS Interpreter) + if(Python3_FOUND) + add_custom_target(blaze_format_trace_json + COMMAND "${Python3_EXECUTABLE}" + "${PROJECT_SOURCE_DIR}/contrib/format_trace_json.py" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_draft4.json" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_draft6.json" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_draft7.json" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_2019_09.json" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_2020_12.json" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_openapi_3_1.json" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_openapi_3_2.json" + COMMENT "Formatting trace JSON test files" + VERBATIM) + endif() +endif() + +# Testing +if(BLAZE_TESTS) + enable_testing() + + if(BLAZE_COMPILER) + add_subdirectory(test/compiler) + endif() + + if(BLAZE_EVALUATOR) + add_subdirectory(test/evaluator) + endif() + + if(BLAZE_OUTPUT) + add_subdirectory(test/output) + endif() + + if(BLAZE_TEST) + add_subdirectory(test/test) + endif() + + if(BLAZE_CONFIGURATION) + add_subdirectory(test/configuration) + endif() + + if(BLAZE_ALTERSCHEMA) + add_subdirectory(test/alterschema) + endif() + + if(PROJECT_IS_TOP_LEVEL) + # Otherwise we need the child project to link + # against the sanitizers too. + if(NOT BLAZE_ADDRESS_SANITIZER AND NOT BLAZE_UNDEFINED_SANITIZER) + add_subdirectory(test/packaging) + endif() + endif() + + if(BLAZE_BENCHMARK) + add_subdirectory(benchmark) + endif() +endif() diff --git a/vendor/blaze/DEPENDENCIES b/vendor/blaze/DEPENDENCIES new file mode 100644 index 0000000..ea6677d --- /dev/null +++ b/vendor/blaze/DEPENDENCIES @@ -0,0 +1,3 @@ +vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 +core https://github.com/sourcemeta/core 1d572ae3a7e1e4909147391654b1a1eda3d09f55 +jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite 06481b143722c8c06671bd40dcde99b422ffd531 diff --git a/vendor/blaze/LICENSE b/vendor/blaze/LICENSE new file mode 100644 index 0000000..9348973 --- /dev/null +++ b/vendor/blaze/LICENSE @@ -0,0 +1,12 @@ +This software is dual-licensed: you can redistribute it and/or modify it under +the terms of the GNU Affero General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. For the terms of this license, see +. + +You are free to use this software under the terms of the GNU Affero General +Public License WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +Alternatively, you can use this software under a commercial license, as set out +in . diff --git a/vendor/blaze/config.cmake.in b/vendor/blaze/config.cmake.in new file mode 100644 index 0000000..8bcccb7 --- /dev/null +++ b/vendor/blaze/config.cmake.in @@ -0,0 +1,43 @@ +@PACKAGE_INIT@ + +# Support both casing styles +list(APPEND BLAZE_COMPONENTS ${Blaze_FIND_COMPONENTS}) +list(APPEND BLAZE_COMPONENTS ${blaze_FIND_COMPONENTS}) +if(NOT BLAZE_COMPONENTS) + list(APPEND BLAZE_COMPONENTS compiler) + list(APPEND BLAZE_COMPONENTS evaluator) + list(APPEND BLAZE_COMPONENTS output) + list(APPEND BLAZE_COMPONENTS test) + list(APPEND BLAZE_COMPONENTS configuration) + list(APPEND BLAZE_COMPONENTS alterschema) +endif() + +include(CMakeFindDependencyMacro) +find_dependency(Core COMPONENTS regex uri json jsonpointer jsonschema io yaml crypto) + +foreach(component ${BLAZE_COMPONENTS}) + if(component STREQUAL "compiler") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_evaluator.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_compiler.cmake") + elseif(component STREQUAL "evaluator") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_evaluator.cmake") + elseif(component STREQUAL "test") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_evaluator.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_compiler.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_test.cmake") + elseif(component STREQUAL "output") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_evaluator.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_output.cmake") + elseif(component STREQUAL "configuration") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_configuration.cmake") + elseif(component STREQUAL "alterschema") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_evaluator.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_compiler.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_output.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_blaze_alterschema.cmake") + else() + message(FATAL_ERROR "Unknown Blaze component: ${component}") + endif() +endforeach() + +check_required_components("@PROJECT_NAME@") diff --git a/vendor/blaze/ports/javascript/LICENSE b/vendor/blaze/ports/javascript/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/vendor/blaze/ports/javascript/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/vendor/blaze/ports/javascript/README.md b/vendor/blaze/ports/javascript/README.md new file mode 100644 index 0000000..4f6d205 --- /dev/null +++ b/vendor/blaze/ports/javascript/README.md @@ -0,0 +1,140 @@ +# @sourcemeta/blaze + +[![NPM Version](https://img.shields.io/npm/v/@sourcemeta/blaze)](https://www.npmjs.com/package/@sourcemeta/blaze) +[![NPM Downloads](https://img.shields.io/npm/dm/%40sourcemeta%2Fblaze)](https://www.npmjs.com/package/@sourcemeta/blaze) +[![GitHub contributors](https://img.shields.io/github/contributors/sourcemeta/blaze.svg)](https://github.com/sourcemeta/blaze/graphs/contributors/) + +A pure JavaScript port of the evaluator from +[Blaze](https://github.com/sourcemeta/blaze), a high-performance +C++ JSON Schema validator. Zero dependencies. Supports Draft 4, +Draft 6, Draft 7, 2019-09, and 2020-12 with schema-specific code +generation for near-native speed. + +## Install + +```sh +npm install --save @sourcemeta/blaze +``` + +## Usage + +Blaze evaluates pre-compiled schema templates. Compile your JSON Schema using +the [JSON Schema CLI](https://github.com/sourcemeta/jsonschema) (see the +[`compile`](https://github.com/sourcemeta/jsonschema/blob/main/docs/compile.markdown) +command): + +```sh +npm install --global @sourcemeta/jsonschema + +cat > schema.json <<'EOF' +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "type": "object", + "properties": { + "name": { "type": "string" }, + "age": { "type": "integer" } + }, + "required": [ "name" ] +} +EOF + +jsonschema compile schema.json --fast > template.json +``` + +Then validate instances: + +```javascript +import { readFileSync } from "node:fs"; +import { Blaze } from "@sourcemeta/blaze"; + +const template = + JSON.parse(readFileSync("template.json", "utf-8")); +const evaluator = new Blaze(template); + +// true or false +console.log(evaluator.validate({ name: "John", age: 30 })); +``` + +With an evaluation callback for tracing: + +```javascript +const instance = { name: "John", age: 30 }; +const result = evaluator.validate(instance, + (type, valid, instruction, + evaluatePath, instanceLocation, annotation) => { + console.log(type, evaluatePath, + instanceLocation, valid); + }); +console.log(result); // true or false +``` + +### Parsing large integers + +JavaScript's +[`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) +silently truncates integers that exceed +[`Number.MAX_SAFE_INTEGER`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER) +to IEEE 754 double-precision floats. If your schemas or instances contain large +integers, pass `Blaze.reviver` to `JSON.parse` to preserve them as +[`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) +values. This relies on the +[`JSON.parse` source text +access](https://github.com/tc39/proposal-json-parse-with-source) proposal +(Stage 4, shipped in all major engines): + +```javascript +const template = + JSON.parse(readFileSync("template.json", "utf-8"), Blaze.reviver); +const evaluator = new Blaze(template); + +const instance = + JSON.parse(readFileSync("instance.json", "utf-8"), Blaze.reviver); +console.log(evaluator.validate(instance)); +``` + +The evaluator handles `BigInt` values natively in all numeric instructions. +Without a reviver, large integers are silently truncated and validation may +produce incorrect results for affected values. + +## Why compile separately? + +Unlike validators that compile and evaluate in a single step, Blaze separates +the two. You compile your schemas ahead of time using the [JSON Schema +CLI](https://github.com/sourcemeta/jsonschema) (`jsonschema compile +schema.json`) on CI or at build time. + +The compilation step analyses the schema, resolves all references, and produces +a set of optimised low-level instructions serialised as JSON that every +evaluator can rely on. This evaluator package then executes those instructions +against your data, with no knowledge of JSON Schema itself. + +- **Consistent validation across languages.** The same compiled schema produces + identical results whether evaluated in JavaScript, Python, Java, or C++ (we + are working hard increase the number of ports) so you never have to rely on + multiple implementations with different interpretations of the standard + +- **No schema processing at startup.** The evaluator loads pre-compiled + instructions. There is no reference resolution, or vocabulary processing at + runtime + +- **Schema governance.** Compiled schemas can be managed, versioned, and + distributed centrally via [Sourcemeta One](https://one.sourcemeta.com), + ensuring all consumers validate against the same compiled artifact + +## Limitations + +**No arbitrary-precision real number support.** Large integers can be preserved +as `BigInt` using a reviver (see above), but JavaScript has no equivalent type +for arbitrary-precision real numbers. `JSON.parse` truncates values like +`0.1000000000000000000000000000000001` to IEEE 754 doubles, and there is no +reviver-based workaround: + +``` +$ node --eval "console.log(JSON.parse('0.1000000000000000000000000000000001'))" +0.1 +``` + +Numeric keywords like `multipleOf` that depend on exact decimal arithmetic may +produce incorrect results for real values that lose precision during parsing. +The TC39 [Decimal proposal](https://github.com/tc39/proposal-decimal) (Stage 2) +aims to address this in the future. diff --git a/vendor/blaze/ports/javascript/index.d.mts b/vendor/blaze/ports/javascript/index.d.mts new file mode 100644 index 0000000..f1bc892 --- /dev/null +++ b/vendor/blaze/ports/javascript/index.d.mts @@ -0,0 +1,20 @@ +export type Template = Array; + +export type EvaluationCallback = ( + type: "pre" | "post", + valid: boolean, + instruction: unknown[], + evaluatePath: string, + instanceLocation: string, + annotation: unknown +) => void; + +export declare class Blaze { + static reviver( + key: string, + value: unknown, + context: { source: string } + ): unknown; + constructor(template: Template); + validate(instance: unknown, callback?: EvaluationCallback): boolean; +} diff --git a/vendor/blaze/ports/javascript/index.mjs b/vendor/blaze/ports/javascript/index.mjs new file mode 100644 index 0000000..a070777 --- /dev/null +++ b/vendor/blaze/ports/javascript/index.mjs @@ -0,0 +1,3970 @@ +const JSON_VERSION = 4; +const DEPTH_LIMIT = 300; +const ANNOTATION_EMIT = 49; +const ANNOTATION_TO_PARENT = 50; +const ANNOTATION_BASENAME_TO_PARENT = 51; +const CONTROL_GROUP_START = 92; +const CONTROL_EVALUATE_END = 96; +const URI_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/; + +function buildJsonPointer(tokens, length) { + if (length === 0) return ''; + let result = ''; + for (let index = 0; index < length; index++) { + const token = String(tokens[index]); + result += '/' + token.replaceAll('~', '~0').replaceAll('/', '~1'); + } + return result; +} + +const Type = { + Null: 0, + Boolean: 1, + Integer: 2, + Real: 3, + String: 4, + Array: 5, + Object: 6, + Decimal: 7 +}; + +function jsonTypeOf(value) { + if (value === null) return Type.Null; + switch (typeof value) { + case 'boolean': return Type.Boolean; + case 'number': return Number.isInteger(value) ? Type.Integer : Type.Real; + case 'bigint': return Type.Integer; + case 'string': return Type.String; + case 'object': return Array.isArray(value) ? Type.Array : Type.Object; + default: return Type.Null; + } +} + +function isIntegral(value) { + return typeof value === 'bigint' || + (typeof value === 'number' && Number.isFinite(value) && Math.floor(value) === value); +} + +function resolveInstance(instance, relativeInstanceLocation) { + const length = relativeInstanceLocation.length; + if (length === 0) return instance; + if (length === 1) { + if (instance === undefined || instance === null) return undefined; + return instance[relativeInstanceLocation[0]]; + } + let current = instance; + for (let index = 0; index < length; index++) { + if (current === undefined || current === null) return undefined; + current = current[relativeInstanceLocation[index]]; + } + return current; +} + +function prepareInstruction(instruction) { + const wrapper = instruction[5]; + if (wrapper === undefined || wrapper === null) { + instruction[5] = null; + } else { + const typeIndex = wrapper[0]; + if (typeIndex === 0 || wrapper.length === 1) { + instruction[5] = null; + } else { + const payload = wrapper[1]; + switch (typeIndex) { + case 4: + instruction[5] = payload[0]; + break; + case 9: + try { instruction[5] = new RegExp(payload, 'u'); } + catch { instruction[5] = new RegExp(payload); } + break; + case 13: { + if (Array.isArray(payload)) { + const object = Object.create(null); + for (let index = 0; index < payload.length; index++) { + object[payload[index][0]] = payload[index][1]; + } + instruction[5] = object; + } else { + instruction[5] = payload; + } + break; + } + case 15: { + if (Array.isArray(payload)) { + const object = Object.create(null); + for (let index = 0; index < payload.length; index++) { + object[payload[index][0]] = payload[index][1]; + } + instruction[5] = object; + } else { + instruction[5] = payload; + } + break; + } + case 16: { + payload[0] = new Set(payload[0]); + const regexes = payload[2]; + for (let index = 0; index < regexes.length; index++) { + try { regexes[index] = new RegExp(regexes[index], 'u'); } + catch { regexes[index] = new RegExp(regexes[index]); } + } + instruction[5] = payload; + break; + } + default: + instruction[5] = payload; + break; + } + } + } + + const opcode = instruction[0]; + if (opcode === 27 && Array.isArray(instruction[5])) { + const values = instruction[5]; + let allPrimitive = true; + for (let index = 0; index < values.length; index++) { + const element = values[index]; + if (element !== null && typeof element === 'object') { + allPrimitive = false; + break; + } + } + if (allPrimitive) { + instruction[5] = { set: new Set(values), values, primitive: true }; + } + } + + if (instruction.length < 7) { + instruction.push(undefined); + } + + instruction.push(handlers[opcode] || null); + + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + prepareInstruction(children[index]); + } + } +} + +function resolveJumpTargets(instructions, targets) { + for (let index = 0; index < instructions.length; index++) { + const instruction = instructions[index]; + if (instruction[0] === 98) { + const targetIndex = instruction[5]; + if (targetIndex < targets.length) { + instruction[5] = targets[targetIndex]; + } + } + const children = instruction[6]; + if (children) { + resolveJumpTargets(children, targets); + } + } +} + +const FNV_OFFSET = 14695981039346656037n; +const FNV_PRIME = 1099511628211n; +const MASK_53 = (1n << 53n) - 1n; + +function blazeHash(resource, fragment) { + let result = FNV_OFFSET & MASK_53; + for (let index = 0; index < fragment.length; index++) { + result ^= BigInt(fragment.charCodeAt(index)); + result = (result * FNV_PRIME) & MASK_53; + } + return Number((BigInt(resource) + result) & MASK_53); +} + +function collectAnchorNames(targets, result) { + for (let targetIndex = 0; targetIndex < targets.length; targetIndex++) { + collectAnchorNamesFromInstructions(targets[targetIndex], result); + } +} + +function collectAnchorNamesFromInstructions(instructions, result) { + for (let index = 0; index < instructions.length; index++) { + const instruction = instructions[index]; + if (instruction[0] === 97 && typeof instruction[5] === 'string') { + result.add(instruction[5]); + } + if (instruction[6]) { + collectAnchorNamesFromInstructions(instruction[6], result); + } + } +} + +function compile(template) { + const targets = template[3]; + for (let targetIndex = 0; targetIndex < targets.length; targetIndex++) { + const target = targets[targetIndex]; + for (let index = 0; index < target.length; index++) { + prepareInstruction(target[index]); + } + } + + for (let targetIndex = 0; targetIndex < targets.length; targetIndex++) { + resolveJumpTargets(targets[targetIndex], targets); + } + + const labels = new Map(); + const rawLabels = template[4]; + for (let index = 0; index < rawLabels.length; index++) { + const pair = rawLabels[index]; + labels.set(pair[0], pair[1]); + } + + const anchors = new Map(); + if (template[1]) { + const anchorNames = new Set(); + collectAnchorNames(targets, anchorNames); + const resourceCount = targets.length; + for (const anchor of anchorNames) { + for (let resource = 0; resource <= resourceCount; resource++) { + const hash = blazeHash(resource, anchor); + const targetIndex = labels.get(hash); + if (targetIndex !== undefined) { + anchors.set(resource + ':' + anchor, targets[targetIndex]); + } + } + } + } + + template[4] = labels; + template[5] = anchors; + return template; +} + +function emitResolve(varName, instanceExpr, relInstance) { + if (relInstance.length === 0) return `var ${varName}=${instanceExpr};`; + if (relInstance.length === 1) return `var ${varName}=${instanceExpr}==null?void 0:${instanceExpr}[${JSON.stringify(relInstance[0])}];`; + return null; +} + +function compileInstructionToCode(instruction, captures, visited, budget) { + if (budget[0] <= 0) { var ci = captures.length; captures.push(instruction); return 'return _fh['+instruction[0]+'](_c['+ci+'],i,d,_t,_v);'; } + var opcode = instruction[0], ri = instruction[2], value = instruction[5], children = instruction[6]; + function R(v) { return emitResolve(v, 'i', ri); } + function inlineCondition(child, tv) { + var op = child[0], cr = child[2], cv = child[5]; + if (cr.length !== 0) return null; + switch (op) { + case 11: return '{if(_es(' + tv + ')!==' + cv + ')return false;}'; + case 42: return 'if(' + tv + '!=null&&typeof ' + tv + "==='object'&&!Array.isArray(" + tv + ')){' + emitResolve('t', tv, cr) + 'if(t!==void 0&&_es(t)!==' + cv + ')return false;}'; + default: return null; + } + } + function seq(ci, tv) { var c = ''; for (var j = 0; j < ci.length; j++) { var inl = inlineCondition(ci[j], tv); if (inl !== null) { budget[0] -= inl.length; c += inl; continue; } var r = compileInstructionToCode(ci[j], captures, visited, budget); if (r === null) { var idx = captures.length; captures.push(ci[j]); c += 'if(!_e(_c['+idx+'],'+tv+',d+1,_t,_v))return false;'; } else { budget[0] -= r.length; c += 'if(!(function(i,d,_t,_v){'+r+'})('+tv+',d+1,_t,_v))return false;'; } } return c; } + function lb(ci, iv) { return seq(ci, iv); } + function fb(op) { var ci = captures.length; captures.push(instruction); return 'return _fh['+op+'](_c['+ci+'],i,d,_t,_v);'; } + function cc(child, tv) { var r = compileInstructionToCode(child, captures, visited, budget); if (r === null) { var ci = captures.length; captures.push(child); return '_e(_c['+ci+'],'+tv+',d+1,_t,_v)'; } budget[0] -= r.length; return '(function(i,d,_t,_v){'+r+'})('+tv+',d+1,_t,_v)'; } + var IO = "if(i==null||typeof i!=='object'||Array.isArray(i))return true;"; + var TO = "if(t==null||typeof t!=='object'||Array.isArray(t))"; + switch (opcode) { + case 0: return 'return false;'; + case 1: { var r=R('t'); return r?r+TO+'return true;return Object.hasOwn(t,'+JSON.stringify(value)+');':null; } + case 2: { var r=R('t'); return r?r+"return t!=null&&typeof t==='object'&&!Array.isArray(t)&&Object.hasOwn(t,"+JSON.stringify(value)+');':null; } + case 3: { var r=R('t'); if(!r)return null; var c=r+TO+'return true;'; for(var j=0;j'+value[1]+')return false;':'')+'return true;':null; } + case 14: { var r=R('t'); return r?r+"return typeof t==='string'&&_ul(t)<="+value+';':null; } + case 15: { var r=R('t'); return r?r+'if(!Array.isArray(t))return false;if(t.length<'+value[0]+')return false;'+(value[1]!==null?'if(t.length>'+value[1]+')return false;':'')+'return true;':null; } + case 16: { var r=R('t'); return r?r+'return Array.isArray(t)&&t.length<='+value+';':null; } + case 17: { var r=R('t'); return r?r+TO+'return false;var s=0;for(var k in t)s++;if(s<'+value[0]+')return false;'+(value[1]!==null?'if(s>'+value[1]+')return false;':'')+'return true;':null; } + case 18: { var r=R('t'); return r?r+TO+'return false;var s=0;for(var k in t)s++;return s<='+value+';':null; } + case 19: return fb(19); case 20: return fb(20); case 21: return fb(21); + case 22: { var r=R('t'); return r?r+'if(!Array.isArray(t))return true;return t.length<'+value+';':null; } + case 23: { var r=R('t'); return r?r+'if(!Array.isArray(t))return true;return t.length>'+value+';':null; } + case 24: { var r=R('t'); return r?r+TO+'return true;var s=0;for(var k in t)s++;return s<'+value+';':null; } + case 25: { var r=R('t'); return r?r+TO+'return true;var s=0;for(var k in t)s++;return s>'+value+';':null; } + case 26: { if(typeof value==='string'||typeof value==='number'||typeof value==='boolean'||value===null){var r=R('t');return r?r+'return t==='+JSON.stringify(value)+';':null;}return fb(26); } + case 27: return fb(27); case 28: return fb(28); + case 29: { var r=R('t'),v=typeof value==='bigint'?value+'n':value; return r?r+"var tt=typeof t;if(tt!=='number'&&tt!=='bigint')return true;return t>="+v+';':null; } + case 30: { var r=R('t'),v=typeof value==='bigint'?value+'n':value; return r?r+"var tt=typeof t;if(tt!=='number'&&tt!=='bigint')return true;return t<="+v+';':null; } + case 31: { var r=R('t'),v=typeof value==='bigint'?value+'n':value; return r?r+"var tt=typeof t;if(tt!=='number'&&tt!=='bigint')return true;return t>"+v+';':null; } + case 32: { var r=R('t'),v=typeof value==='bigint'?value+'n':value; return r?r+"var tt=typeof t;if(tt!=='number'&&tt!=='bigint')return true;return t<"+v+';':null; } + case 33: return fb(33); case 34: return fb(34); + case 35: return fb(35); case 36: return fb(36); case 37: return fb(37); case 38: return fb(38); + case 39: return fb(39); + case 40: { var r=R('t'); return r?IO+r+'if(t===void 0)return true;var a=_jt(t);return a==='+value+'||('+value+'===2&&_ii(t));':null; } + case 41: return fb(41); + case 42: { var r=R('t'); return r?IO+r+'if(t===void 0)return true;return _es(t)==='+value+';':null; } + case 43: return fb(43); + case 44: { var r=R('t'); return r?IO+r+'if(t===void 0)return true;return('+value+'&(1<<_es(t)))!==0;':null; } + case 45: return fb(45); case 46: return fb(46); case 47: return fb(47); + case 48: return fb(48); + case 49: return 'return true;'; case 50: return 'return true;'; case 51: return 'return true;'; case 52: return 'return true;'; + case 53: { var r=R('t'); if(!r)return null; if(!children||children.length===0)return 'return false;'; var c=r; for(var j=0;j0)c+=seq(children,'t'); return c+'return true;'; } + case 60: { var r=R('t'); if(!r)return null; var c=r+TO+'return true;if(!Object.hasOwn(t,'+JSON.stringify(value)+'))return true;'; if(children&&children.length>0)c+=seq(children,'t'); return c+'return true;'; } + case 61: { var r=R('t'); if(!r)return null; var c=r+'if(!Array.isArray(t)||t.length<='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'t'); return c+'return true;'; } + case 62: return fb(62); case 63: return fb(63); + case 64: { var r=R('t'); if(!r)return null; if(!children||children.length===0)return r+'return true;'; var mi=captures.length; captures.push(value); var gf=''; for(var gi=0;gi0){gf+='function(i,d,_t,_v){'+lb(gc,'i')+'return true;},';}else{gf+='null,';}} return r+TO+'return true;var __cg=['+gf+'];for(var k in t){var __mi=_c['+mi+'][k];if(__mi!==void 0){var __cf=__cg[__mi];if(__cf&&!__cf(t,d,_t,_v))return false;}}return true;'; } + case 65: { var r=R('t'); if(!r)return null; if(!children||children.length===0)return r+'return true;'; var mi=captures.length; captures.push(value); var gf=''; for(var gi=0;gi0){gf+='function(i,d,_t,_v){'+lb(gc,'i')+'return true;},';}else{gf+='null,';}} return r+TO+'return true;var __cg=['+gf+'];for(var k in t){var __mi=_c['+mi+'][k];if(__mi===void 0)return false;var __cf=__cg[__mi];if(__cf&&!__cf(t,d,_t,_v))return false;}return true;'; } + case 66: { var r=R('t'); if(!r)return null; if(!children||children.length===0)return r+'return true;'; return r+TO+'return true;for(var k in t){'+lb(children,'t[k]')+'}return true;'; } + case 67: return fb(67); case 68: return fb(68); case 69: return fb(69); case 70: return fb(70); case 71: return fb(71); + case 72: return fb(72); case 73: return fb(73); case 74: return fb(74); case 75: return fb(75); + case 76: { var r=R('t'); return r?r+TO+'return true;for(var k in t){if(_es(t[k])!=='+value+')return false;}return true;':null; } + case 77: return fb(77); case 78: return fb(78); case 79: return fb(79); case 80: return fb(80); + case 81: { var r=R('t'); if(!r)return null; if(!children||children.length===0)return r+'return true;'; return r+'if(!Array.isArray(t))return true;for(var j=0;j=t.length)return true;for(var j='+value+';j0)c+=seq(children,'i'); return c+'return true;'; } + case 94: { var c=IO+'if(!Object.hasOwn(i,'+JSON.stringify(value)+'))return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; } + case 95: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; } + case 96: return 'return true;'; + case 97: return fb(97); + case 98: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(98); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(98); var c=r; for(var j=0;j true; + const instructions = targets[0]; + if (instructions.length === 0) return () => true; + + const capturedInstructions = []; + const budget = [5000000]; + let body = ''; + for (let index = 0; index < instructions.length; index++) { + const code = compileInstructionToCode(instructions[index], capturedInstructions, null, budget); + if (code === null) return null; + budget[0] -= code.length; + body += `if(!(function(i,d,_t,_v){${code}})(instance,0,_t,_v))return false;`; + } + body += 'return true;'; + + try { + const fn = eval( + '(function(_es,_jt,_ii,_ul,_e,_fh,_c,_t){' + + 'return function(instance,_v){' + body + '};' + + '})' + ); + return fn( + effectiveTypeStrictReal, jsonTypeOf, isIntegral, unicodeLength, + evaluateInstructionFast, fastHandlers, capturedInstructions, template + ); + } catch { + return null; + } +} + +function sourceToBigInt(source) { + // Plain integer like "99999999999999999999999999999999999" + if (/^-?[0-9]+$/.test(source)) return BigInt(source); + // Exponential notation like "9.9999999999999999e+34". Parse the coefficient + // and exponent to reconstruct the exact integer, as `BigInt` cannot parse + // exponential notation directly + const match = source.match(/^(-?)([0-9]+)\.?([0-9]*)[eE][+]?([0-9]+)$/); + if (!match) return null; + const exponent = parseInt(match[4]) - match[3].length; + if (exponent < 0) return null; + return BigInt(match[1] + match[2] + match[3]) * (10n ** BigInt(exponent)); +} + +function reviver(_key, value, context) { + // On older engines, `JSON.parse` calls the reviver with only two arguments + if (context === undefined) return value; + if (typeof value !== 'number') return value; + const source = context.source; + // Only convert when `JSON.parse` actually truncated the value, avoiding + // unnecessary `BigInt` conversion for integers that are exactly + // representable as doubles + if (String(value) === source) return value; + const bigint = sourceToBigInt(source); + return bigint !== null ? bigint : value; +} + +class Blaze { + static reviver = reviver; + + constructor(template) { + if (!Array.isArray(template) || template[0] !== JSON_VERSION) { + throw new Error( + `Only version ${JSON_VERSION} of the compiled template is supported by this version of the evaluator`); + } + compile(template); + this.template = template; + this.callbackMode = false; + this.trackMode = false; + this.dynamicMode = false; + this.callback = null; + this.evaluatePathLength = 0; + this.evaluatePathTokens = null; + this.instanceLocationLength = 0; + this.instanceLocationTokens = null; + this.resources = null; + this.evaluated = null; + this.propertyTarget = undefined; + this.propertyParent = undefined; + this.propertyKey = undefined; + this._nativeValidate = generateNativeValidator(template); + } + + validate(instance, callback) { + if (callback === undefined && this._nativeValidate) { + return this._nativeValidate(instance, this); + } + + const template = this.template; + const targets = template[3]; + if (targets.length === 0) return true; + + const track = template[2]; + const dynamic = template[1]; + this.trackMode = track; + this.dynamicMode = dynamic; + this.callbackMode = callback !== undefined; + this.callback = callback; + + if (this.callbackMode) { + this.evaluatePathLength = 0; + this.evaluatePathTokens = []; + this.instanceLocationLength = 0; + this.instanceLocationTokens = []; + this.resources = []; + if (track || dynamic) { + evaluateInstruction = evaluateInstructionTrackedCallback; + if (track) { + this.evaluated = []; + } + } else { + evaluateInstruction = evaluateInstructionFastCallback; + } + } else if (track || dynamic) { + evaluateInstruction = evaluateInstructionTracked; + if (track) { + this.evaluatePathLength = 0; + this.evaluatePathTokens = []; + this.evaluated = []; + } + if (dynamic) { + this.resources = []; + } + } else { + evaluateInstruction = evaluateInstructionFast; + } + + const instructions = targets[0]; + let result = true; + for (let index = 0; index < instructions.length; index++) { + if (!evaluateInstruction(instructions[index], instance, 0, template, this)) { + result = false; + break; + } + } + + this.resources = null; + this.evaluated = null; + this.evaluatePathTokens = null; + this.instanceLocationTokens = null; + this.callback = null; + this.callbackMode = false; + return result; + } + + snapshotPathTokens() { + return this.evaluatePathTokens.slice(0, this.evaluatePathLength); + } + + pushPath(relativeSchemaLocation) { + for (let index = 0; index < relativeSchemaLocation.length; index++) { + if (this.evaluatePathLength < this.evaluatePathTokens.length) { + this.evaluatePathTokens[this.evaluatePathLength] = relativeSchemaLocation[index]; + } else { + this.evaluatePathTokens.push(relativeSchemaLocation[index]); + } + this.evaluatePathLength++; + } + } + + popPath(count) { + this.evaluatePathLength -= count; + } + + callbackPush(instruction) { + if (!this.trackMode) { + this.pushPath(instruction[1]); + } + const relInstance = instruction[2]; + for (let index = 0; index < relInstance.length; index++) { + this.pushInstanceToken(relInstance[index]); + } + this.callback("pre", true, instruction, + buildJsonPointer(this.evaluatePathTokens, this.evaluatePathLength), + buildJsonPointer(this.instanceLocationTokens, this.instanceLocationLength), + null); + } + + callbackPop(instruction, result) { + const isAnnotation = instruction[0] >= ANNOTATION_EMIT && + instruction[0] <= ANNOTATION_BASENAME_TO_PARENT; + this.callback("post", result, instruction, + buildJsonPointer(this.evaluatePathTokens, this.evaluatePathLength), + buildJsonPointer(this.instanceLocationTokens, this.instanceLocationLength), + isAnnotation ? instruction[5] : null); + if (!this.trackMode) { + this.popPath(instruction[1].length); + } + const relInstance = instruction[2]; + for (let index = 0; index < relInstance.length; index++) { + this.popInstanceToken(); + } + } + + callbackAnnotation(instruction) { + if (!this.trackMode) { + this.pushPath(instruction[1]); + } + const relInstance = instruction[2]; + for (let index = 0; index < relInstance.length; index++) { + this.pushInstanceToken(relInstance[index]); + } + const evaluatePath = buildJsonPointer(this.evaluatePathTokens, this.evaluatePathLength); + const opcode = instruction[0]; + let instanceLocation; + if (opcode === ANNOTATION_EMIT) { + instanceLocation = buildJsonPointer(this.instanceLocationTokens, this.instanceLocationLength); + } else { + const parentLength = this.instanceLocationLength > 0 ? this.instanceLocationLength - 1 : 0; + instanceLocation = buildJsonPointer(this.instanceLocationTokens, parentLength); + } + let annotationValue = instruction[5]; + if (opcode === ANNOTATION_BASENAME_TO_PARENT && this.instanceLocationLength > 0) { + annotationValue = this.instanceLocationTokens[this.instanceLocationLength - 1]; + } + this.callback("pre", true, instruction, evaluatePath, instanceLocation, null); + this.callback("post", true, instruction, evaluatePath, instanceLocation, annotationValue); + if (!this.trackMode) { + this.popPath(instruction[1].length); + } + for (let index = 0; index < relInstance.length; index++) { + this.popInstanceToken(); + } + } + + pushInstanceToken(token) { + if (this.instanceLocationLength < this.instanceLocationTokens.length) { + this.instanceLocationTokens[this.instanceLocationLength] = token; + } else { + this.instanceLocationTokens.push(token); + } + this.instanceLocationLength++; + } + + popInstanceToken() { + this.instanceLocationLength--; + } + + markEvaluated(target, parent, key) { + this.evaluated.push({ + instance: target, + parent: parent, + key: key, + pathTokens: this.snapshotPathTokens(), + pathLength: this.evaluatePathLength, + skip: false + }); + } + + isEvaluated(target, parent, key) { + const pathLen = this.evaluatePathLength; + const initialLen = pathLen <= 1 ? 0 : pathLen - 1; + const initialTokens = this.evaluatePathTokens; + const isPrimitive = target === null || typeof target !== 'object'; + const hasLocation = parent !== undefined; + + for (let index = this.evaluated.length - 1; index >= 0; index--) { + const entry = this.evaluated[index]; + if (entry.skip) continue; + + if (isPrimitive && hasLocation && entry.parent !== undefined) { + if (entry.parent !== parent || entry.key !== key) continue; + } else { + if (entry.instance !== target) continue; + } + + if (initialLen === 0) return true; + if (entry.pathLength < initialLen) continue; + let match = true; + for (let token = 0; token < initialLen; token++) { + if (entry.pathTokens[token] !== initialTokens[token]) { + match = false; + break; + } + } + if (match) return true; + } + return false; + } + + unevaluate() { + const pathLen = this.evaluatePathLength; + const tokens = this.evaluatePathTokens; + for (let index = 0; index < this.evaluated.length; index++) { + const entry = this.evaluated[index]; + if (entry.skip) continue; + if (entry.pathLength < pathLen) continue; + let match = true; + for (let token = 0; token < pathLen; token++) { + if (entry.pathTokens[token] !== tokens[token]) { + match = false; + break; + } + } + if (match) entry.skip = true; + } + } +} + +function evaluateInstructionFast(instruction, instance, depth, template, evaluator) { + if (depth > DEPTH_LIMIT) { + throw new Error('The evaluation path depth limit was reached likely due to infinite recursion'); + } + const handler = fastHandlers[instruction[0]]; + if (!handler) return true; + return handler(instruction, instance, depth, template, evaluator); +} + +function evaluateInstructionTracked(instruction, instance, depth, template, evaluator) { + if (depth > DEPTH_LIMIT) { + throw new Error('The evaluation path depth limit was reached likely due to infinite recursion'); + } + const handler = fastHandlers[instruction[0]]; + if (!handler) return true; + + const type = instruction[0]; + if (type < 92 || type > 96) { + if (evaluator.trackMode) { + evaluator.pushPath(instruction[1]); + } + if (evaluator.dynamicMode) { + evaluator.resources.push(instruction[4]); + } + + const result = handler(instruction, instance, depth, template, evaluator); + + if (evaluator.trackMode) { + evaluator.popPath(instruction[1].length); + } + if (evaluator.dynamicMode) { + evaluator.resources.pop(); + } + return result; + } + + return handler(instruction, instance, depth, template, evaluator); +} + +function evaluateInstructionFastCallback(instruction, instance, depth, template, evaluator) { + if (depth > DEPTH_LIMIT) { + throw new Error('The evaluation path depth limit was reached likely due to infinite recursion'); + } + const handler = handlers[instruction[0]]; + if (!handler) return true; + return handler(instruction, instance, depth, template, evaluator); +} + +function evaluateInstructionTrackedCallback(instruction, instance, depth, template, evaluator) { + if (depth > DEPTH_LIMIT) { + throw new Error('The evaluation path depth limit was reached likely due to infinite recursion'); + } + const handler = handlers[instruction[0]]; + if (!handler) return true; + + const type = instruction[0]; + if (type < CONTROL_GROUP_START || type > CONTROL_EVALUATE_END) { + if (evaluator.trackMode) { + evaluator.pushPath(instruction[1]); + } + if (evaluator.dynamicMode) { + evaluator.resources.push(instruction[4]); + } + + const result = handler(instruction, instance, depth, template, evaluator); + + if (evaluator.trackMode) { + evaluator.popPath(instruction[1].length); + } + if (evaluator.dynamicMode) { + evaluator.resources.pop(); + } + return result; + } + + return handler(instruction, instance, depth, template, evaluator); +} + +let evaluateInstruction = evaluateInstructionFast; + +function effectiveTypeStrictReal(value) { + if (value === null) return Type.Null; + switch (typeof value) { + case 'boolean': return Type.Boolean; + case 'number': return Number.isInteger(value) ? Type.Integer : Type.Real; + case 'bigint': return Type.Integer; + case 'string': return Type.String; + case 'object': return Array.isArray(value) ? Type.Array : Type.Object; + default: return Type.Null; + } +} + +function typeSetTest(bitmask, typeIndex) { + return (bitmask & (1 << typeIndex)) !== 0; +} + +function jsonEqual(left, right) { + if (left === right) return true; + if (left === null || right === null) return left === right; + if (typeof left !== typeof right) return false; + if (typeof left !== 'object') return left === right; + if (Array.isArray(left) !== Array.isArray(right)) return false; + if (Array.isArray(left)) { + if (left.length !== right.length) return false; + for (let index = 0; index < left.length; index++) { + if (!jsonEqual(left[index], right[index])) return false; + } + return true; + } + const keysLeft = Object.keys(left); + const keysRight = Object.keys(right); + if (keysLeft.length !== keysRight.length) return false; + for (let index = 0; index < keysLeft.length; index++) { + const key = keysLeft[index]; + if (!Object.hasOwn(right, key)) return false; + if (!jsonEqual(left[key], right[key])) return false; + } + return true; +} + +function fastHash(value) { + if (value === null) return 2; + switch (typeof value) { + case 'boolean': return value ? 1 : 0; + case 'number': return 4 + ((value | 0) & 255); + case 'bigint': return 4 + Number(value & 255n); + case 'string': return 3 + value.length; + case 'object': + if (Array.isArray(value)) { + let hash = 6; + for (let index = 0; index < value.length; index++) hash += 1 + fastHash(value[index]); + return hash; + } else { + let hash = 7; + for (const key in value) hash += 1 + key.length + fastHash(value[key]); + return hash; + } + default: return 2; + } +} + +function isUnique(array) { + const length = array.length; + if (length <= 1) return true; + const first = array[0]; + const firstType = typeof first; + if (first !== null && firstType !== 'object') { + let allPrimitive = true; + for (let index = 1; index < length; index++) { + const element = array[index]; + if (element === null || typeof element === 'object') { + allPrimitive = false; + break; + } + } + if (allPrimitive) { + const set = new Set(); + for (let index = 0; index < length; index++) { + const size = set.size; + set.add(array[index]); + if (set.size === size) return false; + } + return true; + } + } + const hashes = new Array(length); + for (let index = 0; index < length; index++) { + hashes[index] = fastHash(array[index]); + } + for (let index = 1; index < length; index++) { + for (let previous = 0; previous < index; previous++) { + if (hashes[index] === hashes[previous] && jsonEqual(array[index], array[previous])) return false; + } + } + return true; +} + +function isDivisibleBy(value, divisor) { + if (divisor === 0 || divisor === 0n) return false; + if (typeof value === 'bigint' && typeof divisor === 'bigint') { + return value % divisor === 0n; + } + if (typeof value === 'bigint') { + value = Number(value); + } else if (typeof divisor === 'bigint') { + divisor = Number(divisor); + } + const remainder = value % divisor; + if (remainder === 0) return true; + return Math.abs(remainder) < 1e-9 || Math.abs(remainder - divisor) < 1e-9 || + Math.abs(remainder + divisor) < 1e-9; +} + +function unicodeLength(string) { + let count = 0; + for (let index = 0; index < string.length; index++) { + count++; + const code = string.charCodeAt(index); + if (code >= 0xD800 && code <= 0xDBFF) index++; + } + return count; +} + +function objectSize(object) { + let count = 0; + for (const key in object) count++; + return count; +} + +function isObject(value) { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +function AssertionFail(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; +}; + +function AssertionDefines(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = Object.hasOwn(target, instruction[5]); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionDefinesStrict(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const __result = Object.hasOwn(target, instruction[5]); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionDefinesAll(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const strings = instruction[5]; + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionDefinesAllStrict(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const strings = instruction[5]; + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionDefinesExactly(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + let targetSize = 0; + for (const key in target) targetSize++; + const strings = instruction[5]; + if (targetSize !== strings.length) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionDefinesExactlyStrict(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + let targetSize = 0; + for (const key in target) targetSize++; + const strings = instruction[5]; + if (targetSize !== strings.length) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionDefinesExactlyStrictHash3(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const entries = instruction[5][0]; + let count = 0; + for (const key in target) count++; + if (count !== 3) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const __result = Object.hasOwn(target, entries[0][1]) && + Object.hasOwn(target, entries[1][1]) && + Object.hasOwn(target, entries[2][1]); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionPropertyDependencies(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const value = instruction[5]; + for (const property in value) { + if (!Object.hasOwn(target, property)) continue; + const dependencies = value[property]; + for (let index = 0; index < dependencies.length; index++) { + if (!Object.hasOwn(target, dependencies[index])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionType(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const expected = instruction[5]; + const actual = jsonTypeOf(target); + if (actual === expected) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + if (expected === Type.Integer && isIntegral(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; +}; + +function AssertionTypeAny(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const bitmask = instruction[5]; + const typeIndex = jsonTypeOf(target); + if (typeSetTest(bitmask, typeIndex)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + if (typeSetTest(bitmask, Type.Integer) && isIntegral(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; +}; + +function AssertionTypeStrict(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const __result = effectiveTypeStrictReal(target) === instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeStrictAny(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const __result = typeSetTest(instruction[5], effectiveTypeStrictReal(target)); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeStringBounded(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const range = instruction[5]; + const length = unicodeLength(target); + if (length < range[0]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (range[1] !== null && length > range[1]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionTypeStringUpper(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const __result = typeof target === 'string' && unicodeLength(target) <= instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeArrayBounded(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const range = instruction[5]; + if (target.length < range[0]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (range[1] !== null && target.length > range[1]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionTypeArrayUpper(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const __result = Array.isArray(target) && target.length <= instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeObjectBounded(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const range = instruction[5]; + const size = objectSize(target); + if (size < range[0]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (range[1] !== null && size > range[1]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionTypeObjectUpper(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const __result = objectSize(target) <= instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionRegex(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = instruction[5].test(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionStringSizeLess(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = unicodeLength(target) < instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionStringSizeGreater(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = unicodeLength(target) > instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionArraySizeLess(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = target.length < instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionArraySizeGreater(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = target.length > instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionObjectSizeLess(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = objectSize(target) < instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionObjectSizeGreater(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = objectSize(target) > instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionEqual(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + let __result; + if (evaluator.propertyTarget !== undefined) { + const value = instruction[5]; + __result = typeof value === 'string' && value === evaluator.propertyTarget; + } else { + __result = jsonEqual(resolveInstance(instance, instruction[2]), instruction[5]); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionEqualsAny(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const value = instruction[5]; + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (value.primitive) { + const __result = value.set.has(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; + } + const values = Array.isArray(value) ? value : value.values; + for (let index = 0; index < values.length; index++) { + if (jsonEqual(target, values[index])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; +}; + +function AssertionEqualsAnyStringHash(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget + : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + + const value = instruction[5]; + const entries = value[0]; + const tableOfContents = value[1]; + + const stringSize = target.length; + if (stringSize < tableOfContents.length) { + const hint = tableOfContents[stringSize]; + if (hint[1] === 0) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + for (let index = hint[0] - 1; index < hint[1]; index++) { + if (entries[index][1] === target) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; +}; + +function AssertionGreaterEqual(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = target >= instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionLessEqual(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = target <= instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionGreater(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = target > instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionLess(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = target < instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionUnique(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = isUnique(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionDivisible(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = isDivisibleBy(target, instruction[5]); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeIntegerBounded(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const range = instruction[5]; + const __result = (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0] && target <= range[1]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeIntegerBoundedStrict(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const range = instruction[5]; + const __result = (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0] && target <= range[1]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeIntegerLowerBound(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const range = instruction[5]; + const __result = (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionTypeIntegerLowerBoundStrict(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const range = instruction[5]; + const __result = (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionStringType(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = URI_REGEX.test(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionPropertyType(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + const actual = jsonTypeOf(target); + const __result = actual === expected || (expected === Type.Integer && isIntegral(target)); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionPropertyTypeEvaluate(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + const actual = jsonTypeOf(target); + const result = actual === expected || (expected === Type.Integer && isIntegral(target)); + if (result && evaluator.trackMode) { + const location = instruction[2]; + evaluator.markEvaluated(target, instance, location.length > 0 ? location[location.length - 1] : undefined); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, result); + return result; +}; + +function AssertionPropertyTypeStrict(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = effectiveTypeStrictReal(target) === instruction[5]; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionPropertyTypeStrictEvaluate(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const result = effectiveTypeStrictReal(target) === instruction[5]; + if (result && evaluator.trackMode) { + const location = instruction[2]; + evaluator.markEvaluated(target, instance, location.length > 0 ? location[location.length - 1] : undefined); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, result); + return result; +}; + +function AssertionPropertyTypeStrictAny(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const __result = typeSetTest(instruction[5], effectiveTypeStrictReal(target)); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function AssertionPropertyTypeStrictAnyEvaluate(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const result = typeSetTest(instruction[5], effectiveTypeStrictReal(target)); + if (result && evaluator.trackMode) { + const location = instruction[2]; + evaluator.markEvaluated(target, instance, location.length > 0 ? location[location.length - 1] : undefined); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, result); + return result; +}; + +function AssertionArrayPrefix(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (target.length === 0) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + const children = instruction[6]; + const prefixes = children.length - 1; + const pointer = target.length === prefixes ? prefixes : Math.min(target.length, prefixes) - 1; + const entry = children[pointer]; + const entryChildren = entry[6]; + if (entryChildren) { + for (let index = 0; index < entryChildren.length; index++) { + if (!evaluateInstruction(entryChildren[index], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionArrayPrefixEvaluate(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (target.length === 0) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + const children = instruction[6]; + const prefixes = children.length - 1; + const pointer = target.length === prefixes ? prefixes : Math.min(target.length, prefixes) - 1; + const entry = children[pointer]; + const entryChildren = entry[6]; + if (entryChildren) { + for (let index = 0; index < entryChildren.length; index++) { + if (!evaluateInstruction(entryChildren[index], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.trackMode) { + if (target.length === prefixes) { + evaluator.markEvaluated(target); + } else { + for (let cursor = 0; cursor <= pointer; cursor++) { + evaluator.markEvaluated(target[cursor], target, cursor); + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AssertionObjectPropertiesSimple(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const value = instruction[5]; + const children = instruction[6]; + for (let index = 0; index < value.length; index++) { + const entry = value[index]; + const name = entry[0]; + const required = entry[2]; + if (!Object.hasOwn(target, name)) { + if (required) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + continue; + } + if (index < children.length) { + if (!evaluateInstructionFast(children[index], target[name], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function AnnotationEmit(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackAnnotation(instruction); + return true; +} +function AnnotationToParent(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackAnnotation(instruction); + return true; +} +function AnnotationBasenameToParent(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackAnnotation(instruction); + return true; +} + +function Evaluate(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (evaluator.trackMode) { + const target = resolveInstance(instance, instruction[2]); + evaluator.markEvaluated(target); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LogicalNot(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; +}; + +function LogicalNotEvaluate(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const children = instruction[6]; + let result = false; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + result = true; + break; + } + } + } + if (evaluator.trackMode) evaluator.unevaluate(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, result); + return result; +}; + +function LogicalOr(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + if (!children || children.length === 0) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + const target = resolveInstance(instance, instruction[2]); + const exhaustive = instruction[5]; + let result = false; + if (exhaustive) { + for (let index = 0; index < children.length; index++) { + if (evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + result = true; + } + } + } else { + for (let index = 0; index < children.length; index++) { + if (evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + result = true; + break; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, result); + return result; +}; + +function LogicalAnd(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LogicalXor(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + const exhaustive = instruction[5]; + const children = instruction[6]; + let result = true; + let hasMatched = false; + if (children) { + for (let index = 0; index < children.length; index++) { + if (evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + if (hasMatched) { + result = false; + if (!exhaustive) break; + } else { + hasMatched = true; + } + } + } + } + const __result = result && hasMatched; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function LogicalCondition(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const value = instruction[5]; + const thenStart = value[0]; + const elseStart = value[1]; + const children = instruction[6]; + const childrenSize = children ? children.length : 0; + + let conditionEnd = childrenSize; + if (thenStart > 0) conditionEnd = thenStart; + else if (elseStart > 0) conditionEnd = elseStart; + + const target = resolveInstance(instance, instruction[2]); + + let conditionResult = true; + for (let cursor = 0; cursor < conditionEnd; cursor++) { + if (!evaluateInstruction(children[cursor], target, depth + 1, template, evaluator)) { + conditionResult = false; + break; + } + } + + const consequenceStart = conditionResult ? thenStart : elseStart; + const consequenceEnd = (conditionResult && elseStart > 0) ? elseStart : childrenSize; + + if (consequenceStart > 0) { + if (evaluator.trackMode || evaluator.callbackMode) { + evaluator.popPath(instruction[1].length); + } + + let result = true; + for (let cursor = consequenceStart; cursor < consequenceEnd; cursor++) { + if (!evaluateInstruction(children[cursor], target, depth + 1, template, evaluator)) { + result = false; + break; + } + } + + if (evaluator.trackMode || evaluator.callbackMode) { + evaluator.pushPath(instruction[1]); + } + + if (evaluator.callbackMode) evaluator.callbackPop(instruction, result); + return result; + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LogicalWhenType(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (jsonTypeOf(target) !== instruction[5]) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LogicalWhenDefines(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (!Object.hasOwn(target, instruction[5])) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LogicalWhenArraySizeGreater(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target) || target.length <= instruction[5]) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesUnevaluated(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (evaluator.trackMode && evaluator.isEvaluated(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + const children = instruction[6]; + for (const key in target) { + if (evaluator.trackMode && evaluator.isEvaluated(target[key], target, key)) continue; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesUnevaluatedExcept(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (evaluator.trackMode && evaluator.isEvaluated(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + const filter = instruction[5]; + const filterStrings = filter[0]; + const filterPrefixes = filter[1]; + const filterRegexes = filter[2]; + const children = instruction[6]; + for (const key in target) { + if (filterStrings.has(key)) continue; + let matched = false; + for (let index = 0; index < filterPrefixes.length; index++) { + if (key.startsWith(filterPrefixes[index])) { matched = true; break; } + } + if (matched) continue; + for (let index = 0; index < filterRegexes.length; index++) { + filterRegexes[index].lastIndex = 0; + if (filterRegexes[index].test(key)) { matched = true; break; } + } + if (matched) continue; + if (evaluator.trackMode && evaluator.isEvaluated(target[key], target, key)) continue; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesMatch(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + for (const key in target) { + const index = instruction[5][key]; + if (index === undefined) continue; + const subinstruction = children[index]; + const subchildren = subinstruction[6]; + if (subchildren) { + for (let childIndex = 0; childIndex < subchildren.length; childIndex++) { + if (!evaluateInstruction(subchildren[childIndex], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesMatchClosed(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + for (const key in target) { + const index = instruction[5][key]; + if (index === undefined) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const subinstruction = children[index]; + const subchildren = subinstruction[6]; + if (subchildren) { + for (let childIndex = 0; childIndex < subchildren.length; childIndex++) { + if (!evaluateInstruction(subchildren[childIndex], target, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopProperties(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + for (const key in target) { + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesEvaluate(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + for (const key in target) { + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + if (evaluator.trackMode) evaluator.markEvaluated(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesRegex(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const regex = instruction[5]; + const children = instruction[6]; + for (const key in target) { + regex.lastIndex = 0; + if (!regex.test(key)) continue; + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesRegexClosed(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const regex = instruction[5]; + const children = instruction[6]; + for (const key in target) { + regex.lastIndex = 0; + if (!regex.test(key)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesStartsWith(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const prefix = instruction[5]; + const children = instruction[6]; + for (const key in target) { + if (!key.startsWith(prefix)) continue; + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesExcept(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const filter = instruction[5]; + const filterStrings = filter[0]; + const filterPrefixes = filter[1]; + const filterRegexes = filter[2]; + const children = instruction[6]; + for (const key in target) { + if (filterStrings.has(key)) continue; + let matched = false; + for (let index = 0; index < filterPrefixes.length; index++) { + if (key.startsWith(filterPrefixes[index])) { matched = true; break; } + } + if (matched) continue; + for (let index = 0; index < filterRegexes.length; index++) { + filterRegexes[index].lastIndex = 0; + if (filterRegexes[index].test(key)) { matched = true; break; } + } + if (matched) continue; + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesType(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + for (const key in target) { + const actual = jsonTypeOf(target[key]); + if (actual !== expected && !(expected === Type.Integer && isIntegral(target[key]))) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesTypeEvaluate(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + for (const key in target) { + const actual = jsonTypeOf(target[key]); + if (actual !== expected && !(expected === Type.Integer && isIntegral(target[key]))) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesExactlyTypeStrict(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const value = instruction[5]; + let count = 0; + for (const key in target) { + count++; + if (effectiveTypeStrictReal(target[key]) !== value[0]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + const __result = count === value[1].length; + if (evaluator.callbackMode) evaluator.callbackPop(instruction, __result); + return __result; +}; + +function LoopPropertiesExactlyTypeStrictHash(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const value = instruction[5]; + const entries = value[1][0]; + const expectedCount = entries.length; + let count = 0; + for (const key in target) { + count++; + if (effectiveTypeStrictReal(target[key]) !== value[0]) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (count !== expectedCount) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + for (let index = 0; index < expectedCount; index++) { + if (!Object.hasOwn(target, entries[index][1])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesTypeStrict(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + for (const key in target) { + if (effectiveTypeStrictReal(target[key]) !== expected) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesTypeStrictEvaluate(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + for (const key in target) { + if (effectiveTypeStrictReal(target[key]) !== expected) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesTypeStrictAny(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const bitmask = instruction[5]; + for (const key in target) { + if (!typeSetTest(bitmask, effectiveTypeStrictReal(target[key]))) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopPropertiesTypeStrictAnyEvaluate(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const bitmask = instruction[5]; + for (const key in target) { + if (!typeSetTest(bitmask, effectiveTypeStrictReal(target[key]))) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopKeys(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + for (const key in target) { + if (evaluator.callbackMode) evaluator.pushInstanceToken(key); + const previousPropertyTarget = evaluator.propertyTarget; + evaluator.propertyTarget = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], null, depth + 1, template, evaluator)) { + evaluator.propertyTarget = previousPropertyTarget; + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + evaluator.propertyTarget = previousPropertyTarget; + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItems(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + for (let index = 0; index < target.length; index++) { + if (evaluator.callbackMode) evaluator.pushInstanceToken(index); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsFrom(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const startIndex = instruction[5]; + if (!Array.isArray(target) || startIndex >= target.length) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const children = instruction[6]; + for (let index = startIndex; index < target.length; index++) { + if (evaluator.callbackMode) evaluator.pushInstanceToken(index); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsUnevaluated(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (evaluator.trackMode && evaluator.isEvaluated(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + const children = instruction[6]; + for (let index = 0; index < target.length; index++) { + if (evaluator.trackMode && evaluator.isEvaluated(target[index], target, index)) continue; + if (evaluator.callbackMode) evaluator.pushInstanceToken(index); + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsType(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + for (let index = 0; index < target.length; index++) { + const actual = jsonTypeOf(target[index]); + if (actual !== expected && !(expected === Type.Integer && isIntegral(target[index]))) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsTypeStrict(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const expected = instruction[5]; + for (let index = 0; index < target.length; index++) { + if (effectiveTypeStrictReal(target[index]) !== expected) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsTypeStrictAny(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const bitmask = instruction[5]; + for (let index = 0; index < target.length; index++) { + if (!typeSetTest(bitmask, effectiveTypeStrictReal(target[index]))) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsPropertiesExactlyTypeStrictHash(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + const expectedType = instruction[5][0]; + const entries = instruction[5][1][0]; + const expectedCount = entries.length; + for (let index = 0; index < target.length; index++) { + const item = target[index]; + if (!isObject(item)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + let count = 0; + for (const key in item) { + count++; + if (effectiveTypeStrictReal(item[key]) !== expectedType) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (count !== expectedCount) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + for (let entry = 0; entry < expectedCount; entry++) { + if (!Object.hasOwn(item, entries[entry][1])) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsIntegerBounded(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target) || target.length === 0) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const minimum = instruction[5][0]; + const maximum = instruction[5][1]; + for (let index = 0; index < target.length; index++) { + const element = target[index]; + const elementType = typeof element; + if (elementType !== 'number' && elementType !== 'bigint') { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (element < minimum || element > maximum) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopItemsIntegerBoundedSized(instruction, instance, depth, template, evaluator) { + const value = instruction[5]; + const minimum = value[0][0]; + const maximum = value[0][1]; + const minimumSize = value[1][0]; + const target = resolveInstance(instance, instruction[2]); + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + if (!Array.isArray(target) || target.length < minimumSize) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + for (let index = 0; index < target.length; index++) { + const element = target[index]; + const elementType = typeof element; + if (elementType !== 'number' && elementType !== 'bigint') { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + if (element < minimum || element > maximum) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +function LoopContains(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + const range = instruction[5]; + const minimum = range[0]; + const maximum = range[1]; + const isExhaustive = range[2]; + if (minimum === 0 && target.length === 0) return true; + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + + const children = instruction[6]; + let result = false; + let matchCount = 0; + for (let index = 0; index < target.length; index++) { + if (evaluator.callbackMode) evaluator.pushInstanceToken(index); + let subresult = true; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) { + subresult = false; + break; + } + } + } + if (evaluator.callbackMode) evaluator.popInstanceToken(); + if (subresult) { + matchCount++; + if (maximum !== null && matchCount > maximum) { + result = false; + break; + } + if (matchCount >= minimum) { + result = true; + if (maximum === null && !isExhaustive) break; + } + } + } + + if (evaluator.callbackMode) evaluator.callbackPop(instruction, result); + return result; +}; + +function ControlGroup(instruction, instance, depth, template, evaluator) { + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], instance, depth + 1, template, evaluator)) return false; + } + } + return true; +}; + +function ControlGroupWhenDefines(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (!Object.hasOwn(target, instruction[5])) return true; + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], instance, depth + 1, template, evaluator)) return false; + } + } + return true; +}; + +function ControlGroupWhenDefinesDirect(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + if (!Object.hasOwn(instance, instruction[5])) return true; + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], instance, depth + 1, template, evaluator)) return false; + } + } + return true; +}; + +function ControlGroupWhenType(instruction, instance, depth, template, evaluator) { + if (jsonTypeOf(instance) !== instruction[5]) return true; + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], instance, depth + 1, template, evaluator)) return false; + } + } + return true; +}; + +function ControlEvaluate(instruction, instance, depth, template, evaluator) { + if (evaluator.trackMode) { + const target = resolveInstance(instance, instruction[5]); + evaluator.markEvaluated(target, evaluator.propertyParent, evaluator.propertyKey); + } + return true; +}; + +function ControlDynamicAnchorJump(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const resolved = resolveInstance(instance, instruction[2]); + const anchor = instruction[5]; + + if (!evaluator.resources) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + + const anchors = template[5]; + for (let index = 0; index < evaluator.resources.length; index++) { + const jumpTarget = anchors.get(evaluator.resources[index] + ':' + anchor); + if (jumpTarget !== undefined) { + for (let childIndex = 0; childIndex < jumpTarget.length; childIndex++) { + if (!evaluateInstruction(jumpTarget[childIndex], resolved, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + } + + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; +}; + +function ControlJump(instruction, instance, depth, template, evaluator) { + if (evaluator.callbackMode) evaluator.callbackPush(instruction); + const jumpTarget = instruction[5]; + if (!jumpTarget) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; + } + const resolved = resolveInstance(instance, instruction[2]); + for (let index = 0; index < jumpTarget.length; index++) { + if (!evaluateInstruction(jumpTarget[index], resolved, depth + 1, template, evaluator)) { + if (evaluator.callbackMode) evaluator.callbackPop(instruction, false); + return false; + } + } + if (evaluator.callbackMode) evaluator.callbackPop(instruction, true); + return true; +}; + +const handlers = [ + AssertionFail, // 0 + AssertionDefines, // 1 + AssertionDefinesStrict, // 2 + AssertionDefinesAll, // 3 + AssertionDefinesAllStrict, // 4 + AssertionDefinesExactly, // 5 + AssertionDefinesExactlyStrict, // 6 + AssertionDefinesExactlyStrictHash3, // 7 + AssertionPropertyDependencies, // 8 + AssertionType, // 9 + AssertionTypeAny, // 10 + AssertionTypeStrict, // 11 + AssertionTypeStrictAny, // 12 + AssertionTypeStringBounded, // 13 + AssertionTypeStringUpper, // 14 + AssertionTypeArrayBounded, // 15 + AssertionTypeArrayUpper, // 16 + AssertionTypeObjectBounded, // 17 + AssertionTypeObjectUpper, // 18 + AssertionRegex, // 19 + AssertionStringSizeLess, // 20 + AssertionStringSizeGreater, // 21 + AssertionArraySizeLess, // 22 + AssertionArraySizeGreater, // 23 + AssertionObjectSizeLess, // 24 + AssertionObjectSizeGreater, // 25 + AssertionEqual, // 26 + AssertionEqualsAny, // 27 + AssertionEqualsAnyStringHash, // 28 + AssertionGreaterEqual, // 29 + AssertionLessEqual, // 30 + AssertionGreater, // 31 + AssertionLess, // 32 + AssertionUnique, // 33 + AssertionDivisible, // 34 + AssertionTypeIntegerBounded, // 35 + AssertionTypeIntegerBoundedStrict, // 36 + AssertionTypeIntegerLowerBound, // 37 + AssertionTypeIntegerLowerBoundStrict, // 38 + AssertionStringType, // 39 + AssertionPropertyType, // 40 + AssertionPropertyTypeEvaluate, // 41 + AssertionPropertyTypeStrict, // 42 + AssertionPropertyTypeStrictEvaluate, // 43 + AssertionPropertyTypeStrictAny, // 44 + AssertionPropertyTypeStrictAnyEvaluate, // 45 + AssertionArrayPrefix, // 46 + AssertionArrayPrefixEvaluate, // 47 + AssertionObjectPropertiesSimple, // 48 + AnnotationEmit, // 49 + AnnotationToParent, // 50 + AnnotationBasenameToParent, // 51 + Evaluate, // 52 + LogicalNot, // 53 + LogicalNotEvaluate, // 54 + LogicalOr, // 55 + LogicalAnd, // 56 + LogicalXor, // 57 + LogicalCondition, // 58 + LogicalWhenType, // 59 + LogicalWhenDefines, // 60 + LogicalWhenArraySizeGreater, // 61 + LoopPropertiesUnevaluated, // 62 + LoopPropertiesUnevaluatedExcept, // 63 + LoopPropertiesMatch, // 64 + LoopPropertiesMatchClosed, // 65 + LoopProperties, // 66 + LoopPropertiesEvaluate, // 67 + LoopPropertiesRegex, // 68 + LoopPropertiesRegexClosed, // 69 + LoopPropertiesStartsWith, // 70 + LoopPropertiesExcept, // 71 + LoopPropertiesType, // 72 + LoopPropertiesTypeEvaluate, // 73 + LoopPropertiesExactlyTypeStrict, // 74 + LoopPropertiesExactlyTypeStrictHash, // 75 + LoopPropertiesTypeStrict, // 76 + LoopPropertiesTypeStrictEvaluate, // 77 + LoopPropertiesTypeStrictAny, // 78 + LoopPropertiesTypeStrictAnyEvaluate, // 79 + LoopKeys, // 80 + LoopItems, // 81 + LoopItemsFrom, // 82 + LoopItemsUnevaluated, // 83 + LoopItemsType, // 84 + LoopItemsTypeStrict, // 85 + LoopItemsTypeStrictAny, // 86 + LoopItemsPropertiesExactlyTypeStrictHash, // 87 + LoopItemsPropertiesExactlyTypeStrictHash, // 88 + LoopItemsIntegerBounded, // 89 + LoopItemsIntegerBoundedSized, // 90 + LoopContains, // 91 + ControlGroup, // 92 + ControlGroupWhenDefines, // 93 + ControlGroupWhenDefinesDirect, // 94 + ControlGroupWhenType, // 95 + ControlEvaluate, // 96 + ControlDynamicAnchorJump, // 97 + ControlJump // 98 +]; + +function AssertionTypeArrayBounded_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!Array.isArray(target)) return false; + const range = instruction[5]; + if (target.length < range[0]) return false; + if (range[1] !== null && target.length > range[1]) return false; + return true; +} + +function LoopItemsTypeStrictAny_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!Array.isArray(target)) return true; + const bitmask = instruction[5]; + for (let index = 0; index < target.length; index++) { + if (!typeSetTest(bitmask, effectiveTypeStrictReal(target[index]))) return false; + } + return true; +} + +function AssertionPropertyTypeStrict_fast(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + return effectiveTypeStrictReal(target) === instruction[5]; +} + +function AssertionTypeStrict_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + return effectiveTypeStrictReal(target) === instruction[5]; +} + +function AssertionDefinesAllStrict_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return false; + const strings = instruction[5]; + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) return false; + } + return true; +} + +function AssertionEqual_fast(instruction, instance, depth, template, evaluator) { + if (evaluator.propertyTarget !== undefined) { + const value = instruction[5]; + return typeof value === 'string' && value === evaluator.propertyTarget; + } + return jsonEqual(resolveInstance(instance, instruction[2]), instruction[5]); +} + +function LoopPropertiesMatch_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const children = instruction[6]; + for (const key in target) { + const index = instruction[5][key]; + if (index === undefined) continue; + const subinstruction = children[index]; + const subchildren = subinstruction[6]; + if (subchildren) { + for (let childIndex = 0; childIndex < subchildren.length; childIndex++) { + if (!evaluateInstruction(subchildren[childIndex], target, depth + 1, template, evaluator)) return false; + } + } + } + return true; +} + +function LogicalOr_fast(instruction, instance, depth, template, evaluator) { + const children = instruction[6]; + if (!children || children.length === 0) return true; + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + const exhaustive = instruction[5]; + let result = false; + if (exhaustive) { + for (let index = 0; index < children.length; index++) { + if (evaluateInstruction(children[index], target, depth + 1, template, evaluator)) result = true; + } + } else { + for (let index = 0; index < children.length; index++) { + if (evaluateInstruction(children[index], target, depth + 1, template, evaluator)) return true; + } + } + return result; +} + +function ControlJump_fast(instruction, instance, depth, template, evaluator) { + const jumpTarget = instruction[5]; + if (!jumpTarget) return true; + const relInstance = instruction[2]; + const resolved = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + for (let index = 0; index < jumpTarget.length; index++) { + if (!evaluateInstruction(jumpTarget[index], resolved, depth + 1, template, evaluator)) return false; + } + return true; +} + +function AssertionEqualsAnyStringHash_fast(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget + : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return false; + const value = instruction[5]; + const entries = value[0]; + const tableOfContents = value[1]; + const stringSize = target.length; + if (stringSize < tableOfContents.length) { + const hint = tableOfContents[stringSize]; + if (hint[1] === 0) return false; + for (let index = hint[0] - 1; index < hint[1]; index++) { + if (entries[index][1] === target) return true; + } + } + return false; +} + +function LogicalXor_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + const exhaustive = instruction[5]; + const children = instruction[6]; + let result = true; + let hasMatched = false; + if (children) { + for (let index = 0; index < children.length; index++) { + if (evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + if (hasMatched) { + result = false; + if (!exhaustive) break; + } else { + hasMatched = true; + } + } + } + } + return result && hasMatched; +} + +function AssertionDefinesStrict_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + return isObject(target) && Object.hasOwn(target, instruction[5]); +} + +function LoopItems_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!Array.isArray(target)) return true; + const children = instruction[6]; + for (let index = 0; index < target.length; index++) { + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) return false; + } + } + } + return true; +} + +function LoopPropertiesMatchClosed_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const children = instruction[6]; + for (const key in target) { + const index = instruction[5][key]; + if (index === undefined) return false; + const subinstruction = children[index]; + const subchildren = subinstruction[6]; + if (subchildren) { + for (let childIndex = 0; childIndex < subchildren.length; childIndex++) { + if (!evaluateInstruction(subchildren[childIndex], target, depth + 1, template, evaluator)) return false; + } + } + } + return true; +} + +function LogicalAnd_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) return false; + } + } + return true; +} + +function AssertionTypeStringBounded_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (typeof target !== 'string') return false; + const range = instruction[5]; + const length = unicodeLength(target); + if (length < range[0]) return false; + return range[1] === null || length <= range[1]; +} + +function AssertionPropertyDependencies_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + const value = instruction[5]; + for (const property in value) { + if (!Object.hasOwn(target, property)) continue; + const dependencies = value[property]; + for (let index = 0; index < dependencies.length; index++) { + if (!Object.hasOwn(target, dependencies[index])) return false; + } + } + return true; +} + +function AssertionTypeAny_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + const bitmask = instruction[5]; + const typeIndex = jsonTypeOf(target); + if (typeSetTest(bitmask, typeIndex)) return true; + return typeSetTest(bitmask, Type.Integer) && isIntegral(target); +} + +function LogicalCondition_fast(instruction, instance, depth, template, evaluator) { + const value = instruction[5]; + const thenStart = value[0]; + const elseStart = value[1]; + const children = instruction[6]; + const childrenSize = children ? children.length : 0; + let conditionEnd = childrenSize; + if (thenStart > 0) conditionEnd = thenStart; + else if (elseStart > 0) conditionEnd = elseStart; + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + let conditionResult = true; + for (let cursor = 0; cursor < conditionEnd; cursor++) { + if (!evaluateInstruction(children[cursor], target, depth + 1, template, evaluator)) { + conditionResult = false; + break; + } + } + const consequenceStart = conditionResult ? thenStart : elseStart; + const consequenceEnd = (conditionResult && elseStart > 0) ? elseStart : childrenSize; + if (consequenceStart > 0) { + if (evaluator.trackMode) { + evaluator.popPath(instruction[1].length); + } + let result = true; + for (let cursor = consequenceStart; cursor < consequenceEnd; cursor++) { + if (!evaluateInstruction(children[cursor], target, depth + 1, template, evaluator)) { + result = false; + break; + } + } + if (evaluator.trackMode) { + evaluator.pushPath(instruction[1]); + } + return result; + } + return true; +} + +function LoopPropertiesExcept_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const filter = instruction[5]; + const filterStrings = filter[0]; + const filterPrefixes = filter[1]; + const filterRegexes = filter[2]; + const children = instruction[6]; + for (const key in target) { + if (filterStrings.has(key)) continue; + let matched = false; + for (let index = 0; index < filterPrefixes.length; index++) { + if (key.startsWith(filterPrefixes[index])) { matched = true; break; } + } + if (matched) continue; + for (let index = 0; index < filterRegexes.length; index++) { + filterRegexes[index].lastIndex = 0; + if (filterRegexes[index].test(key)) { matched = true; break; } + } + if (matched) continue; + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return false; + } + } + } + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return true; +} + +function AssertionRegex_fast(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + return instruction[5].test(target); +} + +function LoopProperties_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const children = instruction[6]; + for (const key in target) { + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return false; + } + } + } + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return true; +} + +function AssertionDefines_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + return Object.hasOwn(target, instruction[5]); +} + +function LogicalWhenType_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (jsonTypeOf(target) !== instruction[5]) return true; + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) return false; + } + } + return true; +} + +function LogicalWhenDefines_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (!Object.hasOwn(target, instruction[5])) return true; + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) return false; + } + } + return true; +} + +function AssertionFail_fast() { return false; } + +function LoopContains_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!Array.isArray(target)) return true; + const range = instruction[5]; + const minimum = range[0]; + const maximum = range[1]; + const isExhaustive = range[2]; + if (minimum === 0 && target.length === 0) return true; + const children = instruction[6]; + let matchCount = 0; + for (let index = 0; index < target.length; index++) { + let subresult = true; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) { + subresult = false; + break; + } + } + } + if (subresult) { + matchCount++; + if (maximum !== null && matchCount > maximum) return false; + if (matchCount >= minimum && maximum === null && !isExhaustive) return true; + } + } + return matchCount >= minimum; +} + +function LogicalNot_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) return true; + } + } + return false; +} + +function LoopItemsType_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!Array.isArray(target)) return true; + const expected = instruction[5]; + for (let index = 0; index < target.length; index++) { + const actual = jsonTypeOf(target[index]); + if (actual !== expected && !(expected === Type.Integer && isIntegral(target[index]))) return false; + } + return true; +} + +function LoopItemsTypeStrict_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!Array.isArray(target)) return true; + const expected = instruction[5]; + for (let index = 0; index < target.length; index++) { + if (effectiveTypeStrictReal(target[index]) !== expected) return false; + } + return true; +} + +function AssertionEqualsAny_fast(instruction, instance, depth, template, evaluator) { + const value = instruction[5]; + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (value.primitive) return value.set.has(target); + const values = Array.isArray(value) ? value : value.values; + for (let index = 0; index < values.length; index++) { + if (jsonEqual(target, values[index])) return true; + } + return false; +} + +function AssertionDefinesAll_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + const strings = instruction[5]; + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) return false; + } + return true; +} + +function AssertionDefinesExactly_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + let targetSize = 0; + for (const key in target) targetSize++; + const strings = instruction[5]; + if (targetSize !== strings.length) return false; + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) return false; + } + return true; +} + +function AssertionDefinesExactlyStrict_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return false; + let targetSize = 0; + for (const key in target) targetSize++; + const strings = instruction[5]; + if (targetSize !== strings.length) return false; + for (let index = 0; index < strings.length; index++) { + if (!Object.hasOwn(target, strings[index])) return false; + } + return true; +} + +function AssertionDefinesExactlyStrictHash3_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return false; + const entries = instruction[5][0]; + let count = 0; + for (const key in target) count++; + if (count !== 3) return false; + return Object.hasOwn(target, entries[0][1]) && + Object.hasOwn(target, entries[1][1]) && + Object.hasOwn(target, entries[2][1]); +} + +function AssertionType_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const expected = instruction[5]; + const actual = jsonTypeOf(target); + if (actual === expected) return true; + return expected === Type.Integer && isIntegral(target); +} + +function AssertionTypeStrictAny_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + return typeSetTest(instruction[5], effectiveTypeStrictReal(target)); +} + +function AssertionTypeStringUpper_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + return typeof target === 'string' && unicodeLength(target) <= instruction[5]; +} + +function AssertionTypeArrayUpper_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + return Array.isArray(target) && target.length <= instruction[5]; +} + +function AssertionTypeObjectBounded_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return false; + const range = instruction[5]; + const size = objectSize(target); + if (size < range[0]) return false; + return range[1] === null || size <= range[1]; +} + +function AssertionTypeObjectUpper_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return false; + return objectSize(target) <= instruction[5]; +} + +function AssertionStringSizeLess_fast(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + return unicodeLength(target) < instruction[5]; +} + +function AssertionStringSizeGreater_fast(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + return unicodeLength(target) > instruction[5]; +} + +function AssertionArraySizeLess_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + return target.length < instruction[5]; +} + +function AssertionArraySizeGreater_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + return target.length > instruction[5]; +} + +function AssertionObjectSizeLess_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + return objectSize(target) < instruction[5]; +} + +function AssertionObjectSizeGreater_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + return objectSize(target) > instruction[5]; +} + +function AssertionGreaterEqual_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + return target >= instruction[5]; +} + +function AssertionLessEqual_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + return target <= instruction[5]; +} + +function AssertionGreater_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + return target > instruction[5]; +} + +function AssertionLess_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + return target < instruction[5]; +} + +function AssertionUnique_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + return isUnique(target); +} + +function AssertionDivisible_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const targetType = typeof target; + if (targetType !== 'number' && targetType !== 'bigint') return true; + return isDivisibleBy(target, instruction[5]); +} + +function AssertionStringType_fast(instruction, instance, depth, template, evaluator) { + const target = evaluator.propertyTarget !== undefined + ? evaluator.propertyTarget : resolveInstance(instance, instruction[2]); + if (typeof target !== 'string') return true; + return URI_REGEX.test(target); +} + +function AssertionPropertyType_fast(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + const expected = instruction[5]; + const actual = jsonTypeOf(target); + return actual === expected || (expected === Type.Integer && isIntegral(target)); +} + +function AssertionPropertyTypeEvaluate_fast(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + const expected = instruction[5]; + const actual = jsonTypeOf(target); + const result = actual === expected || (expected === Type.Integer && isIntegral(target)); + if (result && evaluator.trackMode) { + const location = instruction[2]; + evaluator.markEvaluated(target, instance, location.length > 0 ? location[location.length - 1] : undefined); + } + return result; +} + +function AssertionPropertyTypeStrictEvaluate_fast(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + const result = effectiveTypeStrictReal(target) === instruction[5]; + if (result && evaluator.trackMode) { + const location = instruction[2]; + evaluator.markEvaluated(target, instance, location.length > 0 ? location[location.length - 1] : undefined); + } + return result; +} + +function AssertionPropertyTypeStrictAny_fast(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + return typeSetTest(instruction[5], effectiveTypeStrictReal(target)); +} + +function AssertionPropertyTypeStrictAnyEvaluate_fast(instruction, instance, depth, template, evaluator) { + if (!isObject(instance)) return true; + const target = resolveInstance(instance, instruction[2]); + if (target === undefined) return true; + const result = typeSetTest(instruction[5], effectiveTypeStrictReal(target)); + if (result && evaluator.trackMode) { + const location = instruction[2]; + evaluator.markEvaluated(target, instance, location.length > 0 ? location[location.length - 1] : undefined); + } + return result; +} + +function AssertionArrayPrefix_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (target.length === 0) return true; + const children = instruction[6]; + const prefixes = children.length - 1; + const pointer = target.length === prefixes ? prefixes : Math.min(target.length, prefixes) - 1; + const entry = children[pointer]; + const entryChildren = entry[6]; + if (entryChildren) { + for (let index = 0; index < entryChildren.length; index++) { + if (!evaluateInstruction(entryChildren[index], target, depth + 1, template, evaluator)) return false; + } + } + return true; +} + +function AssertionArrayPrefixEvaluate_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (target.length === 0) return true; + const children = instruction[6]; + const prefixes = children.length - 1; + const pointer = target.length === prefixes ? prefixes : Math.min(target.length, prefixes) - 1; + const entry = children[pointer]; + const entryChildren = entry[6]; + if (entryChildren) { + for (let index = 0; index < entryChildren.length; index++) { + if (!evaluateInstruction(entryChildren[index], target, depth + 1, template, evaluator)) return false; + } + } + if (evaluator.trackMode) { + if (target.length === prefixes) { + evaluator.markEvaluated(target); + } else { + for (let cursor = 0; cursor <= pointer; cursor++) { + evaluator.markEvaluated(target[cursor], target, cursor); + } + } + } + return true; +} + +function AssertionObjectPropertiesSimple_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return false; + const value = instruction[5]; + const children = instruction[6]; + for (let index = 0; index < value.length; index++) { + const entry = value[index]; + const name = entry[0]; + const required = entry[2]; + if (!Object.hasOwn(target, name)) { + if (required) return false; + continue; + } + if (index < children.length) { + if (!evaluateInstructionFast(children[index], target[name], depth + 1, template, evaluator)) return false; + } + } + return true; +} + +function AnnotationEmit_fast() { return true; } +function AnnotationToParent_fast() { return true; } +function AnnotationBasenameToParent_fast() { return true; } + +function Evaluate_fast(instruction, instance, depth, template, evaluator) { + if (evaluator.trackMode) { + const target = resolveInstance(instance, instruction[2]); + evaluator.markEvaluated(target); + } + return true; +} + +function LogicalNotEvaluate_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + const children = instruction[6]; + let result = false; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) { + result = true; + break; + } + } + } + if (evaluator.trackMode) evaluator.unevaluate(); + return result; +} + +function LogicalWhenArraySizeGreater_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target) || target.length <= instruction[5]) return true; + const children = instruction[6]; + if (children) { + for (let index = 0; index < children.length; index++) { + if (!evaluateInstruction(children[index], target, depth + 1, template, evaluator)) return false; + } + } + return true; +} + +function LoopPropertiesUnevaluated_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.trackMode && evaluator.isEvaluated(target)) return true; + const children = instruction[6]; + for (const key in target) { + if (evaluator.trackMode && evaluator.isEvaluated(target[key], target, key)) continue; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) return false; + } + } + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + return true; +} + +function LoopPropertiesUnevaluatedExcept_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + if (evaluator.trackMode && evaluator.isEvaluated(target)) return true; + const filter = instruction[5]; + const filterStrings = filter[0]; + const filterPrefixes = filter[1]; + const filterRegexes = filter[2]; + const children = instruction[6]; + for (const key in target) { + if (filterStrings.has(key)) continue; + let matched = false; + for (let index = 0; index < filterPrefixes.length; index++) { + if (key.startsWith(filterPrefixes[index])) { matched = true; break; } + } + if (matched) continue; + for (let index = 0; index < filterRegexes.length; index++) { + filterRegexes[index].lastIndex = 0; + if (filterRegexes[index].test(key)) { matched = true; break; } + } + if (matched) continue; + if (evaluator.trackMode && evaluator.isEvaluated(target[key], target, key)) continue; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) return false; + } + } + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + return true; +} + +function LoopPropertiesEvaluate_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const children = instruction[6]; + for (const key in target) { + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return false; + } + } + } + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + if (evaluator.trackMode) evaluator.markEvaluated(target); + return true; +} + +function LoopPropertiesRegex_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const regex = instruction[5]; + const children = instruction[6]; + for (const key in target) { + regex.lastIndex = 0; + if (!regex.test(key)) continue; + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return false; + } + } + } + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return true; +} + +function LoopPropertiesRegexClosed_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const regex = instruction[5]; + const children = instruction[6]; + for (const key in target) { + regex.lastIndex = 0; + if (!regex.test(key)) return false; + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return false; + } + } + } + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return true; +} + +function LoopPropertiesStartsWith_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const prefix = instruction[5]; + const children = instruction[6]; + for (const key in target) { + if (!key.startsWith(prefix)) continue; + evaluator.propertyParent = target; + evaluator.propertyKey = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[key], depth + 1, template, evaluator)) { + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return false; + } + } + } + } + evaluator.propertyParent = undefined; + evaluator.propertyKey = undefined; + return true; +} + +function LoopPropertiesType_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const expected = instruction[5]; + for (const key in target) { + const actual = jsonTypeOf(target[key]); + if (actual !== expected && !(expected === Type.Integer && isIntegral(target[key]))) return false; + } + return true; +} + +function LoopPropertiesTypeEvaluate_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const expected = instruction[5]; + for (const key in target) { + const actual = jsonTypeOf(target[key]); + if (actual !== expected && !(expected === Type.Integer && isIntegral(target[key]))) return false; + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + return true; +} + +function LoopPropertiesExactlyTypeStrict_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return false; + const value = instruction[5]; + let count = 0; + for (const key in target) { + count++; + if (effectiveTypeStrictReal(target[key]) !== value[0]) return false; + } + return count === value[1].length; +} + +function LoopPropertiesExactlyTypeStrictHash_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return false; + const value = instruction[5]; + const entries = value[1][0]; + const expectedCount = entries.length; + let count = 0; + for (const key in target) { + count++; + if (effectiveTypeStrictReal(target[key]) !== value[0]) return false; + } + if (count !== expectedCount) return false; + for (let index = 0; index < expectedCount; index++) { + if (!Object.hasOwn(target, entries[index][1])) return false; + } + return true; +} + +function LoopPropertiesTypeStrict_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const expected = instruction[5]; + for (const key in target) { + if (effectiveTypeStrictReal(target[key]) !== expected) return false; + } + return true; +} + +function LoopPropertiesTypeStrictEvaluate_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const expected = instruction[5]; + for (const key in target) { + if (effectiveTypeStrictReal(target[key]) !== expected) return false; + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + return true; +} + +function LoopPropertiesTypeStrictAny_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const bitmask = instruction[5]; + for (const key in target) { + if (!typeSetTest(bitmask, effectiveTypeStrictReal(target[key]))) return false; + } + return true; +} + +function LoopPropertiesTypeStrictAnyEvaluate_fast(instruction, instance, depth, template, evaluator) { + const relInstance = instruction[2]; + const target = relInstance.length === 0 ? instance : resolveInstance(instance, relInstance); + if (!isObject(target)) return true; + const bitmask = instruction[5]; + for (const key in target) { + if (!typeSetTest(bitmask, effectiveTypeStrictReal(target[key]))) return false; + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + return true; +} + +function LoopKeys_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!isObject(target)) return true; + const children = instruction[6]; + for (const key in target) { + const previousPropertyTarget = evaluator.propertyTarget; + evaluator.propertyTarget = key; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], null, depth + 1, template, evaluator)) { + evaluator.propertyTarget = previousPropertyTarget; + return false; + } + } + } + evaluator.propertyTarget = previousPropertyTarget; + } + return true; +} + +function LoopItemsFrom_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const startIndex = instruction[5]; + if (!Array.isArray(target) || startIndex >= target.length) return true; + const children = instruction[6]; + for (let index = startIndex; index < target.length; index++) { + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) return false; + } + } + } + return true; +} + +function LoopItemsUnevaluated_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return true; + if (evaluator.trackMode && evaluator.isEvaluated(target)) return true; + const children = instruction[6]; + for (let index = 0; index < target.length; index++) { + if (evaluator.trackMode && evaluator.isEvaluated(target[index], target, index)) continue; + if (children) { + for (let childIndex = 0; childIndex < children.length; childIndex++) { + if (!evaluateInstruction(children[childIndex], target[index], depth + 1, template, evaluator)) return false; + } + } + } + if (evaluator.trackMode) evaluator.markEvaluated(target); + return true; +} + +function LoopItemsPropertiesExactlyTypeStrictHash_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target)) return false; + const expectedType = instruction[5][0]; + const entries = instruction[5][1][0]; + const expectedCount = entries.length; + for (let index = 0; index < target.length; index++) { + const item = target[index]; + if (!isObject(item)) return false; + let count = 0; + for (const key in item) { + count++; + if (effectiveTypeStrictReal(item[key]) !== expectedType) return false; + } + if (count !== expectedCount) return false; + for (let entry = 0; entry < expectedCount; entry++) { + if (!Object.hasOwn(item, entries[entry][1])) return false; + } + } + return true; +} + +function ControlDynamicAnchorJump_fast(instruction, instance, depth, template, evaluator) { + const resolved = resolveInstance(instance, instruction[2]); + const anchor = instruction[5]; + if (!evaluator.resources) return false; + const anchors = template[5]; + for (let index = 0; index < evaluator.resources.length; index++) { + const jumpTarget = anchors.get(evaluator.resources[index] + ':' + anchor); + if (jumpTarget !== undefined) { + for (let childIndex = 0; childIndex < jumpTarget.length; childIndex++) { + if (!evaluateInstruction(jumpTarget[childIndex], resolved, depth + 1, template, evaluator)) return false; + } + return true; + } + } + return false; +} + +function LoopItemsIntegerBounded_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target) || target.length === 0) return true; + const minimum = instruction[5][0]; + const maximum = instruction[5][1]; + for (let index = 0; index < target.length; index++) { + const element = target[index]; + const elementType = typeof element; + if ((elementType !== 'number' && elementType !== 'bigint') || element < minimum || element > maximum) return false; + } + return true; +} + +function LoopItemsIntegerBoundedSized_fast(instruction, instance, depth, template, evaluator) { + const value = instruction[5]; + const minimum = value[0][0]; + const maximum = value[0][1]; + const minimumSize = value[1][0]; + const target = resolveInstance(instance, instruction[2]); + if (!Array.isArray(target) || target.length < minimumSize) return false; + for (let index = 0; index < target.length; index++) { + const element = target[index]; + const elementType = typeof element; + if ((elementType !== 'number' && elementType !== 'bigint') || element < minimum || element > maximum) return false; + } + return true; +} + +function AssertionTypeIntegerBounded_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const range = instruction[5]; + return (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0] && target <= range[1]; +} + +function AssertionTypeIntegerBoundedStrict_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const range = instruction[5]; + return (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0] && target <= range[1]; +} + +function AssertionTypeIntegerLowerBound_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const range = instruction[5]; + return (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0]; +} + +function AssertionTypeIntegerLowerBoundStrict_fast(instruction, instance, depth, template, evaluator) { + const target = resolveInstance(instance, instruction[2]); + const range = instruction[5]; + return (typeof target === 'bigint' || Number.isInteger(target)) && target >= range[0]; +} + +const fastHandlers = handlers.slice(); +fastHandlers[15] = AssertionTypeArrayBounded_fast; +fastHandlers[86] = LoopItemsTypeStrictAny_fast; +fastHandlers[42] = AssertionPropertyTypeStrict_fast; +fastHandlers[11] = AssertionTypeStrict_fast; +fastHandlers[4] = AssertionDefinesAllStrict_fast; +fastHandlers[26] = AssertionEqual_fast; +fastHandlers[64] = LoopPropertiesMatch_fast; +fastHandlers[55] = LogicalOr_fast; +fastHandlers[98] = ControlJump_fast; +fastHandlers[28] = AssertionEqualsAnyStringHash_fast; +fastHandlers[57] = LogicalXor_fast; +fastHandlers[2] = AssertionDefinesStrict_fast; +fastHandlers[81] = LoopItems_fast; +fastHandlers[65] = LoopPropertiesMatchClosed_fast; +fastHandlers[13] = AssertionTypeStringBounded_fast; +fastHandlers[56] = LogicalAnd_fast; +fastHandlers[8] = AssertionPropertyDependencies_fast; +fastHandlers[10] = AssertionTypeAny_fast; +fastHandlers[58] = LogicalCondition_fast; +fastHandlers[71] = LoopPropertiesExcept_fast; +fastHandlers[19] = AssertionRegex_fast; +fastHandlers[66] = LoopProperties_fast; +fastHandlers[1] = AssertionDefines_fast; +fastHandlers[59] = LogicalWhenType_fast; +fastHandlers[60] = LogicalWhenDefines_fast; +fastHandlers[0] = AssertionFail_fast; +fastHandlers[91] = LoopContains_fast; +fastHandlers[53] = LogicalNot_fast; +fastHandlers[84] = LoopItemsType_fast; +fastHandlers[85] = LoopItemsTypeStrict_fast; +fastHandlers[27] = AssertionEqualsAny_fast; +fastHandlers[3] = AssertionDefinesAll_fast; +fastHandlers[5] = AssertionDefinesExactly_fast; +fastHandlers[6] = AssertionDefinesExactlyStrict_fast; +fastHandlers[7] = AssertionDefinesExactlyStrictHash3_fast; +fastHandlers[9] = AssertionType_fast; +fastHandlers[12] = AssertionTypeStrictAny_fast; +fastHandlers[14] = AssertionTypeStringUpper_fast; +fastHandlers[16] = AssertionTypeArrayUpper_fast; +fastHandlers[17] = AssertionTypeObjectBounded_fast; +fastHandlers[18] = AssertionTypeObjectUpper_fast; +fastHandlers[20] = AssertionStringSizeLess_fast; +fastHandlers[21] = AssertionStringSizeGreater_fast; +fastHandlers[22] = AssertionArraySizeLess_fast; +fastHandlers[23] = AssertionArraySizeGreater_fast; +fastHandlers[24] = AssertionObjectSizeLess_fast; +fastHandlers[25] = AssertionObjectSizeGreater_fast; +fastHandlers[29] = AssertionGreaterEqual_fast; +fastHandlers[30] = AssertionLessEqual_fast; +fastHandlers[31] = AssertionGreater_fast; +fastHandlers[32] = AssertionLess_fast; +fastHandlers[33] = AssertionUnique_fast; +fastHandlers[34] = AssertionDivisible_fast; +fastHandlers[35] = AssertionTypeIntegerBounded_fast; +fastHandlers[36] = AssertionTypeIntegerBoundedStrict_fast; +fastHandlers[37] = AssertionTypeIntegerLowerBound_fast; +fastHandlers[38] = AssertionTypeIntegerLowerBoundStrict_fast; +fastHandlers[39] = AssertionStringType_fast; +fastHandlers[40] = AssertionPropertyType_fast; +fastHandlers[41] = AssertionPropertyTypeEvaluate_fast; +fastHandlers[43] = AssertionPropertyTypeStrictEvaluate_fast; +fastHandlers[44] = AssertionPropertyTypeStrictAny_fast; +fastHandlers[45] = AssertionPropertyTypeStrictAnyEvaluate_fast; +fastHandlers[46] = AssertionArrayPrefix_fast; +fastHandlers[47] = AssertionArrayPrefixEvaluate_fast; +fastHandlers[48] = AssertionObjectPropertiesSimple_fast; +fastHandlers[49] = AnnotationEmit_fast; +fastHandlers[50] = AnnotationToParent_fast; +fastHandlers[51] = AnnotationBasenameToParent_fast; +fastHandlers[52] = Evaluate_fast; +fastHandlers[54] = LogicalNotEvaluate_fast; +fastHandlers[61] = LogicalWhenArraySizeGreater_fast; +fastHandlers[62] = LoopPropertiesUnevaluated_fast; +fastHandlers[63] = LoopPropertiesUnevaluatedExcept_fast; +fastHandlers[67] = LoopPropertiesEvaluate_fast; +fastHandlers[68] = LoopPropertiesRegex_fast; +fastHandlers[69] = LoopPropertiesRegexClosed_fast; +fastHandlers[70] = LoopPropertiesStartsWith_fast; +fastHandlers[72] = LoopPropertiesType_fast; +fastHandlers[73] = LoopPropertiesTypeEvaluate_fast; +fastHandlers[74] = LoopPropertiesExactlyTypeStrict_fast; +fastHandlers[75] = LoopPropertiesExactlyTypeStrictHash_fast; +fastHandlers[76] = LoopPropertiesTypeStrict_fast; +fastHandlers[77] = LoopPropertiesTypeStrictEvaluate_fast; +fastHandlers[78] = LoopPropertiesTypeStrictAny_fast; +fastHandlers[79] = LoopPropertiesTypeStrictAnyEvaluate_fast; +fastHandlers[80] = LoopKeys_fast; +fastHandlers[82] = LoopItemsFrom_fast; +fastHandlers[83] = LoopItemsUnevaluated_fast; +fastHandlers[87] = LoopItemsPropertiesExactlyTypeStrictHash_fast; +fastHandlers[88] = LoopItemsPropertiesExactlyTypeStrictHash_fast; +fastHandlers[89] = LoopItemsIntegerBounded_fast; +fastHandlers[90] = LoopItemsIntegerBoundedSized_fast; +fastHandlers[97] = ControlDynamicAnchorJump_fast; + +export { Blaze }; diff --git a/vendor/blaze/ports/javascript/package-lock.json b/vendor/blaze/ports/javascript/package-lock.json new file mode 100644 index 0000000..4b5a3aa --- /dev/null +++ b/vendor/blaze/ports/javascript/package-lock.json @@ -0,0 +1,12 @@ +{ + "name": "@sourcemeta/blaze", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@sourcemeta/blaze", + "version": "0.0.0" + } + } +} diff --git a/vendor/blaze/ports/javascript/package.json b/vendor/blaze/ports/javascript/package.json new file mode 100644 index 0000000..56183a2 --- /dev/null +++ b/vendor/blaze/ports/javascript/package.json @@ -0,0 +1,72 @@ +{ + "name": "@sourcemeta/blaze", + "version": "0.0.0", + "description": "A pure JavaScript port of the evaluator from Blaze, a high-performance C++ JSON Schema validator. Zero dependencies. Supports Draft 4, Draft 6, Draft 7, 2019-09, and 2020-12 with schema-specific code generation for near-native speed", + "type": "module", + "main": "./index.mjs", + "module": "./index.mjs", + "exports": { + ".": { + "import": "./index.mjs", + "default": "./index.mjs" + } + }, + "types": "./index.d.mts", + "license": "LGPL-3.0-or-later", + "homepage": "https://github.com/sourcemeta/blaze", + "author": { + "email": "hello@sourcemeta.com", + "name": "Sourcemeta", + "url": "https://www.sourcemeta.com" + }, + "engines": { + "node": ">=18" + }, + "funding": "https://github.com/sponsors/sourcemeta", + "keywords": [ + "jsonschema", + "json", + "schema", + "json-schema", + "validator", + "validation", + "json-schema-validator", + "json-schema-validation", + "blaze", + "sourcemeta", + "fast", + "performance", + "codegen", + "draft-04", + "draft-06", + "draft-07", + "draft4", + "draft6", + "draft7", + "2019-09", + "2020-12", + "ajv", + "ajv-alternative", + "$ref", + "validate", + "evaluator" + ], + "bugs": { + "url": "https://github.com/sourcemeta/blaze/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/sourcemeta/jsonschema.git", + "directory": "vendor/blaze/ports/javascript" + }, + "publishConfig": { + "provenance": true, + "access": "public" + }, + "files": [ + "index.mjs", + "index.d.mts", + "README.md", + "LICENSE" + ] +} diff --git a/vendor/blaze/schemas/canonical-2019-09.json b/vendor/blaze/schemas/canonical-2019-09.json new file mode 100644 index 0000000..a3803ed --- /dev/null +++ b/vendor/blaze/schemas/canonical-2019-09.json @@ -0,0 +1,830 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:2019-09", + "title": "Canonical JSON Schema 2019-09", + "description": "Meta-schema that validates whether a given JSON Schema 2019-09 document is in canonical form", + "examples": [ + true, + { + "$ref": "#/$defs/foo" + }, + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "examples": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "patternProperties": { + "^x-": true + } + }, + "unevaluated": { + "not": { + "required": [ "unevaluatedProperties", "unevaluatedItems" ] + }, + "properties": { + "unevaluatedProperties": { + "$ref": "#/$defs/schema", + "not": { + "const": true + } + }, + "unevaluatedItems": { + "$ref": "#/$defs/schema", + "not": { + "const": true + } + } + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + }, + "$id": { + "type": "string" + }, + "$anchor": { + "type": "string" + }, + "$recursiveAnchor": { + "const": true + }, + "$vocabulary": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "type": "boolean" + } + }, + "$defs": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/schema" + } + } + } + }, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$ref" ], + "properties": { + "$ref": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$recursiveRef" ], + "properties": { + "$recursiveRef": { + "const": "#" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentSchema": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "multipleOf" ], + "properties": { + "type": { + "const": "integer" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "additionalProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "additionalProperties": { + "$ref": "#/$defs/schema" + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "unevaluatedProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": { + "$ref": "#/$defs/schema" + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "contains", + "minContains", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "contains", + "minContains", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "additionalItems", + "contains", + "minContains", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "additionalItems": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "unevaluatedItems", + "contains", + "minContains", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedItems": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "contains", + "minContains", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "if", "then", "else" ], + "properties": { + "if": { + "$ref": "#/$defs/schema" + }, + "then": { + "$ref": "#/$defs/schema" + }, + "else": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "anyOf" ], + "properties": { + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "allOf" ], + "properties": { + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "oneOf" ], + "properties": { + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "not" ], + "properties": { + "not": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/canonical-2020-12.json b/vendor/blaze/schemas/canonical-2020-12.json new file mode 100644 index 0000000..7988f5a --- /dev/null +++ b/vendor/blaze/schemas/canonical-2020-12.json @@ -0,0 +1,806 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:2020-12", + "title": "Canonical JSON Schema 2020-12", + "description": "Meta-schema that validates whether a given JSON Schema 2020-12 document is in canonical form", + "examples": [ + true, + { + "$ref": "#/$defs/foo" + }, + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean" + }, + "readOnly": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "examples": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "patternProperties": { + "^x-": true + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + }, + "$id": { + "type": "string" + }, + "$anchor": { + "type": "string" + }, + "$dynamicAnchor": { + "type": "string" + }, + "$vocabulary": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "type": "boolean" + } + }, + "$defs": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/schema" + } + } + } + }, + "unevaluated": { + "not": { + "required": [ "unevaluatedProperties", "unevaluatedItems" ] + }, + "properties": { + "unevaluatedProperties": { + "$ref": "#/$defs/schema", + "not": { + "const": true + } + }, + "unevaluatedItems": { + "$ref": "#/$defs/schema", + "not": { + "const": true + } + } + } + }, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$ref" ], + "properties": { + "$ref": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$dynamicRef" ], + "properties": { + "$dynamicRef": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentSchema": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "multipleOf" ], + "properties": { + "type": { + "const": "integer" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "additionalProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "additionalProperties": { + "$ref": "#/$defs/schema" + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "unevaluatedProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": { + "$ref": "#/$defs/schema" + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "items", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "prefixItems", + "items", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "prefixItems": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "items": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "prefixItems", + "unevaluatedItems", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "prefixItems": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedItems": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "prefixItems", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "prefixItems": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minContains": { + "type": "integer", + "minimum": 0 + }, + "maxContains": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "if", "then", "else" ], + "properties": { + "if": { + "$ref": "#/$defs/schema" + }, + "then": { + "$ref": "#/$defs/schema" + }, + "else": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "anyOf" ], + "properties": { + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "allOf" ], + "properties": { + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/unevaluated" + } + ], + "required": [ "oneOf" ], + "properties": { + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "not" ], + "properties": { + "not": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/canonical-draft1.json b/vendor/blaze/schemas/canonical-draft1.json new file mode 100644 index 0000000..2d40e49 --- /dev/null +++ b/vendor/blaze/schemas/canonical-draft1.json @@ -0,0 +1,388 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:draft1", + "title": "Canonical JSON Schema Draft 1", + "description": "Meta-schema that validates whether a given JSON Schema Draft 1 document is in canonical form", + "examples": [ + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true + }, + "patternProperties": { + "^x-": true + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + } + } + }, + "typed": { + "properties": { + "optional": { + "type": "boolean" + }, + "requires": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/schema" + } + ] + } + } + }, + "subproperty": { + "anyOf": [ + { + "$ref": "#/$defs/schema", + "required": [ "optional" ], + "properties": { + "optional": { + "type": "boolean" + } + } + }, + { + "const": {} + } + ] + }, + "schema": { + "anyOf": [ + { + "const": {} + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "maxDecimal" ], + "properties": { + "type": { + "const": "integer" + }, + "maxDecimal": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "maxDecimal": { + "type": "integer", + "minimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "minimumCanEqual": { + "type": "boolean" + }, + "maximumCanEqual": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "properties", "additionalProperties" ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/subproperty" + } + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/schema" + }, + { + "type": "boolean" + } + ] + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "items", "minItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "items", "minItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "extends" ], + "properties": { + "extends": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "disallow" ], + "properties": { + "disallow": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/canonical-draft2.json b/vendor/blaze/schemas/canonical-draft2.json new file mode 100644 index 0000000..4e3144c --- /dev/null +++ b/vendor/blaze/schemas/canonical-draft2.json @@ -0,0 +1,394 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:draft2", + "title": "Canonical JSON Schema Draft 2", + "description": "Meta-schema that validates whether a given JSON Schema Draft 2 document is in canonical form", + "examples": [ + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true + }, + "patternProperties": { + "^x-": true + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + } + } + }, + "typed": { + "properties": { + "optional": { + "type": "boolean" + }, + "requires": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/schema" + } + ] + } + } + }, + "subproperty": { + "anyOf": [ + { + "$ref": "#/$defs/schema", + "required": [ "optional" ], + "properties": { + "optional": { + "type": "boolean" + } + } + }, + { + "const": {} + } + ] + }, + "schema": { + "anyOf": [ + { + "const": {} + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "divisibleBy" ], + "properties": { + "type": { + "const": "integer" + }, + "divisibleBy": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "divisibleBy": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "minimumCanEqual": { + "type": "boolean" + }, + "maximumCanEqual": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "properties", "additionalProperties" ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/subproperty" + } + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/schema" + }, + { + "type": "boolean" + } + ] + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "items", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + }, + { + "$ref": "#/$defs/typed" + } + ], + "required": [ "type", "items", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "extends" ], + "properties": { + "extends": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "disallow" ], + "properties": { + "disallow": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/canonical-draft3.json b/vendor/blaze/schemas/canonical-draft3.json new file mode 100644 index 0000000..9089bf0 --- /dev/null +++ b/vendor/blaze/schemas/canonical-draft3.json @@ -0,0 +1,430 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:draft3", + "title": "Canonical JSON Schema Draft 3", + "description": "Meta-schema that validates whether a given JSON Schema Draft 3 document is in canonical form", + "examples": [ + { + "$ref": "#/foo" + }, + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true + }, + "patternProperties": { + "^x-": true + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + }, + "id": { + "type": "string" + } + } + }, + "subproperty": { + "$ref": "#/$defs/schema", + "if": { + "type": "object", + "not": { + "anyOf": [ + { + "maxProperties": 0 + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "required": [ "$ref" ], + "properties": { + "$ref": true + } + } + ] + } + }, + "then": { + "required": [ "required" ], + "properties": { + "required": { + "type": "boolean" + } + } + } + }, + "schema": { + "anyOf": [ + { + "const": {} + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$ref" ], + "properties": { + "$ref": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "divisibleBy" ], + "properties": { + "type": { + "const": "integer" + }, + "divisibleBy": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "required": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "divisibleBy": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean" + }, + "exclusiveMaximum": { + "type": "boolean" + }, + "required": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "additionalProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/subproperty" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/schema" + }, + { + "type": "boolean" + } + ] + }, + "required": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "items", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + }, + "required": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "additionalItems", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "additionalItems": { + "anyOf": [ + { + "$ref": "#/$defs/schema" + }, + { + "type": "boolean" + } + ] + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + }, + "required": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "extends" ], + "properties": { + "extends": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "disallow" ], + "properties": { + "disallow": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/canonical-draft4.json b/vendor/blaze/schemas/canonical-draft4.json new file mode 100644 index 0000000..0d7b0f8 --- /dev/null +++ b/vendor/blaze/schemas/canonical-draft4.json @@ -0,0 +1,414 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:draft4", + "title": "Canonical JSON Schema Draft 4", + "description": "Meta-schema that validates whether a given JSON Schema Draft 4 document is in canonical form", + "examples": [ + true, + { + "$ref": "#/definitions/foo" + }, + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true + }, + "patternProperties": { + "^x-": true + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + }, + "id": { + "type": "string" + }, + "definitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/schema" + } + } + } + }, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$ref" ], + "properties": { + "$ref": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "multipleOf" ], + "properties": { + "type": { + "const": "integer" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean" + }, + "exclusiveMaximum": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "additionalProperties", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "additionalProperties": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "items", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "additionalItems", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "additionalItems": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "anyOf" ], + "properties": { + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "allOf" ], + "properties": { + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "oneOf" ], + "properties": { + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "not" ], + "properties": { + "not": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/canonical-draft6.json b/vendor/blaze/schemas/canonical-draft6.json new file mode 100644 index 0000000..a8ccdb7 --- /dev/null +++ b/vendor/blaze/schemas/canonical-draft6.json @@ -0,0 +1,429 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:draft6", + "title": "Canonical JSON Schema Draft 6", + "description": "Meta-schema that validates whether a given JSON Schema Draft 6 document is in canonical form", + "examples": [ + true, + { + "$ref": "#/definitions/foo" + }, + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "examples": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "patternProperties": { + "^x-": true + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + }, + "$id": { + "type": "string" + }, + "definitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/schema" + } + } + } + }, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$ref" ], + "properties": { + "$ref": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "multipleOf" ], + "properties": { + "type": { + "const": "integer" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "additionalProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "additionalProperties": { + "$ref": "#/$defs/schema" + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "items", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "additionalItems", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "additionalItems": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "anyOf" ], + "properties": { + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "allOf" ], + "properties": { + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "oneOf" ], + "properties": { + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "not" ], + "properties": { + "not": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/canonical-draft7.json b/vendor/blaze/schemas/canonical-draft7.json new file mode 100644 index 0000000..a47fe87 --- /dev/null +++ b/vendor/blaze/schemas/canonical-draft7.json @@ -0,0 +1,466 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "tag:sourcemeta.com,2026:canonical:draft7", + "title": "Canonical JSON Schema Draft 7", + "description": "Meta-schema that validates whether a given JSON Schema Draft 7 document is in canonical form", + "examples": [ + true, + { + "$ref": "#/definitions/foo" + }, + { + "enum": [ 1, 2, 3 ] + }, + { + "type": "string", + "minLength": 0 + } + ], + "$ref": "#/$defs/schema", + "$defs": { + "metadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean" + }, + "writeOnly": { + "type": "boolean" + }, + "examples": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "patternProperties": { + "^x-": true + } + }, + "core": { + "x-lint-exclude": "simple_properties_identifiers", + "properties": { + "$schema": { + "type": "string" + }, + "$id": { + "type": "string" + }, + "definitions": { + "type": "object", + "minProperties": 1, + "additionalProperties": { + "$ref": "#/$defs/schema" + } + } + } + }, + "schema": { + "anyOf": [ + { + "type": "boolean" + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "$ref": "#/$defs/metadata", + "type": "object", + "required": [ "$ref" ], + "properties": { + "$ref": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "enum" ], + "properties": { + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "minLength" ], + "properties": { + "type": { + "const": "string" + }, + "minLength": { + "type": "integer", + "minimum": 0 + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "pattern": { + "type": "string" + }, + "format": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "multipleOf" ], + "properties": { + "type": { + "const": "integer" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type" ], + "properties": { + "type": { + "const": "number" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "properties", + "patternProperties", + "additionalProperties", + "propertyNames", + "minProperties" + ], + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/schema" + } + }, + "additionalProperties": { + "$ref": "#/$defs/schema" + }, + "propertyNames": { + "$ref": "#/$defs/schema" + }, + "required": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "minProperties": { + "type": "integer", + "minimum": 0 + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "type", "items", "minItems", "uniqueItems" ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ + "type", + "items", + "additionalItems", + "minItems", + "uniqueItems" + ], + "properties": { + "type": { + "const": "array" + }, + "items": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + }, + "additionalItems": { + "$ref": "#/$defs/schema" + }, + "contains": { + "$ref": "#/$defs/schema" + }, + "minItems": { + "type": "integer", + "minimum": 0 + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "uniqueItems": { + "type": "boolean" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "if", "then", "else" ], + "properties": { + "if": { + "$ref": "#/$defs/schema" + }, + "then": { + "$ref": "#/$defs/schema" + }, + "else": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "anyOf" ], + "properties": { + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "allOf" ], + "properties": { + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "oneOf" ], + "properties": { + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/schema" + } + } + }, + "unevaluatedProperties": false + }, + { + "x-lint-exclude": "simple_properties_identifiers", + "type": "object", + "allOf": [ + { + "$ref": "#/$defs/metadata" + }, + { + "$ref": "#/$defs/core" + } + ], + "required": [ "not" ], + "properties": { + "not": { + "$ref": "#/$defs/schema" + } + }, + "unevaluatedProperties": false + } + ] + } + } +} diff --git a/vendor/blaze/schemas/jsonschema.json b/vendor/blaze/schemas/jsonschema.json new file mode 100644 index 0000000..eff66fa --- /dev/null +++ b/vendor/blaze/schemas/jsonschema.json @@ -0,0 +1,3 @@ +{ + "defaultDialect": "https://json-schema.org/draft/2020-12/schema" +} diff --git a/vendor/core/src/extension/alterschema/CMakeLists.txt b/vendor/blaze/src/alterschema/CMakeLists.txt similarity index 54% rename from vendor/core/src/extension/alterschema/CMakeLists.txt rename to vendor/blaze/src/alterschema/CMakeLists.txt index 5fe9f4e..beba7f1 100644 --- a/vendor/core/src/extension/alterschema/CMakeLists.txt +++ b/vendor/blaze/src/alterschema/CMakeLists.txt @@ -1,23 +1,66 @@ -sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema - SOURCES alterschema.cc +sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME alterschema + FOLDER "Blaze/AlterSchema" + PRIVATE_HEADERS error.h transformer.h + SOURCES alterschema.cc schema_rule.cc transformer.cc # Canonicalizer + canonicalizer/additional_items_implicit.h + canonicalizer/additional_properties_implicit.h + canonicalizer/comment_drop.h canonicalizer/const_as_enum.h + canonicalizer/dependencies_to_any_of.h + canonicalizer/dependencies_to_extends_disallow.h + canonicalizer/dependent_required_to_any_of.h + canonicalizer/dependent_schemas_to_any_of.h + canonicalizer/deprecated_false_drop.h + canonicalizer/disallow_to_array_of_schemas.h + canonicalizer/divisible_by_implicit.h + canonicalizer/empty_definitions_drop.h + canonicalizer/empty_defs_drop.h + canonicalizer/empty_dependencies_drop.h + canonicalizer/empty_dependent_required_drop.h + canonicalizer/empty_dependent_schemas_drop.h + canonicalizer/enum_drop_redundant_validation.h + canonicalizer/enum_filter_by_type.h + canonicalizer/exclusive_bounds_false_drop.h + canonicalizer/exclusive_maximum_boolean_integer_fold.h canonicalizer/exclusive_maximum_integer_to_maximum.h + canonicalizer/exclusive_minimum_boolean_integer_fold.h canonicalizer/exclusive_minimum_integer_to_minimum.h + canonicalizer/extends_to_array.h + canonicalizer/if_then_else_implicit.h + canonicalizer/implicit_array_keywords.h + canonicalizer/implicit_contains_keywords.h + canonicalizer/implicit_object_keywords.h canonicalizer/items_implicit.h canonicalizer/max_contains_covered_by_max_items.h + canonicalizer/max_decimal_implicit.h + canonicalizer/maximum_can_equal_integer_fold.h + canonicalizer/maximum_can_equal_true_drop.h canonicalizer/min_items_given_min_contains.h - canonicalizer/min_items_implicit.h canonicalizer/min_length_implicit.h canonicalizer/min_properties_covered_by_required.h canonicalizer/min_properties_implicit.h + canonicalizer/minimum_can_equal_integer_fold.h + canonicalizer/minimum_can_equal_true_drop.h canonicalizer/multiple_of_implicit.h - canonicalizer/no_metadata.h + canonicalizer/optional_property_implicit.h canonicalizer/properties_implicit.h + canonicalizer/property_names_implicit.h + canonicalizer/recursive_anchor_false_drop.h + canonicalizer/required_property_implicit.h canonicalizer/type_array_to_any_of.h canonicalizer/type_boolean_as_enum.h + canonicalizer/type_inherit_in_place.h canonicalizer/type_null_as_enum.h canonicalizer/type_union_implicit.h + canonicalizer/type_union_to_schemas.h + canonicalizer/type_with_applicator_to_allof.h + canonicalizer/type_with_applicator_to_extends.h + canonicalizer/unevaluated_items_to_items.h + canonicalizer/unevaluated_properties_to_additional_properties.h + canonicalizer/unsatisfiable_can_equal_bounds.h + canonicalizer/unsatisfiable_exclusive_equal_bounds.h + canonicalizer/unsatisfiable_type_and_enum.h # Common common/allof_false_simplify.h @@ -40,7 +83,6 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema common/duplicate_enum_values.h common/duplicate_required_values.h common/empty_object_as_true.h - common/else_empty.h common/else_without_if.h common/enum_with_type.h common/equal_numeric_bounds_to_enum.h @@ -62,14 +104,11 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema common/oneof_to_anyof_disjoint_types.h common/required_properties_in_properties.h common/single_type_array.h - common/then_empty.h common/then_without_if.h common/unknown_keywords_prefix.h common/unknown_local_ref.h common/unsatisfiable_drop_validation.h common/unnecessary_allof_ref_wrapper_draft.h - common/unnecessary_allof_ref_wrapper_modern.h - common/unnecessary_allof_wrapper.h common/unsatisfiable_in_place_applicator_type.h # Linter @@ -82,9 +121,11 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/description_trailing_period.h linter/description_trim.h linter/duplicate_examples.h + linter/else_empty.h linter/enum_to_const.h linter/equal_numeric_bounds_to_const.h linter/forbid_empty_enum.h + linter/incoherent_min_max_contains.h linter/invalid_external_ref.h linter/items_array_default.h linter/items_schema_default.h @@ -94,6 +135,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/property_names_default.h linter/property_names_type_default.h linter/simple_properties_identifiers.h + linter/then_empty.h linter/title_description_equal.h linter/title_trailing_period.h linter/title_trim.h @@ -102,14 +144,26 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME alterschema linter/top_level_title.h linter/unevaluated_items_default.h linter/unevaluated_properties_default.h + linter/unnecessary_allof_ref_wrapper_modern.h + linter/unnecessary_allof_wrapper.h linter/unsatisfiable_max_contains.h - linter/unsatisfiable_min_properties.h) + linter/unsatisfiable_min_properties.h + linter/valid_default.h + linter/valid_examples.h) -if(SOURCEMETA_CORE_INSTALL) - sourcemeta_library_install(NAMESPACE sourcemeta PROJECT core NAME alterschema) +if(BLAZE_INSTALL) + sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME alterschema) endif() -target_link_libraries(sourcemeta_core_alterschema PUBLIC +target_link_libraries(sourcemeta_blaze_alterschema PUBLIC sourcemeta::core::jsonschema) -target_link_libraries(sourcemeta_core_alterschema PRIVATE +target_link_libraries(sourcemeta_blaze_alterschema PUBLIC + sourcemeta::blaze::compiler) +target_link_libraries(sourcemeta_blaze_alterschema PRIVATE + sourcemeta::blaze::evaluator) +target_link_libraries(sourcemeta_blaze_alterschema PRIVATE + sourcemeta::blaze::output) +target_link_libraries(sourcemeta_blaze_alterschema PRIVATE sourcemeta::core::regex) +target_link_libraries(sourcemeta_blaze_alterschema PRIVATE + sourcemeta::core::uri) diff --git a/vendor/core/src/extension/alterschema/alterschema.cc b/vendor/blaze/src/alterschema/alterschema.cc similarity index 51% rename from vendor/core/src/extension/alterschema/alterschema.cc rename to vendor/blaze/src/alterschema/alterschema.cc index a6e9744..c9fbaf1 100644 --- a/vendor/core/src/extension/alterschema/alterschema.cc +++ b/vendor/blaze/src/alterschema/alterschema.cc @@ -1,15 +1,29 @@ -#include +#include +#include +#include +#include #include // For built-in rules -#include // std::sort, std::unique -#include // std::floor +#include // std::sort, std::unique, std::ranges::none_of +#include // std::array +#include // std::popcount +#include // assert +#include // std::floor, std::ceil, std::isfinite +#include // std::size_t +#include // std::ref #include // std::back_inserter +#include // std::numeric_limits #include // std::unique_ptr, std::make_unique +#include // std::ostringstream +#include // std::string_view #include // std::unordered_map #include // std::unordered_set #include // std::move, std::to_underlying -namespace sourcemeta::core { + +namespace sourcemeta::blaze { + +using namespace sourcemeta::core; template auto APPLIES_TO_KEYWORDS(Args &&...args) -> SchemaTransformRule::Result { @@ -24,29 +38,133 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) return {std::move(keywords)}; } +// TODO: Move upstream +inline auto IS_IN_PLACE_APPLICATOR(const SchemaKeywordType type) -> bool { + return type == SchemaKeywordType::ApplicatorValueOrElementsInPlace || + type == SchemaKeywordType::ApplicatorMembersInPlaceSome || + type == SchemaKeywordType::ApplicatorElementsInPlace || + type == SchemaKeywordType::ApplicatorElementsInPlaceSome || + type == SchemaKeywordType::ApplicatorElementsInPlaceSomeNegate || + type == SchemaKeywordType::ApplicatorValueInPlaceMaybe || + type == SchemaKeywordType::ApplicatorValueInPlaceOther || + type == SchemaKeywordType::ApplicatorValueInPlaceNegate; +} + +// Walk up from a schema location, continuing as long as the traversal +// predicate returns true for each keyword type encountered. Returns a +// reference to the pointer of the ancestor where the match callback returned +// true, or nullopt if no match was found or the traversal predicate stopped +// the walk. +template +auto WALK_UP(const JSON &root, const SchemaFrame &frame, + const SchemaFrame::Location &location, const SchemaWalker &walker, + const SchemaResolver &resolver, + const TraversePredicate &should_continue, + const MatchCallback &matches) + -> std::optional> { + auto current_pointer{location.pointer}; + auto current_parent{location.parent}; + + while (current_parent.has_value()) { + const auto &parent_pointer{current_parent.value()}; + const auto relative_pointer{current_pointer.resolve_from(parent_pointer)}; + assert(!relative_pointer.empty() && relative_pointer.at(0).is_property()); + const auto parent{frame.traverse(frame.uri(parent_pointer).value().get())}; + assert(parent.has_value()); + const auto parent_vocabularies{ + frame.vocabularies(parent.value().get(), resolver)}; + const auto keyword_type{ + walker(relative_pointer.at(0).to_property(), parent_vocabularies).type}; + + if (!should_continue(keyword_type)) { + return std::nullopt; + } + + if (matches(get(root, parent_pointer), parent_vocabularies)) { + return std::cref(parent.value().get().pointer); + } + + current_pointer = parent_pointer; + current_parent = parent.value().get().parent; + } + + return std::nullopt; +} + +template +auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame, + const SchemaFrame::Location &location, + const SchemaWalker &walker, + const SchemaResolver &resolver, + const MatchCallback &matches) + -> std::optional> { + return WALK_UP(root, frame, location, walker, resolver, + IS_IN_PLACE_APPLICATOR, matches); +} + #define ONLY_CONTINUE_IF(condition) \ if (!(condition)) { \ return false; \ } -// Canonicalizer +#include "canonicalizer/additional_items_implicit.h" +#include "canonicalizer/additional_properties_implicit.h" +#include "canonicalizer/comment_drop.h" #include "canonicalizer/const_as_enum.h" +#include "canonicalizer/dependencies_to_any_of.h" +#include "canonicalizer/dependencies_to_extends_disallow.h" +#include "canonicalizer/dependent_required_to_any_of.h" +#include "canonicalizer/dependent_schemas_to_any_of.h" +#include "canonicalizer/deprecated_false_drop.h" +#include "canonicalizer/disallow_to_array_of_schemas.h" +#include "canonicalizer/divisible_by_implicit.h" +#include "canonicalizer/empty_definitions_drop.h" +#include "canonicalizer/empty_defs_drop.h" +#include "canonicalizer/empty_dependencies_drop.h" +#include "canonicalizer/empty_dependent_required_drop.h" +#include "canonicalizer/empty_dependent_schemas_drop.h" +#include "canonicalizer/enum_drop_redundant_validation.h" +#include "canonicalizer/enum_filter_by_type.h" +#include "canonicalizer/exclusive_bounds_false_drop.h" +#include "canonicalizer/exclusive_maximum_boolean_integer_fold.h" #include "canonicalizer/exclusive_maximum_integer_to_maximum.h" +#include "canonicalizer/exclusive_minimum_boolean_integer_fold.h" #include "canonicalizer/exclusive_minimum_integer_to_minimum.h" +#include "canonicalizer/extends_to_array.h" +#include "canonicalizer/if_then_else_implicit.h" +#include "canonicalizer/implicit_array_keywords.h" +#include "canonicalizer/implicit_contains_keywords.h" +#include "canonicalizer/implicit_object_keywords.h" #include "canonicalizer/items_implicit.h" #include "canonicalizer/max_contains_covered_by_max_items.h" +#include "canonicalizer/max_decimal_implicit.h" +#include "canonicalizer/maximum_can_equal_integer_fold.h" +#include "canonicalizer/maximum_can_equal_true_drop.h" #include "canonicalizer/min_items_given_min_contains.h" -#include "canonicalizer/min_items_implicit.h" #include "canonicalizer/min_length_implicit.h" #include "canonicalizer/min_properties_covered_by_required.h" #include "canonicalizer/min_properties_implicit.h" +#include "canonicalizer/minimum_can_equal_integer_fold.h" +#include "canonicalizer/minimum_can_equal_true_drop.h" #include "canonicalizer/multiple_of_implicit.h" -#include "canonicalizer/no_metadata.h" +#include "canonicalizer/optional_property_implicit.h" #include "canonicalizer/properties_implicit.h" +#include "canonicalizer/property_names_implicit.h" +#include "canonicalizer/recursive_anchor_false_drop.h" +#include "canonicalizer/required_property_implicit.h" #include "canonicalizer/type_array_to_any_of.h" #include "canonicalizer/type_boolean_as_enum.h" +#include "canonicalizer/type_inherit_in_place.h" #include "canonicalizer/type_null_as_enum.h" #include "canonicalizer/type_union_implicit.h" +#include "canonicalizer/type_union_to_schemas.h" +#include "canonicalizer/type_with_applicator_to_allof.h" +#include "canonicalizer/type_with_applicator_to_extends.h" +#include "canonicalizer/unevaluated_items_to_items.h" +#include "canonicalizer/unevaluated_properties_to_additional_properties.h" +#include "canonicalizer/unsatisfiable_can_equal_bounds.h" +#include "canonicalizer/unsatisfiable_exclusive_equal_bounds.h" +#include "canonicalizer/unsatisfiable_type_and_enum.h" // Common #include "common/allof_false_simplify.h" @@ -67,7 +185,6 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "common/duplicate_anyof_branches.h" #include "common/duplicate_enum_values.h" #include "common/duplicate_required_values.h" -#include "common/else_empty.h" #include "common/else_without_if.h" #include "common/empty_object_as_true.h" #include "common/enum_with_type.h" @@ -91,15 +208,16 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "common/orphan_definitions.h" #include "common/required_properties_in_properties.h" #include "common/single_type_array.h" -#include "common/then_empty.h" #include "common/then_without_if.h" #include "common/unknown_keywords_prefix.h" #include "common/unknown_local_ref.h" #include "common/unnecessary_allof_ref_wrapper_draft.h" -#include "common/unnecessary_allof_ref_wrapper_modern.h" -#include "common/unnecessary_allof_wrapper.h" #include "common/unsatisfiable_drop_validation.h" #include "common/unsatisfiable_in_place_applicator_type.h" +#include "linter/else_empty.h" +#include "linter/then_empty.h" +#include "linter/unnecessary_allof_ref_wrapper_modern.h" +#include "linter/unnecessary_allof_wrapper.h" // Linter #include "linter/comment_trim.h" @@ -114,6 +232,7 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "linter/enum_to_const.h" #include "linter/equal_numeric_bounds_to_const.h" #include "linter/forbid_empty_enum.h" +#include "linter/incoherent_min_max_contains.h" #include "linter/invalid_external_ref.h" #include "linter/items_array_default.h" #include "linter/items_schema_default.h" @@ -133,19 +252,46 @@ inline auto APPLIES_TO_POINTERS(std::vector &&keywords) #include "linter/unevaluated_properties_default.h" #include "linter/unsatisfiable_max_contains.h" #include "linter/unsatisfiable_min_properties.h" +#include "linter/valid_default.h" +#include "linter/valid_examples.h" #undef ONLY_CONTINUE_IF -} // namespace sourcemeta::core +} // namespace sourcemeta::blaze -namespace sourcemeta::core { +namespace sourcemeta::blaze { auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { if (mode == AlterSchemaMode::Canonicalizer) { + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + } + + if (mode == AlterSchemaMode::Canonicalizer) { + bundle.add(); bundle.add(); bundle.add(); } - if (mode == AlterSchemaMode::Linter) { + if (mode == AlterSchemaMode::Linter || + mode == AlterSchemaMode::Canonicalizer) { bundle.add(); } @@ -170,8 +316,10 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); - bundle.add(); - bundle.add(); + if (mode != AlterSchemaMode::Canonicalizer) { + bundle.add(); + bundle.add(); + } bundle.add(); bundle.add(); bundle.add(); @@ -206,11 +354,11 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); - bundle.add(); - bundle.add(); bundle.add(); bundle.add(); bundle.add(); + bundle.add(); + bundle.add(); bundle.add(); bundle.add(); } @@ -231,6 +379,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + bundle.add(); bundle.add(); bundle.add(); bundle.add(); @@ -246,13 +395,43 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + bundle.add(); + bundle.add(); } - bundle.add(); + if (mode != AlterSchemaMode::Canonicalizer) { + bundle.add(); + } bundle.add(); - bundle.add(); + + if (mode != AlterSchemaMode::Canonicalizer) { + bundle.add(); + } + bundle.add(); bundle.add(); + + if (mode == AlterSchemaMode::Canonicalizer) { + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + } } -} // namespace sourcemeta::core +} // namespace sourcemeta::blaze diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/additional_items_implicit.h similarity index 58% rename from vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h rename to vendor/blaze/src/alterschema/canonicalizer/additional_items_implicit.h index dfdeae0..154a587 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_items_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/additional_items_implicit.h @@ -1,10 +1,9 @@ -class MinItemsImplicit final : public SchemaTransformRule { +class AdditionalItemsImplicit final : public SchemaTransformRule { public: using mutates = std::true_type; using reframe_after_transform = std::true_type; - MinItemsImplicit() - : SchemaTransformRule{"min_items_implicit", - "Every array has a minimum size of zero items"} {}; + AdditionalItemsImplicit() + : SchemaTransformRule{"additional_items_implicit", ""} {}; [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, @@ -14,22 +13,27 @@ class MinItemsImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( - vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_7, - Vocabularies::Known::JSON_Schema_Draft_6, + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_4, - Vocabularies::Known::JSON_Schema_Draft_3, - Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_1}) && + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7}) && schema.is_object() && schema.defines("type") && schema.at("type").is_string() && - schema.at("type").to_string() == "array" && - !schema.defines("minItems")); + schema.at("type").to_string() == "array" && schema.defines("items") && + schema.at("items").is_array() && !schema.defines("additionalItems")); + this->is_draft3_ = + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_3); return true; } auto transform(JSON &schema, const Result &) const -> void override { - schema.assign("minItems", sourcemeta::core::JSON{0}); + schema.assign("additionalItems", this->is_draft3_ + ? sourcemeta::core::JSON::make_object() + : sourcemeta::core::JSON{true}); } + +private: + mutable bool is_draft3_{false}; }; diff --git a/vendor/blaze/src/alterschema/canonicalizer/additional_properties_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/additional_properties_implicit.h new file mode 100644 index 0000000..2b0e56a --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/additional_properties_implicit.h @@ -0,0 +1,45 @@ +class AdditionalPropertiesImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + AdditionalPropertiesImplicit() + : SchemaTransformRule{"additional_properties_implicit", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "object" && + !schema.defines("additionalProperties")); + this->is_pre_draft4_ = + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.assign("additionalProperties", + this->is_pre_draft4_ ? sourcemeta::core::JSON::make_object() + : sourcemeta::core::JSON{true}); + } + +private: + mutable bool is_pre_draft4_{false}; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/comment_drop.h b/vendor/blaze/src/alterschema/canonicalizer/comment_drop.h new file mode 100644 index 0000000..c55700a --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/comment_drop.h @@ -0,0 +1,27 @@ +class CommentDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + CommentDrop() : SchemaTransformRule{"comment_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Core, + Vocabularies::Known::JSON_Schema_2020_12_Core}) && + schema.is_object() && schema.defines("$comment")); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("$comment"); + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h b/vendor/blaze/src/alterschema/canonicalizer/const_as_enum.h similarity index 95% rename from vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h rename to vendor/blaze/src/alterschema/canonicalizer/const_as_enum.h index 138f718..b0cf8ef 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/const_as_enum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/const_as_enum.h @@ -15,7 +15,7 @@ class ConstAsEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_any_of.h b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_any_of.h new file mode 100644 index 0000000..655c738 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_any_of.h @@ -0,0 +1,110 @@ +class DependenciesToAnyOf final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + DependenciesToAnyOf() : SchemaTransformRule{"dependencies_to_any_of", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7}) && + schema.is_object() && schema.defines("dependencies") && + schema.at("dependencies").is_object()); + + ONLY_CONTINUE_IF(std::ranges::any_of( + schema.at("dependencies").as_object(), [](const auto &entry) { + return is_schema(entry.second) || entry.second.is_array(); + })); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto result_branches{JSON::make_array()}; + + std::vector processed; + for (const auto &entry : schema.at("dependencies").as_object()) { + if (is_schema(entry.second)) { + auto not_required{JSON::make_object()}; + not_required.assign("type", JSON{"object"}); + not_required.assign("required", JSON::make_array()); + not_required.at("required").push_back(JSON{entry.first}); + auto not_branch{JSON::make_object()}; + not_branch.assign("not", std::move(not_required)); + + auto required_obj{JSON::make_object()}; + required_obj.assign("type", JSON{"object"}); + required_obj.assign("required", JSON::make_array()); + required_obj.at("required").push_back(JSON{entry.first}); + + auto all_of{JSON::make_array()}; + all_of.push_back(std::move(required_obj)); + all_of.push_back(entry.second); + + auto allof_branch{JSON::make_object()}; + allof_branch.assign("allOf", std::move(all_of)); + + auto pair{JSON::make_array()}; + pair.push_back(std::move(not_branch)); + pair.push_back(std::move(allof_branch)); + + auto wrapper{JSON::make_object()}; + wrapper.assign("anyOf", std::move(pair)); + result_branches.push_back(std::move(wrapper)); + } else if (entry.second.is_array()) { + auto required_all{JSON::make_array()}; + required_all.push_back(JSON{entry.first}); + for (const auto &dependent : entry.second.as_array()) { + required_all.push_back(dependent); + } + + auto not_required{JSON::make_object()}; + not_required.assign("type", JSON{"object"}); + not_required.assign("required", JSON::make_array()); + not_required.at("required").push_back(JSON{entry.first}); + auto not_branch{JSON::make_object()}; + not_branch.assign("not", std::move(not_required)); + + auto required_branch{JSON::make_object()}; + required_branch.assign("type", JSON{"object"}); + required_branch.assign("required", std::move(required_all)); + + auto pair{JSON::make_array()}; + pair.push_back(std::move(not_branch)); + pair.push_back(std::move(required_branch)); + + auto wrapper{JSON::make_object()}; + wrapper.assign("anyOf", std::move(pair)); + result_branches.push_back(std::move(wrapper)); + } else { + continue; + } + + processed.emplace_back(entry.first); + } + + for (const auto &key : processed) { + schema.at("dependencies").erase(key); + } + + if (schema.at("dependencies").empty()) { + schema.erase("dependencies"); + } + + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + for (auto &item : result_branches.as_array()) { + schema.at("allOf").push_back(std::move(item)); + } + } else { + schema.assign("allOf", std::move(result_branches)); + } + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_extends_disallow.h b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_extends_disallow.h new file mode 100644 index 0000000..98e4f92 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_extends_disallow.h @@ -0,0 +1,131 @@ +class DependenciesToExtendsDisallow final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + DependenciesToExtendsDisallow() + : SchemaTransformRule{"dependencies_to_extends_disallow", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_3) && + schema.is_object() && schema.defines("dependencies") && + schema.at("dependencies").is_object()); + + ONLY_CONTINUE_IF(std::ranges::any_of( + schema.at("dependencies").as_object(), [](const auto &entry) { + return is_schema(entry.second) || entry.second.is_array() || + entry.second.is_string(); + })); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto result_branches{JSON::make_array()}; + std::vector processed; + + for (const auto &entry : schema.at("dependencies").as_object()) { + auto required_property{JSON::make_object()}; + required_property.assign("type", JSON{"object"}); + auto required_props{JSON::make_object()}; + auto required_prop_schema{JSON::make_object()}; + required_prop_schema.assign("required", JSON{true}); + required_props.assign(entry.first, std::move(required_prop_schema)); + required_property.assign("properties", std::move(required_props)); + required_property.assign("patternProperties", JSON::make_object()); + required_property.assign("additionalProperties", JSON::make_object()); + + auto not_required{JSON::make_object()}; + auto disallow_array{JSON::make_array()}; + disallow_array.push_back(required_property); + not_required.assign("disallow", std::move(disallow_array)); + + if (is_schema(entry.second)) { + auto extends_array{JSON::make_array()}; + auto required_copy{JSON::make_object()}; + required_copy.assign("type", JSON{"object"}); + auto req_props_copy{JSON::make_object()}; + auto req_prop_schema_copy{JSON::make_object()}; + req_prop_schema_copy.assign("required", JSON{true}); + req_props_copy.assign(entry.first, std::move(req_prop_schema_copy)); + required_copy.assign("properties", std::move(req_props_copy)); + required_copy.assign("patternProperties", JSON::make_object()); + required_copy.assign("additionalProperties", JSON::make_object()); + extends_array.push_back(std::move(required_copy)); + extends_array.push_back(entry.second); + + auto extends_branch{JSON::make_object()}; + extends_branch.assign("extends", std::move(extends_array)); + + auto type_array{JSON::make_array()}; + type_array.push_back(std::move(not_required)); + type_array.push_back(std::move(extends_branch)); + + auto wrapper{JSON::make_object()}; + wrapper.assign("type", std::move(type_array)); + result_branches.push_back(std::move(wrapper)); + } else if (entry.second.is_string() || entry.second.is_array()) { + std::vector dependent_props; + if (entry.second.is_string()) { + dependent_props.push_back(entry.second.to_string()); + } else { + for (const auto &dependent : entry.second.as_array()) { + if (dependent.is_string()) { + dependent_props.push_back(dependent.to_string()); + } + } + } + + auto required_all{JSON::make_object()}; + required_all.assign("type", JSON{"object"}); + auto all_props{JSON::make_object()}; + auto trigger_schema{JSON::make_object()}; + trigger_schema.assign("required", JSON{true}); + all_props.assign(entry.first, std::move(trigger_schema)); + for (const auto &dependent_prop : dependent_props) { + auto dep_schema{JSON::make_object()}; + dep_schema.assign("required", JSON{true}); + all_props.assign(dependent_prop, std::move(dep_schema)); + } + required_all.assign("properties", std::move(all_props)); + required_all.assign("patternProperties", JSON::make_object()); + required_all.assign("additionalProperties", JSON::make_object()); + + auto type_array{JSON::make_array()}; + type_array.push_back(std::move(not_required)); + type_array.push_back(std::move(required_all)); + + auto wrapper{JSON::make_object()}; + wrapper.assign("type", std::move(type_array)); + result_branches.push_back(std::move(wrapper)); + } else { + continue; + } + + processed.emplace_back(entry.first); + } + + for (const auto &key : processed) { + schema.at("dependencies").erase(key); + } + + if (schema.at("dependencies").empty()) { + schema.erase("dependencies"); + } + + if (schema.defines("extends") && schema.at("extends").is_array()) { + for (auto &item : result_branches.as_array()) { + schema.at("extends").push_back(std::move(item)); + } + } else { + schema.assign("extends", std::move(result_branches)); + } + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/dependent_required_to_any_of.h b/vendor/blaze/src/alterschema/canonicalizer/dependent_required_to_any_of.h new file mode 100644 index 0000000..4a71d56 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/dependent_required_to_any_of.h @@ -0,0 +1,84 @@ +class DependentRequiredToAnyOf final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + DependentRequiredToAnyOf() + : SchemaTransformRule{"dependent_required_to_any_of", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_2020_12_Validation}) && + schema.is_object() && schema.defines("dependentRequired") && + schema.at("dependentRequired").is_object() && + !schema.at("dependentRequired").empty()); + + ONLY_CONTINUE_IF(std::ranges::any_of( + schema.at("dependentRequired").as_object(), + [](const auto &entry) { return entry.second.is_array(); })); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto result_branches{JSON::make_array()}; + + std::vector processed; + for (const auto &entry : schema.at("dependentRequired").as_object()) { + if (!entry.second.is_array()) { + continue; + } + + auto required_all{JSON::make_array()}; + required_all.push_back(JSON{entry.first}); + for (const auto &dependent : entry.second.as_array()) { + required_all.push_back(dependent); + } + + auto not_required{JSON::make_object()}; + not_required.assign("type", JSON{"object"}); + not_required.assign("required", JSON::make_array()); + not_required.at("required").push_back(JSON{entry.first}); + auto not_branch{JSON::make_object()}; + not_branch.assign("not", std::move(not_required)); + + auto required_branch{JSON::make_object()}; + required_branch.assign("type", JSON{"object"}); + required_branch.assign("required", std::move(required_all)); + + auto pair{JSON::make_array()}; + pair.push_back(std::move(not_branch)); + pair.push_back(std::move(required_branch)); + + auto wrapper{JSON::make_object()}; + wrapper.assign("anyOf", std::move(pair)); + result_branches.push_back(std::move(wrapper)); + + processed.emplace_back(entry.first); + } + + for (const auto &key : processed) { + schema.at("dependentRequired").erase(key); + } + + if (schema.at("dependentRequired").empty()) { + schema.erase("dependentRequired"); + } + + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + for (auto &item : result_branches.as_array()) { + schema.at("allOf").push_back(std::move(item)); + } + } else { + schema.assign("allOf", std::move(result_branches)); + } + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/dependent_schemas_to_any_of.h b/vendor/blaze/src/alterschema/canonicalizer/dependent_schemas_to_any_of.h new file mode 100644 index 0000000..9dabcda --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/dependent_schemas_to_any_of.h @@ -0,0 +1,69 @@ +class DependentSchemasToAnyOf final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + DependentSchemasToAnyOf() + : SchemaTransformRule{"dependent_schemas_to_any_of", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object() && schema.defines("dependentSchemas") && + schema.at("dependentSchemas").is_object() && + !schema.at("dependentSchemas").empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto result_branches{JSON::make_array()}; + + for (const auto &entry : schema.at("dependentSchemas").as_object()) { + auto not_required{JSON::make_object()}; + not_required.assign("type", JSON{"object"}); + not_required.assign("required", JSON::make_array()); + not_required.at("required").push_back(JSON{entry.first}); + auto not_branch{JSON::make_object()}; + not_branch.assign("not", std::move(not_required)); + + auto required_obj{JSON::make_object()}; + required_obj.assign("type", JSON{"object"}); + required_obj.assign("required", JSON::make_array()); + required_obj.at("required").push_back(JSON{entry.first}); + + auto all_of{JSON::make_array()}; + all_of.push_back(std::move(required_obj)); + all_of.push_back(entry.second); + + auto allof_branch{JSON::make_object()}; + allof_branch.assign("allOf", std::move(all_of)); + + auto pair{JSON::make_array()}; + pair.push_back(std::move(not_branch)); + pair.push_back(std::move(allof_branch)); + + auto wrapper{JSON::make_object()}; + wrapper.assign("anyOf", std::move(pair)); + result_branches.push_back(std::move(wrapper)); + } + + schema.erase("dependentSchemas"); + + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + for (auto &item : result_branches.as_array()) { + schema.at("allOf").push_back(std::move(item)); + } + } else { + schema.assign("allOf", std::move(result_branches)); + } + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/deprecated_false_drop.h b/vendor/blaze/src/alterschema/canonicalizer/deprecated_false_drop.h new file mode 100644 index 0000000..1bee8c0 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/deprecated_false_drop.h @@ -0,0 +1,29 @@ +class DeprecatedFalseDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + DeprecatedFalseDrop() : SchemaTransformRule{"deprecated_false_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, + Vocabularies::Known::JSON_Schema_2020_12_Meta_Data}) && + schema.is_object() && schema.defines("deprecated") && + schema.at("deprecated").is_boolean() && + !schema.at("deprecated").to_boolean()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("deprecated"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/disallow_to_array_of_schemas.h b/vendor/blaze/src/alterschema/canonicalizer/disallow_to_array_of_schemas.h new file mode 100644 index 0000000..14293c7 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/disallow_to_array_of_schemas.h @@ -0,0 +1,106 @@ +class DisallowToArrayOfSchemas final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + DisallowToArrayOfSchemas() + : SchemaTransformRule{"disallow_to_array_of_schemas", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}) && + schema.is_object() && schema.defines("disallow")); + + this->convert_to_schemas_ = + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_3); + + const auto &disallow{schema.at("disallow")}; + if (disallow.is_string()) { + return true; + } + + if (this->convert_to_schemas_) { + ONLY_CONTINUE_IF(disallow.is_array()); + for (const auto &element : disallow.as_array()) { + if (element.is_string()) { + return true; + } + } + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + const auto &disallow{schema.at("disallow")}; + + if (disallow.is_string()) { + auto array{JSON::make_array()}; + if (this->convert_to_schemas_) { + array.push_back(type_string_to_schema(disallow.to_string())); + } else { + array.push_back(disallow); + } + schema.assign("disallow", std::move(array)); + return; + } + + auto new_array{JSON::make_array()}; + for (const auto &element : disallow.as_array()) { + if (element.is_string() && this->convert_to_schemas_) { + new_array.push_back(type_string_to_schema(element.to_string())); + } else { + new_array.push_back(element); + } + } + schema.assign("disallow", std::move(new_array)); + } + + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, + const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + return target.rebase(current.concat({"disallow"}), + current.concat({"disallow", 0})); + } + +private: + static auto type_string_to_schema(const std::string &type_name) -> JSON { + if (type_name == "null") { + auto result{JSON::make_object()}; + auto values{JSON::make_array()}; + values.push_back(JSON{nullptr}); + result.assign("enum", std::move(values)); + return result; + } + + if (type_name == "boolean") { + auto result{JSON::make_object()}; + auto values{JSON::make_array()}; + values.push_back(JSON{false}); + values.push_back(JSON{true}); + result.assign("enum", std::move(values)); + return result; + } + + if (type_name == "any") { + return JSON::make_object(); + } + + auto result{JSON::make_object()}; + result.assign("type", JSON{type_name}); + return result; + } + + mutable bool convert_to_schemas_{true}; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/divisible_by_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/divisible_by_implicit.h new file mode 100644 index 0000000..3d0fa52 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/divisible_by_implicit.h @@ -0,0 +1,29 @@ +class DivisibleByImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + DivisibleByImplicit() : SchemaTransformRule{"divisible_by_implicit", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + !schema.defines("divisibleBy")); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.assign("divisibleBy", sourcemeta::core::JSON{1}); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_definitions_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_definitions_drop.h new file mode 100644 index 0000000..a06b42e --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_definitions_drop.h @@ -0,0 +1,29 @@ +class EmptyDefinitionsDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + EmptyDefinitionsDrop() : SchemaTransformRule{"empty_definitions_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7}) && + schema.is_object() && schema.defines("definitions") && + schema.at("definitions").is_object() && + schema.at("definitions").empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("definitions"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_defs_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_defs_drop.h new file mode 100644 index 0000000..1a3cbcd --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_defs_drop.h @@ -0,0 +1,28 @@ +class EmptyDefsDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + EmptyDefsDrop() : SchemaTransformRule{"empty_defs_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Core, + Vocabularies::Known::JSON_Schema_2020_12_Core}) && + schema.is_object() && schema.defines("$defs") && + schema.at("$defs").is_object() && + schema.at("$defs").empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("$defs"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_dependencies_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_dependencies_drop.h new file mode 100644 index 0000000..e9b838e --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_dependencies_drop.h @@ -0,0 +1,31 @@ +class EmptyDependenciesDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + EmptyDependenciesDrop() + : SchemaTransformRule{"empty_dependencies_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7}) && + schema.is_object() && schema.defines("dependencies") && + schema.at("dependencies").is_object() && + schema.at("dependencies").empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("dependencies"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_required_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_required_drop.h new file mode 100644 index 0000000..3aa03ec --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_required_drop.h @@ -0,0 +1,30 @@ +class EmptyDependentRequiredDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + EmptyDependentRequiredDrop() + : SchemaTransformRule{"empty_dependent_required_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_2020_12_Validation}) && + schema.is_object() && schema.defines("dependentRequired") && + schema.at("dependentRequired").is_object() && + schema.at("dependentRequired").empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("dependentRequired"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_schemas_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_schemas_drop.h new file mode 100644 index 0000000..e6d0b4d --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_schemas_drop.h @@ -0,0 +1,30 @@ +class EmptyDependentSchemasDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + EmptyDependentSchemasDrop() + : SchemaTransformRule{"empty_dependent_schemas_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object() && schema.defines("dependentSchemas") && + schema.at("dependentSchemas").is_object() && + schema.at("dependentSchemas").empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("dependentSchemas"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/enum_drop_redundant_validation.h b/vendor/blaze/src/alterschema/canonicalizer/enum_drop_redundant_validation.h new file mode 100644 index 0000000..42e16ef --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/enum_drop_redundant_validation.h @@ -0,0 +1,134 @@ +class EnumDropRedundantValidation final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + EnumDropRedundantValidation() + : SchemaTransformRule{"enum_drop_redundant_validation", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_2020_12_Validation}) && + schema.is_object() && schema.defines("enum") && + schema.at("enum").is_array() && !schema.defines("type")); + + this->keywords_.clear(); + this->wrap_keywords_.clear(); + this->is_pre_draft4_ = + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}); + this->has_if_group_ = + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.defines("if"); + for (const auto &entry : schema.as_object()) { + if (entry.first == "enum") { + continue; + } + + if (this->has_if_group_ && + (entry.first == "then" || entry.first == "else")) { + continue; + } + + const auto &metadata{walker(entry.first, vocabularies)}; + if (metadata.type == sourcemeta::core::SchemaKeywordType::Unknown || + metadata.type == sourcemeta::core::SchemaKeywordType::Annotation || + metadata.type == sourcemeta::core::SchemaKeywordType::Other || + metadata.type == sourcemeta::core::SchemaKeywordType::Comment || + metadata.type == + sourcemeta::core::SchemaKeywordType::LocationMembers) { + continue; + } + + if (entry.second.is_boolean() && entry.second.to_boolean()) { + if (!frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(entry.first)})) { + this->keywords_.emplace_back(entry.first); + } + continue; + } + + if (entry.second.is_object() && entry.second.empty()) { + this->keywords_.emplace_back(entry.first); + continue; + } + + if (!frame.has_references_through( + location.pointer, WeakPointer::Token{std::cref(entry.first)})) { + this->wrap_keywords_.emplace_back(entry.first); + } + } + + ONLY_CONTINUE_IF(!this->keywords_.empty() || !this->wrap_keywords_.empty()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + for (const auto &keyword : this->keywords_) { + schema.erase(keyword); + } + + if (this->wrap_keywords_.empty()) { + return; + } + + auto new_allof{JSON::make_array()}; + for (const auto &keyword : this->wrap_keywords_) { + auto branch{JSON::make_object()}; + branch.assign(keyword, schema.at(keyword)); + if (keyword == "if" && this->has_if_group_) { + if (schema.defines("then")) { + branch.assign("then", schema.at("then")); + } + if (schema.defines("else")) { + branch.assign("else", schema.at("else")); + } + } + new_allof.push_back(std::move(branch)); + schema.erase(keyword); + if (keyword == "if" && this->has_if_group_) { + if (schema.defines("then")) { + schema.erase("then"); + } + if (schema.defines("else")) { + schema.erase("else"); + } + } + } + + auto enum_branch{JSON::make_object()}; + enum_branch.assign("enum", schema.at("enum")); + schema.erase("enum"); + new_allof.push_back(std::move(enum_branch)); + + const auto wrapper_keyword{this->is_pre_draft4_ ? "extends" : "allOf"}; + schema.assign(wrapper_keyword, std::move(new_allof)); + } + +private: + mutable std::vector keywords_; + mutable std::vector wrap_keywords_; + mutable bool has_if_group_{false}; + mutable bool is_pre_draft4_{false}; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/enum_filter_by_type.h b/vendor/blaze/src/alterschema/canonicalizer/enum_filter_by_type.h new file mode 100644 index 0000000..925bef8 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/enum_filter_by_type.h @@ -0,0 +1,70 @@ +class EnumFilterByType final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + EnumFilterByType() : SchemaTransformRule{"enum_filter_by_type", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_2020_12_Validation}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && schema.defines("enum") && + schema.at("enum").is_array() && !schema.at("enum").empty()); + + const auto declared_types{parse_schema_type(schema.at("type"))}; + const bool integer_matches_integral{ + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_2020_12_Validation}) && + declared_types.test(std::to_underlying(JSON::Type::Integer))}; + + this->matching_indices_.clear(); + bool has_mismatch{false}; + std::size_t index{0}; + for (const auto &value : schema.at("enum").as_array()) { + const bool matches{ + declared_types.test(std::to_underlying(value.type())) || + (integer_matches_integral && value.is_integral())}; + if (matches) { + this->matching_indices_.push_back(index); + } else { + has_mismatch = true; + } + index++; + } + + ONLY_CONTINUE_IF(!this->matching_indices_.empty() && has_mismatch); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto filtered{JSON::make_array()}; + for (const auto &index : this->matching_indices_) { + filtered.push_back(schema.at("enum").at(index)); + } + + schema.assign("enum", std::move(filtered)); + } + +private: + mutable std::vector matching_indices_; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/exclusive_bounds_false_drop.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_bounds_false_drop.h new file mode 100644 index 0000000..1d6c080 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_bounds_false_drop.h @@ -0,0 +1,49 @@ +class ExclusiveBoundsFalseDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ExclusiveBoundsFalseDrop() + : SchemaTransformRule{"exclusive_bounds_false_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "integer" || + schema.at("type").to_string() == "number")); + + this->has_exclusive_min_ = schema.defines("exclusiveMinimum") && + schema.at("exclusiveMinimum").is_boolean() && + !schema.at("exclusiveMinimum").to_boolean(); + this->has_exclusive_max_ = schema.defines("exclusiveMaximum") && + schema.at("exclusiveMaximum").is_boolean() && + !schema.at("exclusiveMaximum").to_boolean(); + + ONLY_CONTINUE_IF(this->has_exclusive_min_ || this->has_exclusive_max_); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + if (this->has_exclusive_min_) { + schema.erase("exclusiveMinimum"); + } + + if (this->has_exclusive_max_) { + schema.erase("exclusiveMaximum"); + } + } + +private: + mutable bool has_exclusive_min_{false}; + mutable bool has_exclusive_max_{false}; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_boolean_integer_fold.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_boolean_integer_fold.h new file mode 100644 index 0000000..af14629 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_boolean_integer_fold.h @@ -0,0 +1,106 @@ +class ExclusiveMaximumBooleanIntegerFold final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ExclusiveMaximumBooleanIntegerFold() + : SchemaTransformRule{"exclusive_maximum_boolean_integer_fold", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("exclusiveMaximum") && + schema.at("exclusiveMaximum").is_boolean() && + schema.at("exclusiveMaximum").to_boolean() && + schema.defines("maximum") && schema.at("maximum").is_number()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + const auto &maximum{schema.at("maximum")}; + if (maximum.is_integer()) { + if (maximum.to_integer() > std::numeric_limits::min()) { + auto new_maximum = maximum; + new_maximum += sourcemeta::core::JSON{-1}; + schema.assign("maximum", std::move(new_maximum)); + } else { + auto result{ + sourcemeta::core::Decimal{std::to_string(maximum.to_integer())}}; + result -= sourcemeta::core::Decimal{1}; + schema.assign("maximum", sourcemeta::core::JSON{std::move(result)}); + } + } else if (maximum.is_decimal()) { + auto current{maximum.to_decimal()}; + auto floored{current.to_integral()}; + if (floored > current) { + floored -= sourcemeta::core::Decimal{1}; + } + + if (floored == current) { + floored -= sourcemeta::core::Decimal{1}; + } + + if (floored.is_int64()) { + schema.assign("maximum", sourcemeta::core::JSON{floored.to_int64()}); + } else { + schema.assign("maximum", sourcemeta::core::JSON{std::move(floored)}); + } + } else { + const auto value{maximum.to_real()}; + const auto floor_value{std::floor(value)}; + if (floor_value == value) { + if (value >= + static_cast(std::numeric_limits::min()) && + value < + static_cast(std::numeric_limits::max()) + + 1.0) { + auto decimal_result{sourcemeta::core::Decimal{ + std::to_string(static_cast(value))}}; + decimal_result -= sourcemeta::core::Decimal{1}; + if (decimal_result.is_int64()) { + schema.assign("maximum", + sourcemeta::core::JSON{decimal_result.to_int64()}); + } else { + schema.assign("maximum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } else { + auto decimal_fallback{sourcemeta::core::Decimal{value}}; + decimal_fallback -= sourcemeta::core::Decimal{1}; + schema.assign("maximum", + sourcemeta::core::JSON{std::move(decimal_fallback)}); + } + } else if (std::isfinite(floor_value) && + floor_value >= static_cast( + std::numeric_limits::min()) && + floor_value < static_cast( + std::numeric_limits::max()) + + 1.0) { + schema.assign("maximum", sourcemeta::core::JSON{ + static_cast(floor_value)}); + } else { + auto decimal_result{sourcemeta::core::Decimal{value}}; + decimal_result = decimal_result.to_integral(); + if (decimal_result > sourcemeta::core::Decimal{value}) { + decimal_result -= sourcemeta::core::Decimal{1}; + } + + schema.assign("maximum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } + + schema.erase("exclusiveMaximum"); + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h similarity index 56% rename from vendor/core/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h rename to vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h index 1624687..95d2c8e 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h @@ -16,7 +16,7 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -33,9 +33,19 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { auto transform(JSON &schema, const Result &) const -> void override { if (schema.at("exclusiveMaximum").is_integer()) { - auto new_maximum = schema.at("exclusiveMaximum"); - new_maximum += sourcemeta::core::JSON{-1}; - schema.at("exclusiveMaximum").into(new_maximum); + if (schema.at("exclusiveMaximum").to_integer() > + std::numeric_limits::min()) { + auto new_maximum = schema.at("exclusiveMaximum"); + new_maximum += sourcemeta::core::JSON{-1}; + schema.at("exclusiveMaximum").into(new_maximum); + } else { + auto result{sourcemeta::core::Decimal{ + std::to_string(schema.at("exclusiveMaximum").to_integer())}}; + result -= sourcemeta::core::Decimal{1}; + schema.at("exclusiveMaximum") + .into(sourcemeta::core::JSON{std::move(result)}); + } + schema.rename("exclusiveMaximum", "maximum"); } else if (schema.at("exclusiveMaximum").is_decimal()) { const auto current{schema.at("exclusiveMaximum").to_decimal()}; @@ -44,7 +54,7 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { new_value -= sourcemeta::core::Decimal{1}; } - if (current.is_integer()) { + if (new_value == current) { new_value -= sourcemeta::core::Decimal{1}; } @@ -58,8 +68,37 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { schema.rename("exclusiveMaximum", "maximum"); } else { const auto current{schema.at("exclusiveMaximum").to_real()}; - const auto new_value{static_cast(std::floor(current))}; - schema.at("exclusiveMaximum").into(sourcemeta::core::JSON{new_value}); + const auto floor_value{std::floor(current)}; + if (floor_value == current) { + auto result{sourcemeta::core::Decimal{current}}; + result -= sourcemeta::core::Decimal{1}; + if (result.is_int64()) { + schema.at("exclusiveMaximum") + .into(sourcemeta::core::JSON{result.to_int64()}); + } else { + schema.at("exclusiveMaximum") + .into(sourcemeta::core::JSON{std::move(result)}); + } + } else if (std::isfinite(floor_value) && + floor_value >= static_cast( + std::numeric_limits::min()) && + floor_value < static_cast( + std::numeric_limits::max()) + + 1.0) { + schema.at("exclusiveMaximum") + .into( + sourcemeta::core::JSON{static_cast(floor_value)}); + } else { + auto result{sourcemeta::core::Decimal{current}}; + result = result.to_integral(); + if (result > sourcemeta::core::Decimal{current}) { + result -= sourcemeta::core::Decimal{1}; + } + + schema.at("exclusiveMaximum") + .into(sourcemeta::core::JSON{std::move(result)}); + } + schema.rename("exclusiveMaximum", "maximum"); } } diff --git a/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_boolean_integer_fold.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_boolean_integer_fold.h new file mode 100644 index 0000000..ceb0d6f --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_boolean_integer_fold.h @@ -0,0 +1,106 @@ +class ExclusiveMinimumBooleanIntegerFold final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ExclusiveMinimumBooleanIntegerFold() + : SchemaTransformRule{"exclusive_minimum_boolean_integer_fold", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("exclusiveMinimum") && + schema.at("exclusiveMinimum").is_boolean() && + schema.at("exclusiveMinimum").to_boolean() && + schema.defines("minimum") && schema.at("minimum").is_number()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + const auto &minimum{schema.at("minimum")}; + if (minimum.is_integer()) { + if (minimum.to_integer() < std::numeric_limits::max()) { + auto new_minimum = minimum; + new_minimum += sourcemeta::core::JSON{1}; + schema.assign("minimum", std::move(new_minimum)); + } else { + auto result{ + sourcemeta::core::Decimal{std::to_string(minimum.to_integer())}}; + result += sourcemeta::core::Decimal{1}; + schema.assign("minimum", sourcemeta::core::JSON{std::move(result)}); + } + } else if (minimum.is_decimal()) { + auto current{minimum.to_decimal()}; + auto ceiled{current.to_integral()}; + if (ceiled < current) { + ceiled += sourcemeta::core::Decimal{1}; + } + + if (ceiled == current) { + ceiled += sourcemeta::core::Decimal{1}; + } + + if (ceiled.is_int64()) { + schema.assign("minimum", sourcemeta::core::JSON{ceiled.to_int64()}); + } else { + schema.assign("minimum", sourcemeta::core::JSON{std::move(ceiled)}); + } + } else { + const auto value{minimum.to_real()}; + const auto ceil_value{std::ceil(value)}; + if (ceil_value == value) { + if (value >= + static_cast(std::numeric_limits::min()) && + value < + static_cast(std::numeric_limits::max()) + + 1.0) { + auto decimal_result{sourcemeta::core::Decimal{ + std::to_string(static_cast(value))}}; + decimal_result += sourcemeta::core::Decimal{1}; + if (decimal_result.is_int64()) { + schema.assign("minimum", + sourcemeta::core::JSON{decimal_result.to_int64()}); + } else { + schema.assign("minimum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } else { + auto decimal_fallback{sourcemeta::core::Decimal{value}}; + decimal_fallback += sourcemeta::core::Decimal{1}; + schema.assign("minimum", + sourcemeta::core::JSON{std::move(decimal_fallback)}); + } + } else if (std::isfinite(ceil_value) && + ceil_value >= static_cast( + std::numeric_limits::min()) && + ceil_value < static_cast( + std::numeric_limits::max()) + + 1.0) { + schema.assign("minimum", sourcemeta::core::JSON{ + static_cast(ceil_value)}); + } else { + auto decimal_result{sourcemeta::core::Decimal{value}}; + decimal_result = decimal_result.to_integral(); + if (decimal_result < sourcemeta::core::Decimal{value}) { + decimal_result += sourcemeta::core::Decimal{1}; + } + + schema.assign("minimum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } + + schema.erase("exclusiveMinimum"); + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h similarity index 56% rename from vendor/core/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h rename to vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h index 87479ab..a86232c 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h @@ -16,7 +16,7 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -33,9 +33,19 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { auto transform(JSON &schema, const Result &) const -> void override { if (schema.at("exclusiveMinimum").is_integer()) { - auto new_minimum = schema.at("exclusiveMinimum"); - new_minimum += sourcemeta::core::JSON{1}; - schema.at("exclusiveMinimum").into(new_minimum); + if (schema.at("exclusiveMinimum").to_integer() < + std::numeric_limits::max()) { + auto new_minimum = schema.at("exclusiveMinimum"); + new_minimum += sourcemeta::core::JSON{1}; + schema.at("exclusiveMinimum").into(new_minimum); + } else { + auto result{sourcemeta::core::Decimal{ + std::to_string(schema.at("exclusiveMinimum").to_integer())}}; + result += sourcemeta::core::Decimal{1}; + schema.at("exclusiveMinimum") + .into(sourcemeta::core::JSON{std::move(result)}); + } + schema.rename("exclusiveMinimum", "minimum"); } else if (schema.at("exclusiveMinimum").is_decimal()) { const auto current{schema.at("exclusiveMinimum").to_decimal()}; @@ -44,7 +54,7 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { new_value += sourcemeta::core::Decimal{1}; } - if (current.is_integer()) { + if (new_value == current) { new_value += sourcemeta::core::Decimal{1}; } @@ -58,8 +68,37 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { schema.rename("exclusiveMinimum", "minimum"); } else { const auto current{schema.at("exclusiveMinimum").to_real()}; - const auto new_value{static_cast(std::ceil(current))}; - schema.at("exclusiveMinimum").into(sourcemeta::core::JSON{new_value}); + const auto ceil_value{std::ceil(current)}; + if (ceil_value == current) { + auto result{sourcemeta::core::Decimal{current}}; + result += sourcemeta::core::Decimal{1}; + if (result.is_int64()) { + schema.at("exclusiveMinimum") + .into(sourcemeta::core::JSON{result.to_int64()}); + } else { + schema.at("exclusiveMinimum") + .into(sourcemeta::core::JSON{std::move(result)}); + } + } else if (std::isfinite(ceil_value) && + ceil_value >= static_cast( + std::numeric_limits::min()) && + ceil_value < static_cast( + std::numeric_limits::max()) + + 1.0) { + schema.at("exclusiveMinimum") + .into( + sourcemeta::core::JSON{static_cast(ceil_value)}); + } else { + auto result{sourcemeta::core::Decimal{current}}; + result = result.to_integral(); + if (result < sourcemeta::core::Decimal{current}) { + result += sourcemeta::core::Decimal{1}; + } + + schema.at("exclusiveMinimum") + .into(sourcemeta::core::JSON{std::move(result)}); + } + schema.rename("exclusiveMinimum", "minimum"); } } diff --git a/vendor/blaze/src/alterschema/canonicalizer/extends_to_array.h b/vendor/blaze/src/alterschema/canonicalizer/extends_to_array.h new file mode 100644 index 0000000..976d221 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/extends_to_array.h @@ -0,0 +1,39 @@ +class ExtendsToArray final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ExtendsToArray() : SchemaTransformRule{"extends_to_array", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}) && + schema.is_object() && schema.defines("extends") && + !schema.at("extends").is_array()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto array{sourcemeta::core::JSON::make_array()}; + array.push_back(schema.at("extends")); + schema.assign("extends", std::move(array)); + } + + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, + const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + return target.rebase(current.concat({"extends"}), + current.concat({"extends", 0})); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/if_then_else_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/if_then_else_implicit.h new file mode 100644 index 0000000..0184861 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/if_then_else_implicit.h @@ -0,0 +1,35 @@ +class IfThenElseImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + IfThenElseImplicit() : SchemaTransformRule{"if_then_else_implicit", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object() && schema.defines("if") && + (schema.defines("then") || schema.defines("else")) && + (!schema.defines("then") || !schema.defines("else"))); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + if (!schema.defines("then")) { + schema.assign("then", JSON{true}); + } + if (!schema.defines("else")) { + schema.assign("else", JSON{true}); + } + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/implicit_array_keywords.h b/vendor/blaze/src/alterschema/canonicalizer/implicit_array_keywords.h new file mode 100644 index 0000000..b609674 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/implicit_array_keywords.h @@ -0,0 +1,70 @@ +class ImplicitArrayKeywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ImplicitArrayKeywords() + : SchemaTransformRule{"implicit_array_keywords", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "array"); + + const bool is_modern{vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator})}; + this->is_pre_draft4_ = + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}); + this->has_unique_items_ = + !vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1}); + const bool needs_items{!is_modern && !schema.defines("items")}; + ONLY_CONTINUE_IF( + (this->has_unique_items_ && !schema.defines("uniqueItems")) || + needs_items || !schema.defines("minItems")); + + this->add_items_ = needs_items; + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + if (this->has_unique_items_ && !schema.defines("uniqueItems")) { + schema.assign("uniqueItems", sourcemeta::core::JSON{false}); + } + if (this->add_items_ && !schema.defines("items")) { + schema.assign("items", this->is_pre_draft4_ + ? sourcemeta::core::JSON::make_object() + : sourcemeta::core::JSON{true}); + } + if (!schema.defines("minItems")) { + schema.assign("minItems", sourcemeta::core::JSON{0}); + } + } + +private: + mutable bool add_items_{true}; + mutable bool is_pre_draft4_{false}; + mutable bool has_unique_items_{true}; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/implicit_contains_keywords.h b/vendor/blaze/src/alterschema/canonicalizer/implicit_contains_keywords.h new file mode 100644 index 0000000..39a9cb9 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/implicit_contains_keywords.h @@ -0,0 +1,53 @@ +class ImplicitContainsKeywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ImplicitContainsKeywords() + : SchemaTransformRule{"implicit_contains_keywords", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &root, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "array"); + + if (schema.defines("contains")) { + ONLY_CONTINUE_IF(!schema.defines("minContains")); + } else { + ONLY_CONTINUE_IF(!schema.defines("minContains") && + !schema.defines("maxContains")); + ONLY_CONTINUE_IF(!WALK_UP_IN_PLACE_APPLICATORS( + root, frame, location, walker, resolver, + [](const JSON &ancestor, + const Vocabularies &ancestor_vocabularies) { + return ancestor.defines("unevaluatedItems") && + ancestor_vocabularies.contains( + Vocabularies::Known:: + JSON_Schema_2020_12_Unevaluated); + }) + .has_value()); + } + + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + if (!schema.defines("contains")) { + schema.assign("contains", sourcemeta::core::JSON{true}); + schema.assign("minContains", sourcemeta::core::JSON{0}); + } else { + schema.assign("minContains", sourcemeta::core::JSON{1}); + } + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/implicit_object_keywords.h b/vendor/blaze/src/alterschema/canonicalizer/implicit_object_keywords.h new file mode 100644 index 0000000..337edf3 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/implicit_object_keywords.h @@ -0,0 +1,35 @@ +class ImplicitObjectKeywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ImplicitObjectKeywords() + : SchemaTransformRule{"implicit_object_keywords", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "object" && + !schema.defines("patternProperties")); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.assign("patternProperties", sourcemeta::core::JSON::make_object()); + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/items_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/items_implicit.h similarity index 54% rename from vendor/core/src/extension/alterschema/canonicalizer/items_implicit.h rename to vendor/blaze/src/alterschema/canonicalizer/items_implicit.h index 50a361e..a3068a3 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/items_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/items_implicit.h @@ -9,13 +9,13 @@ class ItemsImplicit final : public SchemaTransformRule { [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, - const sourcemeta::core::JSON &, + const sourcemeta::core::JSON &root, const sourcemeta::core::Vocabularies &vocabularies, - const sourcemeta::core::SchemaFrame &, - const sourcemeta::core::SchemaFrame::Location &, - const sourcemeta::core::SchemaWalker &, - const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( ((vocabularies.contains( Vocabularies::Known::JSON_Schema_2020_12_Validation) && @@ -31,6 +31,23 @@ class ItemsImplicit final : public SchemaTransformRule { schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "array" && !schema.defines("items")); + ONLY_CONTINUE_IF( + !(schema.defines("unevaluatedItems") && + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}))); + ONLY_CONTINUE_IF( + !WALK_UP_IN_PLACE_APPLICATORS( + root, frame, location, walker, resolver, + [](const JSON &ancestor, + const Vocabularies &ancestor_vocabularies) { + return ancestor.defines("unevaluatedItems") && + ancestor_vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known:: + JSON_Schema_2019_09_Applicator}); + }) + .has_value()); return true; } diff --git a/vendor/core/src/extension/alterschema/canonicalizer/max_contains_covered_by_max_items.h b/vendor/blaze/src/alterschema/canonicalizer/max_contains_covered_by_max_items.h similarity index 95% rename from vendor/core/src/extension/alterschema/canonicalizer/max_contains_covered_by_max_items.h rename to vendor/blaze/src/alterschema/canonicalizer/max_contains_covered_by_max_items.h index 225a289..c7da520 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/max_contains_covered_by_max_items.h +++ b/vendor/blaze/src/alterschema/canonicalizer/max_contains_covered_by_max_items.h @@ -17,7 +17,7 @@ class MaxContainsCoveredByMaxItems final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/blaze/src/alterschema/canonicalizer/max_decimal_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/max_decimal_implicit.h new file mode 100644 index 0000000..05375e3 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/max_decimal_implicit.h @@ -0,0 +1,29 @@ +class MaxDecimalImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + MaxDecimalImplicit() : SchemaTransformRule{"max_decimal_implicit", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + !schema.defines("maxDecimal")); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.assign("maxDecimal", sourcemeta::core::JSON{0}); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_integer_fold.h b/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_integer_fold.h new file mode 100644 index 0000000..0c2826b --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_integer_fold.h @@ -0,0 +1,104 @@ +class MaximumCanEqualIntegerFold final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + MaximumCanEqualIntegerFold() + : SchemaTransformRule{"maximum_can_equal_integer_fold", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("maximumCanEqual") && + schema.at("maximumCanEqual").is_boolean() && + !schema.at("maximumCanEqual").to_boolean() && + schema.defines("maximum") && schema.at("maximum").is_number()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + const auto &maximum{schema.at("maximum")}; + if (maximum.is_integer()) { + if (maximum.to_integer() > std::numeric_limits::min()) { + auto new_maximum = maximum; + new_maximum += sourcemeta::core::JSON{-1}; + schema.assign("maximum", std::move(new_maximum)); + } else { + auto result{ + sourcemeta::core::Decimal{std::to_string(maximum.to_integer())}}; + result -= sourcemeta::core::Decimal{1}; + schema.assign("maximum", sourcemeta::core::JSON{std::move(result)}); + } + } else if (maximum.is_decimal()) { + auto current{maximum.to_decimal()}; + auto floored{current.to_integral()}; + if (floored > current) { + floored -= sourcemeta::core::Decimal{1}; + } + if (floored == current) { + floored -= sourcemeta::core::Decimal{1}; + } + if (floored.is_int64()) { + schema.assign("maximum", sourcemeta::core::JSON{floored.to_int64()}); + } else { + schema.assign("maximum", sourcemeta::core::JSON{std::move(floored)}); + } + } else { + const auto value{maximum.to_real()}; + const auto floor_value{std::floor(value)}; + if (floor_value == value) { + if (value >= + static_cast(std::numeric_limits::min()) && + value < + static_cast(std::numeric_limits::max()) + + 1.0) { + auto decimal_result{sourcemeta::core::Decimal{ + std::to_string(static_cast(value))}}; + decimal_result -= sourcemeta::core::Decimal{1}; + if (decimal_result.is_int64()) { + schema.assign("maximum", + sourcemeta::core::JSON{decimal_result.to_int64()}); + } else { + schema.assign("maximum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } else { + auto decimal_fallback{sourcemeta::core::Decimal{value}}; + decimal_fallback -= sourcemeta::core::Decimal{1}; + schema.assign("maximum", + sourcemeta::core::JSON{std::move(decimal_fallback)}); + } + } else if (std::isfinite(floor_value) && + floor_value >= static_cast( + std::numeric_limits::min()) && + floor_value < static_cast( + std::numeric_limits::max()) + + 1.0) { + schema.assign("maximum", sourcemeta::core::JSON{ + static_cast(floor_value)}); + } else { + auto decimal_result{sourcemeta::core::Decimal{value}}; + decimal_result = decimal_result.to_integral(); + if (decimal_result > sourcemeta::core::Decimal{value}) { + decimal_result -= sourcemeta::core::Decimal{1}; + } + schema.assign("maximum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } + + schema.erase("maximumCanEqual"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_true_drop.h b/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_true_drop.h new file mode 100644 index 0000000..3104fcb --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_true_drop.h @@ -0,0 +1,34 @@ +class MaximumCanEqualTrueDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + MaximumCanEqualTrueDrop() + : SchemaTransformRule{"maximum_can_equal_true_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "integer" || + schema.at("type").to_string() == "number") && + schema.defines("maximumCanEqual") && + schema.at("maximumCanEqual").is_boolean() && + schema.at("maximumCanEqual").to_boolean()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("maximumCanEqual"); + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_items_given_min_contains.h b/vendor/blaze/src/alterschema/canonicalizer/min_items_given_min_contains.h similarity index 96% rename from vendor/core/src/extension/alterschema/canonicalizer/min_items_given_min_contains.h rename to vendor/blaze/src/alterschema/canonicalizer/min_items_given_min_contains.h index 56a2335..73d2c34 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_items_given_min_contains.h +++ b/vendor/blaze/src/alterschema/canonicalizer/min_items_given_min_contains.h @@ -16,7 +16,7 @@ class MinItemsGivenMinContains final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/min_length_implicit.h similarity index 93% rename from vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h rename to vendor/blaze/src/alterschema/canonicalizer/min_length_implicit.h index 7414758..8f16f8f 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_length_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/min_length_implicit.h @@ -15,7 +15,7 @@ class MinLengthImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -24,7 +24,8 @@ class MinLengthImplicit final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_1}) && + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_0}) && schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "string" && diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_covered_by_required.h b/vendor/blaze/src/alterschema/canonicalizer/min_properties_covered_by_required.h similarity index 96% rename from vendor/core/src/extension/alterschema/canonicalizer/min_properties_covered_by_required.h rename to vendor/blaze/src/alterschema/canonicalizer/min_properties_covered_by_required.h index 60a3191..2f1cb30 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_covered_by_required.h +++ b/vendor/blaze/src/alterschema/canonicalizer/min_properties_covered_by_required.h @@ -16,7 +16,7 @@ class MinPropertiesCoveredByRequired final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/min_properties_implicit.h similarity index 96% rename from vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h rename to vendor/blaze/src/alterschema/canonicalizer/min_properties_implicit.h index 9a4dca7..4fbdd05 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/min_properties_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/min_properties_implicit.h @@ -16,7 +16,7 @@ class MinPropertiesImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_integer_fold.h b/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_integer_fold.h new file mode 100644 index 0000000..3ea2bab --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_integer_fold.h @@ -0,0 +1,104 @@ +class MinimumCanEqualIntegerFold final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + MinimumCanEqualIntegerFold() + : SchemaTransformRule{"minimum_can_equal_integer_fold", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "integer" && + schema.defines("minimumCanEqual") && + schema.at("minimumCanEqual").is_boolean() && + !schema.at("minimumCanEqual").to_boolean() && + schema.defines("minimum") && schema.at("minimum").is_number()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + const auto &minimum{schema.at("minimum")}; + if (minimum.is_integer()) { + if (minimum.to_integer() < std::numeric_limits::max()) { + auto new_minimum = minimum; + new_minimum += sourcemeta::core::JSON{1}; + schema.assign("minimum", std::move(new_minimum)); + } else { + auto result{ + sourcemeta::core::Decimal{std::to_string(minimum.to_integer())}}; + result += sourcemeta::core::Decimal{1}; + schema.assign("minimum", sourcemeta::core::JSON{std::move(result)}); + } + } else if (minimum.is_decimal()) { + auto current{minimum.to_decimal()}; + auto ceiled{current.to_integral()}; + if (ceiled < current) { + ceiled += sourcemeta::core::Decimal{1}; + } + if (ceiled == current) { + ceiled += sourcemeta::core::Decimal{1}; + } + if (ceiled.is_int64()) { + schema.assign("minimum", sourcemeta::core::JSON{ceiled.to_int64()}); + } else { + schema.assign("minimum", sourcemeta::core::JSON{std::move(ceiled)}); + } + } else { + const auto value{minimum.to_real()}; + const auto ceil_value{std::ceil(value)}; + if (ceil_value == value) { + if (value >= + static_cast(std::numeric_limits::min()) && + value < + static_cast(std::numeric_limits::max()) + + 1.0) { + auto decimal_result{sourcemeta::core::Decimal{ + std::to_string(static_cast(value))}}; + decimal_result += sourcemeta::core::Decimal{1}; + if (decimal_result.is_int64()) { + schema.assign("minimum", + sourcemeta::core::JSON{decimal_result.to_int64()}); + } else { + schema.assign("minimum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } else { + auto decimal_fallback{sourcemeta::core::Decimal{value}}; + decimal_fallback += sourcemeta::core::Decimal{1}; + schema.assign("minimum", + sourcemeta::core::JSON{std::move(decimal_fallback)}); + } + } else if (std::isfinite(ceil_value) && + ceil_value >= static_cast( + std::numeric_limits::min()) && + ceil_value < static_cast( + std::numeric_limits::max()) + + 1.0) { + schema.assign("minimum", sourcemeta::core::JSON{ + static_cast(ceil_value)}); + } else { + auto decimal_result{sourcemeta::core::Decimal{value}}; + decimal_result = decimal_result.to_integral(); + if (decimal_result < sourcemeta::core::Decimal{value}) { + decimal_result += sourcemeta::core::Decimal{1}; + } + schema.assign("minimum", + sourcemeta::core::JSON{std::move(decimal_result)}); + } + } + + schema.erase("minimumCanEqual"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_true_drop.h b/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_true_drop.h new file mode 100644 index 0000000..32c775a --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_true_drop.h @@ -0,0 +1,34 @@ +class MinimumCanEqualTrueDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + MinimumCanEqualTrueDrop() + : SchemaTransformRule{"minimum_can_equal_true_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "integer" || + schema.at("type").to_string() == "number") && + schema.defines("minimumCanEqual") && + schema.at("minimumCanEqual").is_boolean() && + schema.at("minimumCanEqual").to_boolean()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("minimumCanEqual"); + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/multiple_of_implicit.h similarity index 96% rename from vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h rename to vendor/blaze/src/alterschema/canonicalizer/multiple_of_implicit.h index b04a47f..330578b 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/multiple_of_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/multiple_of_implicit.h @@ -14,7 +14,7 @@ class MultipleOfImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/blaze/src/alterschema/canonicalizer/optional_property_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/optional_property_implicit.h new file mode 100644 index 0000000..c380741 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/optional_property_implicit.h @@ -0,0 +1,50 @@ +class OptionalPropertyImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + OptionalPropertyImplicit() + : SchemaTransformRule{"optional_property_implicit", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "object" && + schema.defines("properties") && schema.at("properties").is_object()); + + for (const auto &entry : schema.at("properties").as_object()) { + if (entry.second.is_object() && !entry.second.empty() && + !entry.second.defines("optional")) { + return true; + } + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + std::vector keys; + for (const auto &entry : schema.at("properties").as_object()) { + if (entry.second.is_object() && !entry.second.empty() && + !entry.second.defines("optional")) { + keys.emplace_back(entry.first); + } + } + for (const auto &key : keys) { + schema.at("properties") + .at(key) + .assign("optional", sourcemeta::core::JSON{false}); + } + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/properties_implicit.h similarity index 91% rename from vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h rename to vendor/blaze/src/alterschema/canonicalizer/properties_implicit.h index 780f095..9d2f69b 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/properties_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/properties_implicit.h @@ -15,7 +15,7 @@ class PropertiesImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( ((vocabularies.contains( Vocabularies::Known::JSON_Schema_2020_12_Validation) && @@ -31,7 +31,8 @@ class PropertiesImplicit final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_1})) && + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_0})) && schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "object" && diff --git a/vendor/blaze/src/alterschema/canonicalizer/property_names_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/property_names_implicit.h new file mode 100644 index 0000000..b3f83e7 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/property_names_implicit.h @@ -0,0 +1,33 @@ +class PropertyNamesImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + PropertyNamesImplicit() + : SchemaTransformRule{"property_names_implicit", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "object" && + !schema.defines("propertyNames")); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.assign("propertyNames", sourcemeta::core::JSON{true}); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/recursive_anchor_false_drop.h b/vendor/blaze/src/alterschema/canonicalizer/recursive_anchor_false_drop.h new file mode 100644 index 0000000..a973fcf --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/recursive_anchor_false_drop.h @@ -0,0 +1,28 @@ +class RecursiveAnchorFalseDrop final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + RecursiveAnchorFalseDrop() + : SchemaTransformRule{"recursive_anchor_false_drop", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_2019_09_Core) && + schema.is_object() && schema.defines("$recursiveAnchor") && + schema.at("$recursiveAnchor").is_boolean() && + !schema.at("$recursiveAnchor").to_boolean()); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("$recursiveAnchor"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/required_property_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/required_property_implicit.h new file mode 100644 index 0000000..862912d --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/required_property_implicit.h @@ -0,0 +1,48 @@ +class RequiredPropertyImplicit final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + RequiredPropertyImplicit() + : SchemaTransformRule{"required_property_implicit", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_3) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + schema.at("type").to_string() == "object" && + schema.defines("properties") && schema.at("properties").is_object()); + + for (const auto &entry : schema.at("properties").as_object()) { + if (entry.second.is_object() && !entry.second.empty() && + !entry.second.defines("$ref") && !entry.second.defines("required")) { + return true; + } + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + std::vector keys; + for (const auto &entry : schema.at("properties").as_object()) { + if (entry.second.is_object() && !entry.second.empty() && + !entry.second.defines("$ref") && !entry.second.defines("required")) { + keys.emplace_back(entry.first); + } + } + for (const auto &key : keys) { + schema.at("properties") + .at(key) + .assign("required", sourcemeta::core::JSON{false}); + } + } +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of.h b/vendor/blaze/src/alterschema/canonicalizer/type_array_to_any_of.h similarity index 63% rename from vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of.h rename to vendor/blaze/src/alterschema/canonicalizer/type_array_to_any_of.h index 1e9c74b..6155618 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_array_to_any_of.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_array_to_any_of.h @@ -16,7 +16,7 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, @@ -37,7 +37,12 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { } const auto &metadata{walker(entry.first, vocabularies)}; - if (metadata.instances.any()) { + if (metadata.instances.any() && + !(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}) && + (entry.first == "unevaluatedProperties" || + entry.first == "unevaluatedItems"))) { this->keyword_instances_[entry.first] = metadata.instances; } } @@ -46,7 +51,9 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { } auto transform(JSON &schema, const Result &) const -> void override { + this->keyword_branch_index_.clear(); auto disjunctors{sourcemeta::core::JSON::make_array()}; + std::size_t branch_index{0}; for (const auto &type : schema.at("type").as_array()) { auto branch{sourcemeta::core::JSON::make_object()}; branch.assign("type", type); @@ -54,16 +61,22 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { for (const auto &[keyword, instances] : this->keyword_instances_) { if ((instances & current_type_set).any()) { branch.assign(keyword, schema.at(keyword)); + if (!this->keyword_branch_index_.contains(keyword)) { + this->keyword_branch_index_[keyword] = branch_index; + } } } disjunctors.push_back(std::move(branch)); + branch_index++; } for (const auto &[keyword, instances] : this->keyword_instances_) { schema.erase(keyword); } + static const std::string anyof_keyword{"anyOf"}; + static const std::string allof_keyword{"allOf"}; if (schema.defines("anyOf")) { auto first_branch{sourcemeta::core::JSON::make_object()}; first_branch.assign("anyOf", schema.at("anyOf")); @@ -72,23 +85,48 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { schema.erase("anyOf"); if (schema.defines("allOf")) { + const auto allof_index{schema.at("allOf").size() + 1}; schema.at("allOf").push_back(std::move(first_branch)); schema.at("allOf").push_back(std::move(second_branch)); schema.erase("type"); + this->disjunctors_prefix_ = {allof_keyword, allof_index, anyof_keyword}; } else { auto allof_wrapper{sourcemeta::core::JSON::make_array()}; allof_wrapper.push_back(std::move(first_branch)); allof_wrapper.push_back(std::move(second_branch)); schema.at("type").into(std::move(allof_wrapper)); schema.rename("type", "allOf"); + this->disjunctors_prefix_ = {allof_keyword, 1, anyof_keyword}; } } else { schema.at("type").into(std::move(disjunctors)); schema.rename("type", "anyOf"); + this->disjunctors_prefix_ = {anyof_keyword}; } } + [[nodiscard]] auto rereference(const std::string_view reference, + const Pointer &origin, const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + const auto relative{target.resolve_from(current)}; + assert(!relative.empty() && relative.at(0).is_property()); + const auto &keyword{relative.at(0).to_property()}; + const auto match{this->keyword_branch_index_.find(keyword)}; + if (match == this->keyword_branch_index_.end()) { + return SchemaTransformRule::rereference(reference, origin, target, + current); + } + + const Pointer old_prefix{current.concat({keyword})}; + const Pointer new_prefix{current.concat(this->disjunctors_prefix_) + .concat({match->second, keyword})}; + return target.rebase(old_prefix, new_prefix); + } + private: mutable std::unordered_map keyword_instances_; + mutable std::unordered_map keyword_branch_index_; + mutable Pointer disjunctors_prefix_; }; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_boolean_as_enum.h b/vendor/blaze/src/alterschema/canonicalizer/type_boolean_as_enum.h similarity index 94% rename from vendor/core/src/extension/alterschema/canonicalizer/type_boolean_as_enum.h rename to vendor/blaze/src/alterschema/canonicalizer/type_boolean_as_enum.h index bf689d9..f1c21b0 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_boolean_as_enum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_boolean_as_enum.h @@ -16,7 +16,7 @@ class TypeBooleanAsEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -25,7 +25,8 @@ class TypeBooleanAsEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_1}) && + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_0}) && schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "boolean" && diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_inherit_in_place.h b/vendor/blaze/src/alterschema/canonicalizer/type_inherit_in_place.h new file mode 100644 index 0000000..d4dcd19 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/type_inherit_in_place.h @@ -0,0 +1,74 @@ +class TypeInheritInPlace final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + TypeInheritInPlace() + : SchemaTransformRule{ + "type_inherit_in_place", + "An untyped schema inside an in-place applicator inherits " + "the type from its nearest typed ancestor"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &root, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const + -> SchemaTransformRule::Result override { + using namespace sourcemeta::core; + ONLY_CONTINUE_IF(schema.is_object()); + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Validation, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_0})); + ONLY_CONTINUE_IF(!schema.defines("type")); + ONLY_CONTINUE_IF(!schema.defines("enum")); + ONLY_CONTINUE_IF(!vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Validation, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) || + !schema.defines("const")); + + for (const auto &entry : schema.as_object()) { + const auto &keyword_type{walker(entry.first, vocabularies).type}; + ONLY_CONTINUE_IF(keyword_type != SchemaKeywordType::Reference); + ONLY_CONTINUE_IF(keyword_type == + SchemaKeywordType::ApplicatorValueInPlaceOther || + !IS_IN_PLACE_APPLICATOR(keyword_type)); + } + + // Walk up through in-place applicators excluding `allOf`. In `allOf` the + // parent's type already constrains all branches (a conjunction), and other + // rules may want to lift type out of conjunctions + const auto ancestor{WALK_UP( + root, frame, location, walker, resolver, + [](const SchemaKeywordType keyword_type) { + return IS_IN_PLACE_APPLICATOR(keyword_type) && + keyword_type != SchemaKeywordType::ApplicatorElementsInPlace; + }, + [](const JSON &ancestor_schema, const Vocabularies &) { + return ancestor_schema.defines("type"); + })}; + + ONLY_CONTINUE_IF(ancestor.has_value()); + this->inherited_type_ = get(root, ancestor.value().get()).at("type"); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.assign("type", this->inherited_type_); + } + +private: + mutable sourcemeta::core::JSON inherited_type_{ + sourcemeta::core::JSON{nullptr}}; +}; diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_null_as_enum.h b/vendor/blaze/src/alterschema/canonicalizer/type_null_as_enum.h similarity index 94% rename from vendor/core/src/extension/alterschema/canonicalizer/type_null_as_enum.h rename to vendor/blaze/src/alterschema/canonicalizer/type_null_as_enum.h index 81a01c2..93b6af3 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_null_as_enum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_null_as_enum.h @@ -16,7 +16,7 @@ class TypeNullAsEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -25,7 +25,8 @@ class TypeNullAsEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_1}) && + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_0}) && schema.is_object() && schema.defines("type") && schema.at("type").is_string() && schema.at("type").to_string() == "null" && diff --git a/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/type_union_implicit.h similarity index 71% rename from vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h rename to vendor/blaze/src/alterschema/canonicalizer/type_union_implicit.h index aff2a46..8d1c353 100644 --- a/vendor/core/src/extension/alterschema/canonicalizer/type_union_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_union_implicit.h @@ -15,9 +15,15 @@ class TypeUnionImplicit final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { using namespace sourcemeta::core; - ONLY_CONTINUE_IF(schema.is_object()); + ONLY_CONTINUE_IF(schema.is_object() && !schema.empty()); + ONLY_CONTINUE_IF(!vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}) || + !schema.defines("disallow")); ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -40,22 +46,12 @@ class TypeUnionImplicit final : public SchemaTransformRule { for (const auto &entry : schema.as_object()) { const auto &keyword_type{walker(entry.first, vocabularies).type}; - // References point to other schemas that may have type constraints ONLY_CONTINUE_IF(keyword_type != SchemaKeywordType::Reference); - - // Logical in-place applicators apply without affecting the instance - // location, meaning they impose constraints on the same instance. Adding - // an implicit type union alongside these would create redundant branches - // that need complex simplification ONLY_CONTINUE_IF( - keyword_type != SchemaKeywordType::ApplicatorValueOrElementsInPlace && - keyword_type != SchemaKeywordType::ApplicatorMembersInPlaceSome && - keyword_type != SchemaKeywordType::ApplicatorElementsInPlace && - keyword_type != SchemaKeywordType::ApplicatorElementsInPlaceSome && - keyword_type != - SchemaKeywordType::ApplicatorElementsInPlaceSomeNegate && - keyword_type != SchemaKeywordType::ApplicatorValueInPlaceMaybe && - keyword_type != SchemaKeywordType::ApplicatorValueInPlaceNegate); + // Applicators like `contentSchema` applies to decoded content, not + // the current instance + keyword_type == SchemaKeywordType::ApplicatorValueInPlaceOther || + !IS_IN_PLACE_APPLICATOR(keyword_type)); } return true; diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_union_to_schemas.h b/vendor/blaze/src/alterschema/canonicalizer/type_union_to_schemas.h new file mode 100644 index 0000000..086c349 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/type_union_to_schemas.h @@ -0,0 +1,72 @@ +class TypeUnionToSchemas final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + TypeUnionToSchemas() : SchemaTransformRule{"type_union_to_schemas", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_array()); + + for (const auto &element : schema.at("type").as_array()) { + if (element.is_string()) { + return true; + } + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + auto new_array{JSON::make_array()}; + for (const auto &element : schema.at("type").as_array()) { + if (element.is_string()) { + new_array.push_back(type_string_to_schema(element.to_string())); + } else { + new_array.push_back(element); + } + } + schema.assign("type", std::move(new_array)); + } + +private: + static auto type_string_to_schema(const std::string &type_name) -> JSON { + if (type_name == "null") { + auto result{JSON::make_object()}; + auto values{JSON::make_array()}; + values.push_back(JSON{nullptr}); + result.assign("enum", std::move(values)); + return result; + } + + if (type_name == "boolean") { + auto result{JSON::make_object()}; + auto values{JSON::make_array()}; + values.push_back(JSON{false}); + values.push_back(JSON{true}); + result.assign("enum", std::move(values)); + return result; + } + + if (type_name == "any") { + return JSON::make_object(); + } + + auto result{JSON::make_object()}; + result.assign("type", JSON{type_name}); + return result; + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_allof.h b/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_allof.h new file mode 100644 index 0000000..11de0de --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_allof.h @@ -0,0 +1,445 @@ +class TypeWithApplicatorToAllOf final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + TypeWithApplicatorToAllOf() + : SchemaTransformRule{"type_with_applicator_to_allof", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.is_object()); + + const bool has_not{schema.defines("not")}; + const bool has_anyof{schema.defines("anyOf")}; + const bool has_allof{schema.defines("allOf")}; + const bool has_oneof{schema.defines("oneOf")}; + const bool has_if{ + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator}) && + schema.defines("if")}; + this->has_if_then_else_ = has_if; + const bool has_type{schema.defines("type") && + schema.at("type").is_string()}; + const bool has_enum{schema.defines("enum")}; + const bool is_modern{ + vocabularies.contains(Vocabularies::Known::JSON_Schema_2019_09_Core) || + vocabularies.contains(Vocabularies::Known::JSON_Schema_2020_12_Core)}; + const bool has_ref{!is_modern && schema.defines("$ref")}; + this->has_modern_ref_ = is_modern && schema.defines("$ref"); + this->has_dynamic_ref_ = + vocabularies.contains(Vocabularies::Known::JSON_Schema_2020_12_Core) && + schema.defines("$dynamicRef"); + const unsigned int applicator_count{ + (has_not ? 1U : 0U) + (has_anyof ? 1U : 0U) + (has_allof ? 1U : 0U) + + (has_oneof ? 1U : 0U) + (has_if ? 1U : 0U) + + (this->has_modern_ref_ ? 1U : 0U) + (this->has_dynamic_ref_ ? 1U : 0U)}; + const bool has_structural{has_type || has_enum || has_ref}; + + bool modern_ref_needs_wrapping{false}; + if (this->has_modern_ref_ || this->has_dynamic_ref_) { + for (const auto &entry : schema.as_object()) { + if (entry.first == "$ref" || entry.first == "$dynamicRef") { + continue; + } + const auto keyword_type{walker(entry.first, vocabularies).type}; + if (keyword_type != sourcemeta::core::SchemaKeywordType::Unknown && + keyword_type != sourcemeta::core::SchemaKeywordType::Annotation && + keyword_type != sourcemeta::core::SchemaKeywordType::Comment) { + modern_ref_needs_wrapping = true; + break; + } + } + } + + this->has_unevaluated_ = + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}) && + (schema.defines("unevaluatedProperties") || + schema.defines("unevaluatedItems")); + bool has_orphaned_typed_keywords{false}; + if (is_modern && applicator_count >= 1 && !has_structural) { + for (const auto &entry : schema.as_object()) { + if (entry.first == "unevaluatedProperties" || + entry.first == "unevaluatedItems") { + continue; + } + const auto &metadata{walker(entry.first, vocabularies)}; + if (metadata.instances.any()) { + has_orphaned_typed_keywords = true; + break; + } + } + } + + ONLY_CONTINUE_IF((has_structural && applicator_count >= 1) || + applicator_count >= 2 || modern_ref_needs_wrapping || + has_orphaned_typed_keywords); + + this->strategy_ = Strategy::FullRestructure; + this->applicators_with_refs_ = 0; + for (const auto &reference : frame.references()) { + const auto &source_pointer{reference.first.second}; + if (!source_pointer.starts_with(location.pointer)) { + continue; + } + const auto relative{source_pointer.resolve_from(location.pointer)}; + if (relative.empty() || !relative.at(0).is_property()) { + continue; + } + const auto &first_keyword{relative.at(0).to_property()}; + const auto bit{applicator_bit(first_keyword)}; + if (bit != 0) { + const auto destination{frame.traverse(reference.second.destination)}; + if (destination.has_value()) { + const auto &dest_pointer{destination->get().pointer}; + if (dest_pointer.starts_with(location.pointer)) { + const auto relative_dest{ + dest_pointer.resolve_from(location.pointer)}; + if (!relative_dest.empty() && relative_dest.at(0).is_property() && + (relative_dest.at(0).to_property() == "definitions" || + relative_dest.at(0).to_property() == "$defs" || + relative_dest.at(0).to_property() == "dependencies" || + relative_dest.at(0).to_property() == "dependentSchemas")) { + continue; + } + } else { + continue; + } + } + this->strategy_ = Strategy::SafeExtract; + this->applicators_with_refs_ |= bit; + } + } + + if (this->strategy_ == Strategy::SafeExtract && !has_structural) { + if (!has_allof) { + this->strategy_ = Strategy::FullRestructure; + return true; + } + + bool all_refs_fixed{true}; + for (const auto &reference : frame.references()) { + const auto &source_pointer{reference.first.second}; + if (!source_pointer.starts_with(location.pointer)) { + continue; + } + const auto relative_src{source_pointer.resolve_from(location.pointer)}; + if (relative_src.empty() || !relative_src.at(0).is_property()) { + continue; + } + const auto &src_keyword{relative_src.at(0).to_property()}; + if (src_keyword != "not" && src_keyword != "anyOf" && + src_keyword != "oneOf" && + !(this->has_if_then_else_ && + (src_keyword == "if" || src_keyword == "then" || + src_keyword == "else"))) { + continue; + } + + const auto destination{frame.traverse(reference.second.destination)}; + if (!destination.has_value()) { + all_refs_fixed = false; + break; + } + + const auto relative_dest{ + destination->get().pointer.resolve_from(location.pointer)}; + if (relative_dest.empty() || !relative_dest.at(0).is_property() || + relative_dest.at(0).to_property() != "allOf") { + all_refs_fixed = false; + break; + } + } + + if (all_refs_fixed) { + this->strategy_ = Strategy::MergeIntoAllOf; + } else { + return false; + } + } + + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + this->typed_keywords_.clear(); + + if (this->strategy_ == Strategy::MergeIntoAllOf) { + for (const auto &applicator : APPLICATORS_WITHOUT_ALLOF) { + if (!schema.defines(applicator)) { + continue; + } + auto branch{JSON::make_object()}; + branch.assign(applicator, schema.at(applicator)); + if (std::string_view{applicator} == "if" && this->has_if_then_else_) { + if (schema.defines("then")) { + branch.assign("then", schema.at("then")); + } + if (schema.defines("else")) { + branch.assign("else", schema.at("else")); + } + } + schema.at("allOf").push_back(std::move(branch)); + schema.erase(applicator); + } + if (this->has_if_then_else_) { + if (schema.defines("then")) { + schema.erase("then"); + } + if (schema.defines("else")) { + schema.erase("else"); + } + } + return; + } + + auto typed_branch{JSON::make_object()}; + for (const auto &entry : schema.as_object()) { + if (entry.first == "not" || entry.first == "anyOf" || + entry.first == "allOf" || entry.first == "oneOf" || + entry.first == "$schema" || entry.first == "id" || + entry.first == "$id" || entry.first == "definitions" || + entry.first == "$defs" || entry.first == "dependencies" || + entry.first == "dependentSchemas" || + (this->has_if_then_else_ && + (entry.first == "if" || entry.first == "then" || + entry.first == "else")) || + (this->has_modern_ref_ && entry.first == "$ref") || + (this->has_dynamic_ref_ && entry.first == "$dynamicRef") || + (this->has_unevaluated_ && (entry.first == "unevaluatedProperties" || + entry.first == "unevaluatedItems"))) { + continue; + } + typed_branch.assign(entry.first, entry.second); + this->typed_keywords_.emplace_back(entry.first); + } + + for (const auto &key : this->typed_keywords_) { + schema.erase(key); + } + + if (this->strategy_ == Strategy::SafeExtract) { + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + this->typed_branch_index_ = schema.at("allOf").size(); + schema.at("allOf").push_back(std::move(typed_branch)); + } else { + this->typed_branch_index_ = 0; + auto new_allof{JSON::make_array()}; + new_allof.push_back(std::move(typed_branch)); + schema.assign("allOf", std::move(new_allof)); + } + + for (const auto &applicator : APPLICATORS_WITHOUT_ALLOF) { + if (!schema.defines(applicator)) { + continue; + } + if (this->applicators_with_refs_ & applicator_bit(applicator)) { + continue; + } + auto branch{JSON::make_object()}; + branch.assign(applicator, schema.at(applicator)); + if (std::string_view{applicator} == "if" && this->has_if_then_else_) { + if (schema.defines("then")) { + branch.assign("then", schema.at("then")); + } + if (schema.defines("else")) { + branch.assign("else", schema.at("else")); + } + } + schema.at("allOf").push_back(std::move(branch)); + schema.erase(applicator); + if (std::string_view{applicator} == "if" && this->has_if_then_else_) { + if (schema.defines("then")) { + schema.erase("then"); + } + if (schema.defines("else")) { + schema.erase("else"); + } + } + } + + return; + } + + auto new_allof{JSON::make_array()}; + this->applicator_indices_ = 0; + + if (this->has_modern_ref_ && schema.defines("$ref")) { + auto branch{JSON::make_object()}; + branch.assign("$ref", schema.at("$ref")); + new_allof.push_back(std::move(branch)); + } + if (this->has_dynamic_ref_ && schema.defines("$dynamicRef")) { + auto branch{JSON::make_object()}; + branch.assign("$dynamicRef", schema.at("$dynamicRef")); + new_allof.push_back(std::move(branch)); + } + + for (const auto &applicator : APPLICATORS) { + if (!schema.defines(applicator)) { + continue; + } + auto branch{JSON::make_object()}; + branch.assign(applicator, schema.at(applicator)); + if (std::string_view{applicator} == "if" && this->has_if_then_else_) { + if (schema.defines("then")) { + branch.assign("then", schema.at("then")); + } + if (schema.defines("else")) { + branch.assign("else", schema.at("else")); + } + } + new_allof.push_back(std::move(branch)); + this->applicator_indices_ |= applicator_bit(applicator); + } + + if (!this->typed_keywords_.empty()) { + new_allof.push_back(std::move(typed_branch)); + } + + auto new_schema{JSON::make_object()}; + if (schema.defines("$schema")) { + new_schema.assign("$schema", schema.at("$schema")); + } + if (schema.defines("id")) { + new_schema.assign("id", schema.at("id")); + } + if (schema.defines("$id")) { + new_schema.assign("$id", schema.at("$id")); + } + if (schema.defines("definitions")) { + new_schema.assign("definitions", schema.at("definitions")); + } + if (schema.defines("$defs")) { + new_schema.assign("$defs", schema.at("$defs")); + } + if (schema.defines("dependencies")) { + new_schema.assign("dependencies", schema.at("dependencies")); + } + if (schema.defines("dependentSchemas")) { + new_schema.assign("dependentSchemas", schema.at("dependentSchemas")); + } + if (this->has_unevaluated_) { + if (schema.defines("unevaluatedProperties")) { + new_schema.assign("unevaluatedProperties", + schema.at("unevaluatedProperties")); + } + if (schema.defines("unevaluatedItems")) { + new_schema.assign("unevaluatedItems", schema.at("unevaluatedItems")); + } + } + new_schema.assign("allOf", std::move(new_allof)); + schema.into(std::move(new_schema)); + } + + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, + const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + const auto relative{target.resolve_from(current)}; + if (relative.empty() || !relative.at(0).is_property()) { + return target; + } + + const auto &keyword{relative.at(0).to_property()}; + static const JSON::String allof_keyword{"allOf"}; + + for (const auto &typed_kw : this->typed_keywords_) { + if (typed_kw == keyword) { + const Pointer old_prefix{current.concat({keyword})}; + if (this->strategy_ == Strategy::SafeExtract) { + const Pointer new_prefix{current.concat( + {allof_keyword, this->typed_branch_index_, keyword})}; + return target.rebase(old_prefix, new_prefix); + } else { + const std::size_t typed_index{(this->has_modern_ref_ ? 1U : 0U) + + (this->has_dynamic_ref_ ? 1U : 0U) + + static_cast(std::popcount( + this->applicator_indices_))}; + const Pointer new_prefix{ + current.concat({allof_keyword, typed_index, keyword})}; + return target.rebase(old_prefix, new_prefix); + } + } + } + + if (this->strategy_ == Strategy::FullRestructure) { + std::size_t index{0}; + if (this->has_modern_ref_) { + index++; + } + if (this->has_dynamic_ref_) { + index++; + } + + for (const auto &applicator : APPLICATORS) { + if (keyword == applicator || + (this->has_if_then_else_ && std::string_view{applicator} == "if" && + (keyword == "then" || keyword == "else"))) { + const Pointer old_prefix{current.concat({keyword})}; + const Pointer new_prefix{ + current.concat({allof_keyword, index, keyword})}; + return target.rebase(old_prefix, new_prefix); + } + if (this->applicator_indices_ & applicator_bit(applicator)) { + index++; + } + } + } + + return target; + } + +private: + static constexpr std::array APPLICATORS{ + {"not", "anyOf", "allOf", "oneOf", "if"}}; + static constexpr std::array APPLICATORS_WITHOUT_ALLOF{ + {"not", "anyOf", "oneOf", "if"}}; + + static constexpr auto applicator_bit(std::string_view keyword) + -> std::uint8_t { + if (keyword == "not") + return 1; + if (keyword == "anyOf") + return 2; + if (keyword == "allOf") + return 4; + if (keyword == "oneOf") + return 8; + if (keyword == "if" || keyword == "then" || keyword == "else") + return 16; + return 0; + } + + enum class Strategy : std::uint8_t { + FullRestructure, + SafeExtract, + MergeIntoAllOf + }; + mutable Strategy strategy_{Strategy::FullRestructure}; + mutable bool has_if_then_else_{false}; + mutable bool has_modern_ref_{false}; + mutable bool has_dynamic_ref_{false}; + mutable bool has_unevaluated_{false}; + mutable std::vector typed_keywords_; + mutable std::uint8_t applicator_indices_{0}; + mutable std::uint8_t applicators_with_refs_{0}; + mutable std::size_t typed_branch_index_{0}; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_extends.h b/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_extends.h new file mode 100644 index 0000000..c4a11bd --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_extends.h @@ -0,0 +1,149 @@ +class TypeWithApplicatorToExtends final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + TypeWithApplicatorToExtends() + : SchemaTransformRule{"type_with_applicator_to_extends", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3}) && + schema.is_object()); + + const bool has_extends{schema.defines("extends") && + schema.at("extends").is_array()}; + const bool has_disallow{schema.defines("disallow") && + schema.at("disallow").is_array()}; + const bool has_type_array{schema.defines("type") && + schema.at("type").is_array()}; + const bool has_type{schema.defines("type") && + schema.at("type").is_string()}; + const bool has_enum{schema.defines("enum")}; + const unsigned int applicator_count{(has_extends ? 1U : 0U) + + (has_disallow ? 1U : 0U) + + (has_type_array ? 1U : 0U)}; + const bool has_structural{has_type || has_enum}; + + ONLY_CONTINUE_IF((has_structural && applicator_count >= 1) || + applicator_count >= 2); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + this->typed_keywords_.clear(); + + auto typed_branch{JSON::make_object()}; + for (const auto &entry : schema.as_object()) { + if (entry.first == "extends" || entry.first == "disallow" || + entry.first == "$schema" || entry.first == "id" || + (entry.first == "type" && entry.second.is_array())) { + continue; + } + typed_branch.assign(entry.first, entry.second); + this->typed_keywords_.emplace_back(entry.first); + } + + for (const auto &key : this->typed_keywords_) { + schema.erase(key); + } + + auto new_extends{JSON::make_array()}; + this->applicator_indices_ = 0; + + for (const auto &applicator : APPLICATORS) { + if (!schema.defines(applicator)) { + continue; + } + const auto &value{schema.at(applicator)}; + if (std::string_view{applicator} == "type" && !value.is_array()) { + continue; + } + auto branch{JSON::make_object()}; + branch.assign(applicator, value); + new_extends.push_back(std::move(branch)); + this->applicator_indices_ |= applicator_bit(applicator); + } + + if (!this->typed_keywords_.empty()) { + new_extends.push_back(std::move(typed_branch)); + } + + auto new_schema{JSON::make_object()}; + if (schema.defines("$schema")) { + new_schema.assign("$schema", schema.at("$schema")); + } + if (schema.defines("id")) { + new_schema.assign("id", schema.at("id")); + } + new_schema.assign("extends", std::move(new_extends)); + schema.into(std::move(new_schema)); + } + + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, + const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + const auto relative{target.resolve_from(current)}; + if (relative.empty() || !relative.at(0).is_property()) { + return target; + } + + const auto &keyword{relative.at(0).to_property()}; + static const JSON::String extends_keyword{"extends"}; + + for (const auto &typed_keyword : this->typed_keywords_) { + if (typed_keyword == keyword) { + const Pointer old_prefix{current.concat({keyword})}; + const std::size_t typed_index{ + static_cast(std::popcount(this->applicator_indices_))}; + const Pointer new_prefix{ + current.concat({extends_keyword, typed_index, keyword})}; + return target.rebase(old_prefix, new_prefix); + } + } + + std::size_t index{0}; + for (const auto &applicator : APPLICATORS) { + if (keyword == applicator) { + const Pointer old_prefix{current.concat({keyword})}; + const Pointer new_prefix{ + current.concat({extends_keyword, index, keyword})}; + return target.rebase(old_prefix, new_prefix); + } + if (this->applicator_indices_ & applicator_bit(applicator)) { + index++; + } + } + + return target; + } + +private: + static constexpr std::array APPLICATORS{ + {"extends", "disallow", "type"}}; + + static constexpr auto applicator_bit(std::string_view keyword) + -> std::uint8_t { + if (keyword == "extends") + return 1; + if (keyword == "disallow") + return 2; + if (keyword == "type") + return 4; + return 0; + } + + mutable std::vector typed_keywords_; + mutable std::uint8_t applicator_indices_{0}; +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/unevaluated_items_to_items.h b/vendor/blaze/src/alterschema/canonicalizer/unevaluated_items_to_items.h new file mode 100644 index 0000000..6894d6b --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/unevaluated_items_to_items.h @@ -0,0 +1,54 @@ +class UnevaluatedItemsToItems final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UnevaluatedItemsToItems() + : SchemaTransformRule{"unevaluated_items_to_items", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}) && + schema.is_object() && schema.defines("unevaluatedItems")); + + for (const auto &entry : schema.as_object()) { + if (entry.first == "unevaluatedItems") { + continue; + } + const auto &metadata{walker(entry.first, vocabularies)}; + const auto keyword_type{metadata.type}; + if (keyword_type != sourcemeta::core::SchemaKeywordType::Unknown && + keyword_type != sourcemeta::core::SchemaKeywordType::Assertion && + keyword_type != sourcemeta::core::SchemaKeywordType::Annotation && + keyword_type != sourcemeta::core::SchemaKeywordType::Comment && + keyword_type != sourcemeta::core::SchemaKeywordType::Other && + keyword_type != + sourcemeta::core::SchemaKeywordType::LocationMembers) { + return false; + } + } + + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.rename("unevaluatedItems", "items"); + } + + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, + const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + return target.rebase(current.concat({"unevaluatedItems"}), + current.concat({"items"})); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/unevaluated_properties_to_additional_properties.h b/vendor/blaze/src/alterschema/canonicalizer/unevaluated_properties_to_additional_properties.h new file mode 100644 index 0000000..33a5bc1 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/unevaluated_properties_to_additional_properties.h @@ -0,0 +1,56 @@ +class UnevaluatedPropertiesToAdditionalProperties final + : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UnevaluatedPropertiesToAdditionalProperties() + : SchemaTransformRule{"unevaluated_properties_to_additional_properties", + ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}) && + schema.is_object() && schema.defines("unevaluatedProperties")); + + for (const auto &entry : schema.as_object()) { + if (entry.first == "unevaluatedProperties") { + continue; + } + const auto &metadata{walker(entry.first, vocabularies)}; + const auto keyword_type{metadata.type}; + if (keyword_type != sourcemeta::core::SchemaKeywordType::Unknown && + keyword_type != sourcemeta::core::SchemaKeywordType::Assertion && + keyword_type != sourcemeta::core::SchemaKeywordType::Annotation && + keyword_type != sourcemeta::core::SchemaKeywordType::Comment && + keyword_type != sourcemeta::core::SchemaKeywordType::Other && + keyword_type != + sourcemeta::core::SchemaKeywordType::LocationMembers) { + return false; + } + } + + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.rename("unevaluatedProperties", "additionalProperties"); + } + + [[nodiscard]] auto rereference(const std::string_view, const Pointer &, + const Pointer &target, + const Pointer ¤t) const + -> Pointer override { + return target.rebase(current.concat({"unevaluatedProperties"}), + current.concat({"additionalProperties"})); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_can_equal_bounds.h b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_can_equal_bounds.h new file mode 100644 index 0000000..b39ca0d --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_can_equal_bounds.h @@ -0,0 +1,40 @@ +class UnsatisfiableCanEqualBounds final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; + UnsatisfiableCanEqualBounds() + : SchemaTransformRule{"unsatisfiable_can_equal_bounds", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_2) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "number" || + schema.at("type").to_string() == "integer") && + schema.defines("minimum") && schema.at("minimum").is_number() && + schema.defines("maximum") && schema.at("maximum").is_number() && + schema.at("minimum") == schema.at("maximum")); + + const bool min_exclusive{schema.defines("minimumCanEqual") && + schema.at("minimumCanEqual").is_boolean() && + !schema.at("minimumCanEqual").to_boolean()}; + const bool max_exclusive{schema.defines("maximumCanEqual") && + schema.at("maximumCanEqual").is_boolean() && + !schema.at("maximumCanEqual").to_boolean()}; + ONLY_CONTINUE_IF(min_exclusive || max_exclusive); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.into(JSON{false}); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_exclusive_equal_bounds.h b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_exclusive_equal_bounds.h new file mode 100644 index 0000000..6da4b2d --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_exclusive_equal_bounds.h @@ -0,0 +1,41 @@ +class UnsatisfiableExclusiveEqualBounds final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; + UnsatisfiableExclusiveEqualBounds() + : SchemaTransformRule{"unsatisfiable_exclusive_equal_bounds", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && + (schema.at("type").to_string() == "number" || + schema.at("type").to_string() == "integer") && + schema.defines("minimum") && schema.at("minimum").is_number() && + schema.defines("maximum") && schema.at("maximum").is_number() && + schema.at("minimum") == schema.at("maximum")); + + const bool exclusive_min{schema.defines("exclusiveMinimum") && + schema.at("exclusiveMinimum").is_boolean() && + schema.at("exclusiveMinimum").to_boolean()}; + const bool exclusive_max{schema.defines("exclusiveMaximum") && + schema.at("exclusiveMaximum").is_boolean() && + schema.at("exclusiveMaximum").to_boolean()}; + ONLY_CONTINUE_IF(exclusive_min || exclusive_max); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.into(JSON{false}); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_type_and_enum.h b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_type_and_enum.h new file mode 100644 index 0000000..1c61685 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_type_and_enum.h @@ -0,0 +1,52 @@ +class UnsatisfiableTypeAndEnum final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::false_type; + UnsatisfiableTypeAndEnum() + : SchemaTransformRule{"unsatisfiable_type_and_enum", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_4, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_2020_12_Validation}) && + schema.is_object() && schema.defines("type") && + schema.at("type").is_string() && schema.defines("enum") && + schema.at("enum").is_array() && !schema.at("enum").empty()); + + const auto declared_types{parse_schema_type(schema.at("type"))}; + const bool integer_matches_integral{ + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_2019_09_Validation, + Vocabularies::Known::JSON_Schema_2020_12_Validation}) && + declared_types.test(std::to_underlying(JSON::Type::Integer))}; + ONLY_CONTINUE_IF(std::ranges::none_of( + schema.at("enum").as_array(), + [&declared_types, integer_matches_integral](const auto &value) { + return declared_types.test(std::to_underlying(value.type())) || + (integer_matches_integral && value.is_integral()); + })); + return true; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.into(JSON{false}); + } +}; diff --git a/vendor/core/src/extension/alterschema/common/allof_false_simplify.h b/vendor/blaze/src/alterschema/common/allof_false_simplify.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/allof_false_simplify.h rename to vendor/blaze/src/alterschema/common/allof_false_simplify.h index f8a59b1..3abef5b 100644 --- a/vendor/core/src/extension/alterschema/common/allof_false_simplify.h +++ b/vendor/blaze/src/alterschema/common/allof_false_simplify.h @@ -15,7 +15,7 @@ class AllOfFalseSimplify final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { static const JSON::String KEYWORD{"allOf"}; ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/anyof_false_simplify.h b/vendor/blaze/src/alterschema/common/anyof_false_simplify.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/anyof_false_simplify.h rename to vendor/blaze/src/alterschema/common/anyof_false_simplify.h index 562ba54..387f308 100644 --- a/vendor/core/src/extension/alterschema/common/anyof_false_simplify.h +++ b/vendor/blaze/src/alterschema/common/anyof_false_simplify.h @@ -15,7 +15,7 @@ class AnyOfFalseSimplify final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { static const JSON::String KEYWORD{"anyOf"}; ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/anyof_remove_false_schemas.h b/vendor/blaze/src/alterschema/common/anyof_remove_false_schemas.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/anyof_remove_false_schemas.h rename to vendor/blaze/src/alterschema/common/anyof_remove_false_schemas.h index d8bee7d..837c5f7 100644 --- a/vendor/core/src/extension/alterschema/common/anyof_remove_false_schemas.h +++ b/vendor/blaze/src/alterschema/common/anyof_remove_false_schemas.h @@ -16,7 +16,7 @@ class AnyOfRemoveFalseSchemas final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { static const JSON::String KEYWORD{"anyOf"}; ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/anyof_true_simplify.h b/vendor/blaze/src/alterschema/common/anyof_true_simplify.h similarity index 63% rename from vendor/core/src/extension/alterschema/common/anyof_true_simplify.h rename to vendor/blaze/src/alterschema/common/anyof_true_simplify.h index 8cdef08..f9f08af 100644 --- a/vendor/core/src/extension/alterschema/common/anyof_true_simplify.h +++ b/vendor/blaze/src/alterschema/common/anyof_true_simplify.h @@ -9,13 +9,13 @@ class AnyOfTrueSimplify final : public SchemaTransformRule { [[nodiscard]] auto condition(const sourcemeta::core::JSON &schema, - const sourcemeta::core::JSON &, + const sourcemeta::core::JSON &root, const sourcemeta::core::Vocabularies &vocabularies, const sourcemeta::core::SchemaFrame &frame, const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { static const JSON::String KEYWORD{"anyOf"}; ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, @@ -26,6 +26,32 @@ class AnyOfTrueSimplify final : public SchemaTransformRule { schema.is_object() && schema.defines(KEYWORD) && schema.at(KEYWORD).is_array()); + // When unevaluated keywords are present at this level or any + // ancestor, `anyOf` annotations are semantically meaningful even + // if one branch always succeeds + if (vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_2020_12_Applicator})) { + auto cursor{std::cref(location)}; + while (true) { + const auto ¤t_schema{ + sourcemeta::core::get(root, cursor.get().pointer)}; + if (current_schema.is_object() && + (current_schema.defines("unevaluatedItems") || + current_schema.defines("unevaluatedProperties"))) { + return false; + } + if (!cursor.get().parent.has_value()) { + break; + } + const auto parent_location{frame.traverse(cursor.get().parent.value())}; + if (!parent_location.has_value()) { + break; + } + cursor = parent_location.value(); + } + } + const auto &anyof{schema.at(KEYWORD)}; for (std::size_t index = 0; index < anyof.size(); ++index) { const auto &entry{anyof.at(index)}; diff --git a/vendor/core/src/extension/alterschema/common/const_in_enum.h b/vendor/blaze/src/alterschema/common/const_in_enum.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/const_in_enum.h rename to vendor/blaze/src/alterschema/common/const_in_enum.h index ccc0a34..001b695 100644 --- a/vendor/core/src/extension/alterschema/common/const_in_enum.h +++ b/vendor/blaze/src/alterschema/common/const_in_enum.h @@ -16,7 +16,7 @@ class ConstInEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/const_with_type.h b/vendor/blaze/src/alterschema/common/const_with_type.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/const_with_type.h rename to vendor/blaze/src/alterschema/common/const_with_type.h index a853f28..0254441 100644 --- a/vendor/core/src/extension/alterschema/common/const_with_type.h +++ b/vendor/blaze/src/alterschema/common/const_with_type.h @@ -16,7 +16,7 @@ class ConstWithType final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/content_media_type_without_encoding.h b/vendor/blaze/src/alterschema/common/content_media_type_without_encoding.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/content_media_type_without_encoding.h rename to vendor/blaze/src/alterschema/common/content_media_type_without_encoding.h index 7c8e3e0..61da1f7 100644 --- a/vendor/core/src/extension/alterschema/common/content_media_type_without_encoding.h +++ b/vendor/blaze/src/alterschema/common/content_media_type_without_encoding.h @@ -16,7 +16,7 @@ class ContentMediaTypeWithoutEncoding final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Content, Vocabularies::Known::JSON_Schema_2019_09_Content, diff --git a/vendor/core/src/extension/alterschema/common/content_schema_without_media_type.h b/vendor/blaze/src/alterschema/common/content_schema_without_media_type.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/content_schema_without_media_type.h rename to vendor/blaze/src/alterschema/common/content_schema_without_media_type.h index bfa89ef..705fa9f 100644 --- a/vendor/core/src/extension/alterschema/common/content_schema_without_media_type.h +++ b/vendor/blaze/src/alterschema/common/content_schema_without_media_type.h @@ -19,7 +19,7 @@ class ContentSchemaWithoutMediaType final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Content, Vocabularies::Known::JSON_Schema_2019_09_Content}) && diff --git a/vendor/core/src/extension/alterschema/common/dependencies_property_tautology.h b/vendor/blaze/src/alterschema/common/dependencies_property_tautology.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/dependencies_property_tautology.h rename to vendor/blaze/src/alterschema/common/dependencies_property_tautology.h index e7eec43..e8a6f06 100644 --- a/vendor/core/src/extension/alterschema/common/dependencies_property_tautology.h +++ b/vendor/blaze/src/alterschema/common/dependencies_property_tautology.h @@ -17,7 +17,7 @@ class DependenciesPropertyTautology final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, diff --git a/vendor/core/src/extension/alterschema/common/dependent_required_tautology.h b/vendor/blaze/src/alterschema/common/dependent_required_tautology.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/dependent_required_tautology.h rename to vendor/blaze/src/alterschema/common/dependent_required_tautology.h index 2cebac0..5aff7a3 100644 --- a/vendor/core/src/extension/alterschema/common/dependent_required_tautology.h +++ b/vendor/blaze/src/alterschema/common/dependent_required_tautology.h @@ -17,7 +17,7 @@ class DependentRequiredTautology final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/common/draft_official_dialect_with_https.h b/vendor/blaze/src/alterschema/common/draft_official_dialect_with_https.h similarity index 98% rename from vendor/core/src/extension/alterschema/common/draft_official_dialect_with_https.h rename to vendor/blaze/src/alterschema/common/draft_official_dialect_with_https.h index d9e83d9..0f1540c 100644 --- a/vendor/core/src/extension/alterschema/common/draft_official_dialect_with_https.h +++ b/vendor/blaze/src/alterschema/common/draft_official_dialect_with_https.h @@ -16,7 +16,7 @@ class DraftOfficialDialectWithHttps final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { using sourcemeta::core::SchemaBaseDialect; ONLY_CONTINUE_IF( location.base_dialect == SchemaBaseDialect::JSON_Schema_Draft_7 || diff --git a/vendor/core/src/extension/alterschema/common/draft_official_dialect_without_empty_fragment.h b/vendor/blaze/src/alterschema/common/draft_official_dialect_without_empty_fragment.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/draft_official_dialect_without_empty_fragment.h rename to vendor/blaze/src/alterschema/common/draft_official_dialect_without_empty_fragment.h index ff47fa6..2295076 100644 --- a/vendor/core/src/extension/alterschema/common/draft_official_dialect_without_empty_fragment.h +++ b/vendor/blaze/src/alterschema/common/draft_official_dialect_without_empty_fragment.h @@ -15,7 +15,7 @@ class DraftOfficialDialectWithoutEmptyFragment final const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && schema.at("$schema").is_string()); const auto &dialect{schema.at("$schema").to_string()}; diff --git a/vendor/core/src/extension/alterschema/common/draft_ref_siblings.h b/vendor/blaze/src/alterschema/common/draft_ref_siblings.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/draft_ref_siblings.h rename to vendor/blaze/src/alterschema/common/draft_ref_siblings.h index 2b4e02d..9384072 100644 --- a/vendor/core/src/extension/alterschema/common/draft_ref_siblings.h +++ b/vendor/blaze/src/alterschema/common/draft_ref_siblings.h @@ -15,7 +15,7 @@ class DraftRefSiblings final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, diff --git a/vendor/core/src/extension/alterschema/common/drop_allof_empty_schemas.h b/vendor/blaze/src/alterschema/common/drop_allof_empty_schemas.h similarity index 100% rename from vendor/core/src/extension/alterschema/common/drop_allof_empty_schemas.h rename to vendor/blaze/src/alterschema/common/drop_allof_empty_schemas.h diff --git a/vendor/core/src/extension/alterschema/common/duplicate_allof_branches.h b/vendor/blaze/src/alterschema/common/duplicate_allof_branches.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/duplicate_allof_branches.h rename to vendor/blaze/src/alterschema/common/duplicate_allof_branches.h index 2b84e54..da5921e 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_allof_branches.h +++ b/vendor/blaze/src/alterschema/common/duplicate_allof_branches.h @@ -18,7 +18,7 @@ class DuplicateAllOfBranches final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/duplicate_anyof_branches.h b/vendor/blaze/src/alterschema/common/duplicate_anyof_branches.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/duplicate_anyof_branches.h rename to vendor/blaze/src/alterschema/common/duplicate_anyof_branches.h index 50719fe..3f0e5b4 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_anyof_branches.h +++ b/vendor/blaze/src/alterschema/common/duplicate_anyof_branches.h @@ -18,7 +18,7 @@ class DuplicateAnyOfBranches final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/duplicate_enum_values.h b/vendor/blaze/src/alterschema/common/duplicate_enum_values.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/duplicate_enum_values.h rename to vendor/blaze/src/alterschema/common/duplicate_enum_values.h index 8b422cf..7246b55 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_enum_values.h +++ b/vendor/blaze/src/alterschema/common/duplicate_enum_values.h @@ -15,7 +15,7 @@ class DuplicateEnumValues final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/duplicate_required_values.h b/vendor/blaze/src/alterschema/common/duplicate_required_values.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/duplicate_required_values.h rename to vendor/blaze/src/alterschema/common/duplicate_required_values.h index 9e3f3cb..6d1d7d0 100644 --- a/vendor/core/src/extension/alterschema/common/duplicate_required_values.h +++ b/vendor/blaze/src/alterschema/common/duplicate_required_values.h @@ -16,7 +16,7 @@ class DuplicateRequiredValues final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/else_without_if.h b/vendor/blaze/src/alterschema/common/else_without_if.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/else_without_if.h rename to vendor/blaze/src/alterschema/common/else_without_if.h index 475c622..5488595 100644 --- a/vendor/core/src/extension/alterschema/common/else_without_if.h +++ b/vendor/blaze/src/alterschema/common/else_without_if.h @@ -18,7 +18,7 @@ class ElseWithoutIf final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/empty_object_as_true.h b/vendor/blaze/src/alterschema/common/empty_object_as_true.h similarity index 91% rename from vendor/core/src/extension/alterschema/common/empty_object_as_true.h rename to vendor/blaze/src/alterschema/common/empty_object_as_true.h index 288cab7..d506e04 100644 --- a/vendor/core/src/extension/alterschema/common/empty_object_as_true.h +++ b/vendor/blaze/src/alterschema/common/empty_object_as_true.h @@ -16,12 +16,13 @@ class EmptyObjectAsTrue final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Core, Vocabularies::Known::JSON_Schema_2019_09_Core, Vocabularies::Known::JSON_Schema_Draft_7, - Vocabularies::Known::JSON_Schema_Draft_6}) && + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_4}) && schema.is_object() && schema.empty()); return true; } diff --git a/vendor/core/src/extension/alterschema/common/enum_with_type.h b/vendor/blaze/src/alterschema/common/enum_with_type.h similarity index 77% rename from vendor/core/src/extension/alterschema/common/enum_with_type.h rename to vendor/blaze/src/alterschema/common/enum_with_type.h index 941b0fb..6e0031a 100644 --- a/vendor/core/src/extension/alterschema/common/enum_with_type.h +++ b/vendor/blaze/src/alterschema/common/enum_with_type.h @@ -16,7 +16,7 @@ class EnumWithType final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -30,9 +30,15 @@ class EnumWithType final : public SchemaTransformRule { schema.defines("enum") && schema.at("enum").is_array()); const auto current_types{parse_schema_type(schema.at("type"))}; + const bool integer_matches_integral{ + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_7}) && + current_types.test(std::to_underlying(JSON::Type::Integer))}; ONLY_CONTINUE_IF(std::ranges::all_of( - schema.at("enum").as_array(), [¤t_types](const auto &item) { - return current_types.test(std::to_underlying(item.type())); + schema.at("enum").as_array(), + [¤t_types, integer_matches_integral](const auto &item) { + return current_types.test(std::to_underlying(item.type())) || + (integer_matches_integral && item.is_integral()); })); return APPLIES_TO_KEYWORDS("enum", "type"); diff --git a/vendor/core/src/extension/alterschema/common/equal_numeric_bounds_to_enum.h b/vendor/blaze/src/alterschema/common/equal_numeric_bounds_to_enum.h similarity index 68% rename from vendor/core/src/extension/alterschema/common/equal_numeric_bounds_to_enum.h rename to vendor/blaze/src/alterschema/common/equal_numeric_bounds_to_enum.h index c7cf7de..5b16a16 100644 --- a/vendor/core/src/extension/alterschema/common/equal_numeric_bounds_to_enum.h +++ b/vendor/blaze/src/alterschema/common/equal_numeric_bounds_to_enum.h @@ -16,19 +16,32 @@ class EqualNumericBoundsToEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, - Vocabularies::Known::JSON_Schema_Draft_1}) && + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_0}) && schema.is_object() && schema.defines("type") && schema.at("type").is_string() && (schema.at("type").to_string() == "integer" || schema.at("type").to_string() == "number") && schema.defines("minimum") && schema.at("minimum").is_number() && schema.defines("maximum") && schema.at("maximum").is_number() && - schema.at("minimum") == schema.at("maximum")); + schema.at("minimum") == schema.at("maximum") && + !(schema.defines("exclusiveMinimum") && + schema.at("exclusiveMinimum").is_boolean() && + schema.at("exclusiveMinimum").to_boolean()) && + !(schema.defines("exclusiveMaximum") && + schema.at("exclusiveMaximum").is_boolean() && + schema.at("exclusiveMaximum").to_boolean()) && + !(schema.defines("minimumCanEqual") && + schema.at("minimumCanEqual").is_boolean() && + !schema.at("minimumCanEqual").to_boolean()) && + !(schema.defines("maximumCanEqual") && + schema.at("maximumCanEqual").is_boolean() && + !schema.at("maximumCanEqual").to_boolean())); return APPLIES_TO_KEYWORDS("minimum", "maximum"); } diff --git a/vendor/core/src/extension/alterschema/common/exclusive_maximum_number_and_maximum.h b/vendor/blaze/src/alterschema/common/exclusive_maximum_number_and_maximum.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/exclusive_maximum_number_and_maximum.h rename to vendor/blaze/src/alterschema/common/exclusive_maximum_number_and_maximum.h index dd096d0..cd44c6a 100644 --- a/vendor/core/src/extension/alterschema/common/exclusive_maximum_number_and_maximum.h +++ b/vendor/blaze/src/alterschema/common/exclusive_maximum_number_and_maximum.h @@ -16,7 +16,7 @@ class ExclusiveMaximumNumberAndMaximum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/exclusive_minimum_number_and_minimum.h b/vendor/blaze/src/alterschema/common/exclusive_minimum_number_and_minimum.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/exclusive_minimum_number_and_minimum.h rename to vendor/blaze/src/alterschema/common/exclusive_minimum_number_and_minimum.h index 3fb192f..58a5626 100644 --- a/vendor/core/src/extension/alterschema/common/exclusive_minimum_number_and_minimum.h +++ b/vendor/blaze/src/alterschema/common/exclusive_minimum_number_and_minimum.h @@ -16,7 +16,7 @@ class ExclusiveMinimumNumberAndMinimum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/if_without_then_else.h b/vendor/blaze/src/alterschema/common/if_without_then_else.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/if_without_then_else.h rename to vendor/blaze/src/alterschema/common/if_without_then_else.h index 93bafb3..c92b0e7 100644 --- a/vendor/core/src/extension/alterschema/common/if_without_then_else.h +++ b/vendor/blaze/src/alterschema/common/if_without_then_else.h @@ -19,7 +19,7 @@ class IfWithoutThenElse final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/ignored_metaschema.h b/vendor/blaze/src/alterschema/common/ignored_metaschema.h similarity index 94% rename from vendor/core/src/extension/alterschema/common/ignored_metaschema.h rename to vendor/blaze/src/alterschema/common/ignored_metaschema.h index 3faed03..1d37d6b 100644 --- a/vendor/core/src/extension/alterschema/common/ignored_metaschema.h +++ b/vendor/blaze/src/alterschema/common/ignored_metaschema.h @@ -16,7 +16,7 @@ class IgnoredMetaschema final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && schema.at("$schema").is_string()); const auto dialect{sourcemeta::core::dialect(schema)}; diff --git a/vendor/core/src/extension/alterschema/common/max_contains_without_contains.h b/vendor/blaze/src/alterschema/common/max_contains_without_contains.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/max_contains_without_contains.h rename to vendor/blaze/src/alterschema/common/max_contains_without_contains.h index 4cad64f..2bb43a7 100644 --- a/vendor/core/src/extension/alterschema/common/max_contains_without_contains.h +++ b/vendor/blaze/src/alterschema/common/max_contains_without_contains.h @@ -16,7 +16,7 @@ class MaxContainsWithoutContains final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/common/maximum_real_for_integer.h b/vendor/blaze/src/alterschema/common/maximum_real_for_integer.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/maximum_real_for_integer.h rename to vendor/blaze/src/alterschema/common/maximum_real_for_integer.h index 65b77b9..ac99fab 100644 --- a/vendor/core/src/extension/alterschema/common/maximum_real_for_integer.h +++ b/vendor/blaze/src/alterschema/common/maximum_real_for_integer.h @@ -16,7 +16,7 @@ class MaximumRealForInteger final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/min_contains_without_contains.h b/vendor/blaze/src/alterschema/common/min_contains_without_contains.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/min_contains_without_contains.h rename to vendor/blaze/src/alterschema/common/min_contains_without_contains.h index 1bd56a7..579ae9c 100644 --- a/vendor/core/src/extension/alterschema/common/min_contains_without_contains.h +++ b/vendor/blaze/src/alterschema/common/min_contains_without_contains.h @@ -16,7 +16,7 @@ class MinContainsWithoutContains final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/common/minimum_real_for_integer.h b/vendor/blaze/src/alterschema/common/minimum_real_for_integer.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/minimum_real_for_integer.h rename to vendor/blaze/src/alterschema/common/minimum_real_for_integer.h index ba46ddb..20180fa 100644 --- a/vendor/core/src/extension/alterschema/common/minimum_real_for_integer.h +++ b/vendor/blaze/src/alterschema/common/minimum_real_for_integer.h @@ -16,7 +16,7 @@ class MinimumRealForInteger final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_empty_fragment.h b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_empty_fragment.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/modern_official_dialect_with_empty_fragment.h rename to vendor/blaze/src/alterschema/common/modern_official_dialect_with_empty_fragment.h index 555dc68..849d70b 100644 --- a/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_empty_fragment.h +++ b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_empty_fragment.h @@ -16,7 +16,7 @@ class ModernOfficialDialectWithEmptyFragment final const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && schema.at("$schema").is_string()); const auto &dialect{schema.at("$schema").to_string()}; diff --git a/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_http.h b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_http.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/modern_official_dialect_with_http.h rename to vendor/blaze/src/alterschema/common/modern_official_dialect_with_http.h index cca26b7..facad86 100644 --- a/vendor/core/src/extension/alterschema/common/modern_official_dialect_with_http.h +++ b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_http.h @@ -16,7 +16,7 @@ class ModernOfficialDialectWithHttp final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { using sourcemeta::core::SchemaBaseDialect; ONLY_CONTINUE_IF( location.base_dialect == SchemaBaseDialect::JSON_Schema_2020_12 || diff --git a/vendor/core/src/extension/alterschema/common/non_applicable_additional_items.h b/vendor/blaze/src/alterschema/common/non_applicable_additional_items.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/non_applicable_additional_items.h rename to vendor/blaze/src/alterschema/common/non_applicable_additional_items.h index 4ac055c..7d6d356 100644 --- a/vendor/core/src/extension/alterschema/common/non_applicable_additional_items.h +++ b/vendor/blaze/src/alterschema/common/non_applicable_additional_items.h @@ -19,7 +19,7 @@ class NonApplicableAdditionalItems final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2019_09_Applicator, Vocabularies::Known::JSON_Schema_Draft_7, diff --git a/vendor/core/src/extension/alterschema/common/non_applicable_enum_validation_keywords.h b/vendor/blaze/src/alterschema/common/non_applicable_enum_validation_keywords.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/non_applicable_enum_validation_keywords.h rename to vendor/blaze/src/alterschema/common/non_applicable_enum_validation_keywords.h index a56c240..249f33d 100644 --- a/vendor/core/src/extension/alterschema/common/non_applicable_enum_validation_keywords.h +++ b/vendor/blaze/src/alterschema/common/non_applicable_enum_validation_keywords.h @@ -16,7 +16,7 @@ class NonApplicableEnumValidationKeywords final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/non_applicable_type_specific_keywords.h b/vendor/blaze/src/alterschema/common/non_applicable_type_specific_keywords.h similarity index 79% rename from vendor/core/src/extension/alterschema/common/non_applicable_type_specific_keywords.h rename to vendor/blaze/src/alterschema/common/non_applicable_type_specific_keywords.h index a727a7d..d67e158 100644 --- a/vendor/core/src/extension/alterschema/common/non_applicable_type_specific_keywords.h +++ b/vendor/blaze/src/alterschema/common/non_applicable_type_specific_keywords.h @@ -15,7 +15,7 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object()); auto current_types{vocabularies.contains_any( @@ -72,6 +72,33 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { continue; } + if (entry.first == "required" && + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_3_Hyper})) { + continue; + } + + if (entry.first == "maxDecimal" && + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_0_Hyper, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_1_Hyper})) { + continue; + } + + if (entry.first == "optional" && + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_0, + Vocabularies::Known::JSON_Schema_Draft_0_Hyper, + Vocabularies::Known::JSON_Schema_Draft_1, + Vocabularies::Known::JSON_Schema_Draft_1_Hyper, + Vocabularies::Known::JSON_Schema_Draft_2, + Vocabularies::Known::JSON_Schema_Draft_2_Hyper})) { + continue; + } + // If none of the types that the keyword applies to is a valid // type for the current schema, then by definition we can remove it if ((metadata.instances & current_types).none()) { diff --git a/vendor/core/src/extension/alterschema/common/not_false.h b/vendor/blaze/src/alterschema/common/not_false.h similarity index 100% rename from vendor/core/src/extension/alterschema/common/not_false.h rename to vendor/blaze/src/alterschema/common/not_false.h diff --git a/vendor/core/src/extension/alterschema/common/oneof_false_simplify.h b/vendor/blaze/src/alterschema/common/oneof_false_simplify.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/oneof_false_simplify.h rename to vendor/blaze/src/alterschema/common/oneof_false_simplify.h index 64c4adf..258700f 100644 --- a/vendor/core/src/extension/alterschema/common/oneof_false_simplify.h +++ b/vendor/blaze/src/alterschema/common/oneof_false_simplify.h @@ -15,7 +15,7 @@ class OneOfFalseSimplify final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { static const JSON::String KEYWORD{"oneOf"}; ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/oneof_to_anyof_disjoint_types.h b/vendor/blaze/src/alterschema/common/oneof_to_anyof_disjoint_types.h similarity index 98% rename from vendor/core/src/extension/alterschema/common/oneof_to_anyof_disjoint_types.h rename to vendor/blaze/src/alterschema/common/oneof_to_anyof_disjoint_types.h index 1d31dbd..2e403cf 100644 --- a/vendor/core/src/extension/alterschema/common/oneof_to_anyof_disjoint_types.h +++ b/vendor/blaze/src/alterschema/common/oneof_to_anyof_disjoint_types.h @@ -16,7 +16,7 @@ class OneOfToAnyOfDisjointTypes final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { static const JSON::String KEYWORD{"oneOf"}; ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/orphan_definitions.h b/vendor/blaze/src/alterschema/common/orphan_definitions.h similarity index 98% rename from vendor/core/src/extension/alterschema/common/orphan_definitions.h rename to vendor/blaze/src/alterschema/common/orphan_definitions.h index a59c869..fe7a7fa 100644 --- a/vendor/core/src/extension/alterschema/common/orphan_definitions.h +++ b/vendor/blaze/src/alterschema/common/orphan_definitions.h @@ -16,7 +16,7 @@ class OrphanDefinitions final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &resolver) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object()); const bool has_modern_core{ vocabularies.contains(Vocabularies::Known::JSON_Schema_2020_12_Core) || diff --git a/vendor/core/src/extension/alterschema/common/required_properties_in_properties.h b/vendor/blaze/src/alterschema/common/required_properties_in_properties.h similarity index 60% rename from vendor/core/src/extension/alterschema/common/required_properties_in_properties.h rename to vendor/blaze/src/alterschema/common/required_properties_in_properties.h index f4e7a17..00335df 100644 --- a/vendor/core/src/extension/alterschema/common/required_properties_in_properties.h +++ b/vendor/blaze/src/alterschema/common/required_properties_in_properties.h @@ -16,7 +16,7 @@ class RequiredPropertiesInProperties final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &resolver) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( ((vocabularies.contains( Vocabularies::Known::JSON_Schema_2020_12_Validation) && @@ -40,8 +40,13 @@ class RequiredPropertiesInProperties final : public SchemaTransformRule { for (const auto &property : schema.at("required").as_array()) { if (property.is_string() && !this->defined_in_properties_sibling(schema, property.to_string()) && - !this->defined_in_properties_parent(root, frame, location, walker, - resolver, property.to_string())) { + !WALK_UP_IN_PLACE_APPLICATORS( + root, frame, location, walker, resolver, + [&](const JSON &ancestor, const Vocabularies &) { + return this->defined_in_properties_sibling( + ancestor, property.to_string()); + }) + .has_value()) { locations.push_back(Pointer{"required", index}); } @@ -71,43 +76,4 @@ class RequiredPropertiesInProperties final : public SchemaTransformRule { schema.at("properties").is_object() && schema.at("properties").defines(property); }; - - [[nodiscard]] auto - defined_in_properties_parent(const JSON &root, const SchemaFrame &frame, - const SchemaFrame::Location &location, - const SchemaWalker &walker, - const SchemaResolver &resolver, - const JSON::String &property) const -> bool { - auto current_pointer = location.pointer; - auto current_parent = location.parent; - - while (current_parent.has_value()) { - const auto &parent_pointer{current_parent.value()}; - const auto relative_pointer{current_pointer.resolve_from(parent_pointer)}; - assert(!relative_pointer.empty() && relative_pointer.at(0).is_property()); - const auto parent{ - frame.traverse(frame.uri(parent_pointer).value().get())}; - assert(parent.has_value()); - const auto type{walker(relative_pointer.at(0).to_property(), - frame.vocabularies(parent.value().get(), resolver)) - .type}; - if (type != SchemaKeywordType::ApplicatorElementsInPlaceSome && - type != SchemaKeywordType::ApplicatorElementsInPlace && - type != SchemaKeywordType::ApplicatorValueInPlaceMaybe && - type != SchemaKeywordType::ApplicatorValueInPlaceNegate && - type != SchemaKeywordType::ApplicatorValueInPlaceOther) { - return false; - } - - if (this->defined_in_properties_sibling(get(root, parent_pointer), - property)) { - return true; - } - - current_pointer = parent_pointer; - current_parent = parent.value().get().parent; - } - - return false; - }; }; diff --git a/vendor/core/src/extension/alterschema/common/single_type_array.h b/vendor/blaze/src/alterschema/common/single_type_array.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/single_type_array.h rename to vendor/blaze/src/alterschema/common/single_type_array.h index 742ea10..310f82d 100644 --- a/vendor/core/src/extension/alterschema/common/single_type_array.h +++ b/vendor/blaze/src/alterschema/common/single_type_array.h @@ -15,7 +15,7 @@ class SingleTypeArray final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/common/then_without_if.h b/vendor/blaze/src/alterschema/common/then_without_if.h similarity index 95% rename from vendor/core/src/extension/alterschema/common/then_without_if.h rename to vendor/blaze/src/alterschema/common/then_without_if.h index 75d526d..cf233c8 100644 --- a/vendor/core/src/extension/alterschema/common/then_without_if.h +++ b/vendor/blaze/src/alterschema/common/then_without_if.h @@ -18,7 +18,7 @@ class ThenWithoutIf final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/unknown_keywords_prefix.h b/vendor/blaze/src/alterschema/common/unknown_keywords_prefix.h similarity index 100% rename from vendor/core/src/extension/alterschema/common/unknown_keywords_prefix.h rename to vendor/blaze/src/alterschema/common/unknown_keywords_prefix.h diff --git a/vendor/core/src/extension/alterschema/common/unknown_local_ref.h b/vendor/blaze/src/alterschema/common/unknown_local_ref.h similarity index 100% rename from vendor/core/src/extension/alterschema/common/unknown_local_ref.h rename to vendor/blaze/src/alterschema/common/unknown_local_ref.h diff --git a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_draft.h b/vendor/blaze/src/alterschema/common/unnecessary_allof_ref_wrapper_draft.h similarity index 96% rename from vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_draft.h rename to vendor/blaze/src/alterschema/common/unnecessary_allof_ref_wrapper_draft.h index f5f8d38..6aed8a6 100644 --- a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_draft.h +++ b/vendor/blaze/src/alterschema/common/unnecessary_allof_ref_wrapper_draft.h @@ -15,7 +15,7 @@ class UnnecessaryAllOfRefWrapperDraft final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, diff --git a/vendor/core/src/extension/alterschema/common/unsatisfiable_drop_validation.h b/vendor/blaze/src/alterschema/common/unsatisfiable_drop_validation.h similarity index 98% rename from vendor/core/src/extension/alterschema/common/unsatisfiable_drop_validation.h rename to vendor/blaze/src/alterschema/common/unsatisfiable_drop_validation.h index 4e5b44c..2c5fb83 100644 --- a/vendor/core/src/extension/alterschema/common/unsatisfiable_drop_validation.h +++ b/vendor/blaze/src/alterschema/common/unsatisfiable_drop_validation.h @@ -15,7 +15,7 @@ class UnsatisfiableDropValidation final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/unsatisfiable_in_place_applicator_type.h b/vendor/blaze/src/alterschema/common/unsatisfiable_in_place_applicator_type.h similarity index 98% rename from vendor/core/src/extension/alterschema/common/unsatisfiable_in_place_applicator_type.h rename to vendor/blaze/src/alterschema/common/unsatisfiable_in_place_applicator_type.h index 08152fa..72967d7 100644 --- a/vendor/core/src/extension/alterschema/common/unsatisfiable_in_place_applicator_type.h +++ b/vendor/blaze/src/alterschema/common/unsatisfiable_in_place_applicator_type.h @@ -16,7 +16,7 @@ class UnsatisfiableInPlaceApplicatorType final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object() && schema.defines("type")); ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema.h b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema.h new file mode 100644 index 0000000..beee4de --- /dev/null +++ b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema.h @@ -0,0 +1,117 @@ +#ifndef SOURCEMETA_BLAZE_ALTERSCHEMA_H_ +#define SOURCEMETA_BLAZE_ALTERSCHEMA_H_ + +/// @defgroup alterschema AlterSchema +/// @brief A growing collection of JSON Schema transformation rules. +/// +/// This functionality is included as follows: +/// +/// ```cpp +/// #include +/// ``` + +#ifndef SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT +#include +#endif + +#include +#include + +#include +#include + +#include // std::uint8_t +#include // std::optional, std::nullopt +#include // std::string_view +#include // std::false_type + +namespace sourcemeta::blaze { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup alterschema +/// The category of a built-in transformation rule +enum class AlterSchemaMode : std::uint8_t { + /// Rules that simplify the given schema for both human readability and + /// performance + Linter, + + /// Rules that surface implicit constraints and simplifies keywords that + /// are syntax sugar to other keywords, potentially decreasing human + /// readability in favor of explicitness + Canonicalizer, +}; + +/// @ingroup alterschema +/// Add a set of built-in schema transformation rules given a category. For +/// example: +/// +/// ```cpp +/// #include +/// #include +/// +/// sourcemeta::blaze::SchemaTransformer bundle; +/// +/// sourcemeta::blaze::add(bundle, +/// sourcemeta::blaze::AlterSchemaMode::Linter); +/// +/// auto schema = sourcemeta::core::parse_json(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "foo": 1, +/// "items": { +/// "type": "string", +/// "foo": 2 +/// } +/// })JSON"); +/// +/// bundle.apply(schema, sourcemeta::core::schema_walker, +/// sourcemeta::core::schema_resolver); +/// ``` +SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT +auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void; + +/// @ingroup alterschema +/// +/// A linter rule driven by a JSON Schema. Every subschema in the document +/// under inspection is validated as a JSON instance against the provided +/// rule schema. When a subschema does not conform, the rule fires and +/// reports the validation errors. The rule name is extracted from the +/// `title` keyword of the rule schema, and the rule description from the +/// `description` keyword. The title must consist only of lowercase ASCII +/// letters, digits, underscores, or slashes. +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaRule final + : public SchemaTransformRule { +public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; + SchemaRule(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const Compiler &compiler, + const std::string_view default_dialect = "", + const std::optional &tweaks = std::nullopt); + [[nodiscard]] auto condition(const sourcemeta::core::JSON &, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override; + +private: + Template template_; +}; + +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema_error.h b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema_error.h new file mode 100644 index 0000000..21e6012 --- /dev/null +++ b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema_error.h @@ -0,0 +1,171 @@ +#ifndef SOURCEMETA_BLAZE_ALTERSCHEMA_ERROR_H_ +#define SOURCEMETA_BLAZE_ALTERSCHEMA_ERROR_H_ + +#ifndef SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT +#include +#endif + +#include + +#include // std::exception +#include // std::string +#include // std::string_view +#include // std::move + +namespace sourcemeta::blaze { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup alterschema +/// An error that represents a missing schema rule name +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaRuleMissingNameError + : public std::exception { +public: + [[nodiscard]] auto what() const noexcept -> const char * override { + return "The schema rule is missing a title"; + } +}; + +/// @ingroup alterschema +/// An error that represents an invalid schema rule name. The name must +/// consist only of lowercase ASCII letters, digits, underscores, or slashes. +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaRuleInvalidNameError + : public std::exception { +public: + SchemaRuleInvalidNameError(const std::string_view identifier, + const char *message) + : identifier_{identifier}, message_{message} {} + SchemaRuleInvalidNameError(const std::string_view identifier, + std::string message) = delete; + SchemaRuleInvalidNameError(const std::string_view identifier, + std::string &&message) = delete; + SchemaRuleInvalidNameError(const std::string_view identifier, + std::string_view message) = delete; + + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_; + } + + [[nodiscard]] auto identifier() const noexcept -> const std::string & { + return this->identifier_; + } + +private: + std::string identifier_; + const char *message_; +}; + +/// @ingroup alterschema +/// An error that represents a schema rule name that does not match +/// the required pattern +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaRuleInvalidNamePatternError + : public std::exception { +public: + SchemaRuleInvalidNamePatternError(const std::string_view identifier, + const std::string_view regex) + : identifier_{identifier}, regex_{regex} {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return "The schema rule name does not match the required pattern"; + } + + [[nodiscard]] auto identifier() const noexcept -> const std::string & { + return this->identifier_; + } + + [[nodiscard]] auto regex() const noexcept -> const std::string & { + return this->regex_; + } + +private: + std::string identifier_; + std::string regex_; +}; + +/// @ingroup alterschema +/// An error that represents that a schema operation cannot continue +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaAbortError + : public std::exception { +public: + SchemaAbortError(const char *message) : message_{message} {} + SchemaAbortError(std::string message) = delete; + SchemaAbortError(std::string &&message) = delete; + SchemaAbortError(std::string_view message) = delete; + + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_; + } + +private: + const char *message_; +}; + +/// @ingroup alterschema +/// An error that represents a broken schema reference after transformation +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaBrokenReferenceError + : public std::exception { +public: + SchemaBrokenReferenceError(const std::string_view identifier, + sourcemeta::core::Pointer schema_location, + const char *message) + : identifier_{identifier}, schema_location_{std::move(schema_location)}, + message_{message} {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_; + } + + [[nodiscard]] auto identifier() const noexcept -> std::string_view { + return this->identifier_; + } + + [[nodiscard]] auto location() const noexcept + -> const sourcemeta::core::Pointer & { + return this->schema_location_; + } + +private: + std::string identifier_; + sourcemeta::core::Pointer schema_location_; + const char *message_; +}; + +/// @ingroup alterschema +/// An error that signifies that a transform rule was applied more than once +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaTransformRuleProcessedTwiceError + : public std::exception { +public: + SchemaTransformRuleProcessedTwiceError(const std::string_view name, + sourcemeta::core::Pointer location) + : name_{name}, location_{std::move(location)} {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return "Transformation rules must only be processed once"; + } + + [[nodiscard]] auto name() const noexcept -> std::string_view { + return this->name_; + } + + [[nodiscard]] auto location() const noexcept + -> const sourcemeta::core::Pointer & { + return this->location_; + } + +private: + std::string name_; + sourcemeta::core::Pointer location_; +}; + +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema_transformer.h b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema_transformer.h new file mode 100644 index 0000000..400a0df --- /dev/null +++ b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema_transformer.h @@ -0,0 +1,213 @@ +#ifndef SOURCEMETA_BLAZE_ALTERSCHEMA_TRANSFORMER_H_ +#define SOURCEMETA_BLAZE_ALTERSCHEMA_TRANSFORMER_H_ + +#ifndef SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT +#include +#endif + +#include +#include +#include + +#include // assert +#include // std::derived_from, std::same_as +#include // std::uint8_t +#include // std::function +#include // std::make_move_iterator, std::begin, std::end +#include // std::make_unique, std::unique_ptr +#include // std::optional, std::nullopt +#include // std::set +#include // std::string +#include // std::string_view +#include // std::tuple +#include // std::is_same_v, std::true_type +#include // std::move, std::forward +#include // std::vector + +namespace sourcemeta::blaze { + +/// @ingroup alterschema +/// +/// A class that represents a transformation rule. Clients of this class +/// are expected to subclass and implement their own condition and +/// transformation methods. +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaTransformRule { +public: + SchemaTransformRule(const std::string_view name, + const std::string_view message); + + virtual ~SchemaTransformRule() = default; + + SchemaTransformRule(const SchemaTransformRule &) = delete; + SchemaTransformRule(SchemaTransformRule &&) = delete; + auto operator=(const SchemaTransformRule &) -> SchemaTransformRule & = delete; + auto operator=(SchemaTransformRule &&) -> SchemaTransformRule & = delete; + + /// Compare a rule against another rule + auto operator==(const SchemaTransformRule &other) const -> bool; + + /// Fetch the name of a rule + [[nodiscard]] auto name() const noexcept -> std::string_view; + + /// Fetch the message of a rule + [[nodiscard]] auto message() const noexcept -> std::string_view; + + /// The result of evaluating a rule + struct Result { + Result(const bool applies_) : applies{applies_} {} + Result(const sourcemeta::core::Pointer &pointer) + : applies{true}, locations{pointer} { + assert(this->locations.size() == 1); + } + + template + requires std::same_as + Result(T &&container) : applies{true} { + auto &&input = std::forward(container); + if constexpr (requires { input.size(); }) { + locations.reserve(input.size()); + } + +#if __cpp_lib_containers_ranges >= 202202L + locations.assign_range(input); +#else + locations.assign(std::make_move_iterator(std::begin(input)), + std::make_move_iterator(std::end(input))); +#endif + } + + Result(std::vector &&locations_, + sourcemeta::core::JSON::String &&description_) + : applies{true}, locations{std::move(locations_)}, + description{std::move(description_)} {} + + bool applies; + std::vector locations; + std::optional description; + }; + + /// Check if the rule applies to a schema + [[nodiscard]] auto + check(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &root, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::JSON::String &exclude_keyword) const -> Result; + + /// A method to optionally fix any reference location that was affected by the + /// transformation + [[nodiscard]] virtual auto + rereference(const std::string_view reference, + const sourcemeta::core::Pointer &origin, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer; + + /// The rule condition + [[nodiscard]] virtual auto condition( + const sourcemeta::core::JSON &schema, const sourcemeta::core::JSON &root, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const -> Result = 0; + + /// The rule transformation. If this virtual method is not overriden, + /// then the rule is considered to not mutate the schema + virtual auto transform(sourcemeta::core::JSON &schema, + const Result &result) const -> void; + +private: +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251) +#endif + const std::string name_{}; + const std::string message_{}; +#if defined(_MSC_VER) +#pragma warning(default : 4251) +#endif +}; + +/// @ingroup alterschema +/// +/// You can use this class to perform top-down transformations on subschemas +/// given a set of rules. Every registered rule is applied to every subschema +/// of the passed schema until no longer of them applies. +class SOURCEMETA_BLAZE_ALTERSCHEMA_EXPORT SchemaTransformer { +public: + SchemaTransformer() = default; + +#if !defined(DOXYGEN) + SchemaTransformer(const SchemaTransformer &) = delete; + auto operator=(const SchemaTransformer &) -> SchemaTransformer & = delete; + SchemaTransformer(SchemaTransformer &&) = default; + auto operator=(SchemaTransformer &&) -> SchemaTransformer & = default; +#endif + + /// Add a rule to the bundle. Rules are evaluated in the order they are added. + /// It is the caller's responsibility to not add duplicate rules. + template T, typename... Args> + auto add(Args &&...args) -> std::string_view { + static_assert(requires { typename T::mutates; }); + static_assert(requires { typename T::reframe_after_transform; }); + static_assert( + std::is_same_v || + std::is_same_v); + auto &entry{this->rules.emplace_back( + std::make_unique(std::forward(args)...), + std::is_same_v, + std::is_same_v)}; + return std::get<0>(entry)->name(); + } + + /// Remove a rule from the bundle + auto remove(const std::string_view name) -> bool; + + /// The callback that is called whenever the condition of a rule holds true + using Callback = std::function; + + /// Apply the bundle of rules to a schema + [[nodiscard]] auto + apply(sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const Callback &callback, std::string_view default_dialect = "", + std::string_view default_id = "", + const sourcemeta::core::JSON::String &exclude_keyword = "") const + -> std::pair; + + /// Report back the rules from the bundle that need to be applied to a schema + [[nodiscard]] auto + check(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const Callback &callback, std::string_view default_dialect = "", + std::string_view default_id = "", + const sourcemeta::core::JSON::String &exclude_keyword = "") const + -> std::pair; + + [[nodiscard]] auto begin() const -> auto { return this->rules.cbegin(); } + [[nodiscard]] auto end() const -> auto { return this->rules.cend(); } + +private: +#if defined(_MSC_VER) +#pragma warning(disable : 4251) +#endif + std::vector, bool, bool>> + rules; +#if defined(_MSC_VER) +#pragma warning(default : 4251) +#endif +}; + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/core/src/extension/alterschema/linter/comment_trim.h b/vendor/blaze/src/alterschema/linter/comment_trim.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/comment_trim.h rename to vendor/blaze/src/alterschema/linter/comment_trim.h index c296908..9dabf43 100644 --- a/vendor/core/src/extension/alterschema/linter/comment_trim.h +++ b/vendor/blaze/src/alterschema/linter/comment_trim.h @@ -15,7 +15,7 @@ class CommentTrim final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Core, Vocabularies::Known::JSON_Schema_2019_09_Core, diff --git a/vendor/core/src/extension/alterschema/linter/const_not_in_enum.h b/vendor/blaze/src/alterschema/linter/const_not_in_enum.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/const_not_in_enum.h rename to vendor/blaze/src/alterschema/linter/const_not_in_enum.h index 7cb802a..78afaac 100644 --- a/vendor/core/src/extension/alterschema/linter/const_not_in_enum.h +++ b/vendor/blaze/src/alterschema/linter/const_not_in_enum.h @@ -16,7 +16,7 @@ class ConstNotInEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/linter/content_schema_default.h b/vendor/blaze/src/alterschema/linter/content_schema_default.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/content_schema_default.h rename to vendor/blaze/src/alterschema/linter/content_schema_default.h index 411d35a..04021cf 100644 --- a/vendor/core/src/extension/alterschema/linter/content_schema_default.h +++ b/vendor/blaze/src/alterschema/linter/content_schema_default.h @@ -19,7 +19,7 @@ class ContentSchemaDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Content, diff --git a/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h b/vendor/blaze/src/alterschema/linter/definitions_to_defs.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/definitions_to_defs.h rename to vendor/blaze/src/alterschema/linter/definitions_to_defs.h index 016bec7..827d258 100644 --- a/vendor/core/src/extension/alterschema/linter/definitions_to_defs.h +++ b/vendor/blaze/src/alterschema/linter/definitions_to_defs.h @@ -15,7 +15,7 @@ class DefinitionsToDefs final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Core, Vocabularies::Known::JSON_Schema_2019_09_Core}) && diff --git a/vendor/core/src/extension/alterschema/linter/dependencies_default.h b/vendor/blaze/src/alterschema/linter/dependencies_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/dependencies_default.h rename to vendor/blaze/src/alterschema/linter/dependencies_default.h index 29dde6b..ced56f8 100644 --- a/vendor/core/src/extension/alterschema/linter/dependencies_default.h +++ b/vendor/blaze/src/alterschema/linter/dependencies_default.h @@ -19,7 +19,7 @@ class DependenciesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, diff --git a/vendor/core/src/extension/alterschema/linter/dependent_required_default.h b/vendor/blaze/src/alterschema/linter/dependent_required_default.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/dependent_required_default.h rename to vendor/blaze/src/alterschema/linter/dependent_required_default.h index f108dc3..5fdcd11 100644 --- a/vendor/core/src/extension/alterschema/linter/dependent_required_default.h +++ b/vendor/blaze/src/alterschema/linter/dependent_required_default.h @@ -16,7 +16,7 @@ class DependentRequiredDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/linter/description_trailing_period.h b/vendor/blaze/src/alterschema/linter/description_trailing_period.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/description_trailing_period.h rename to vendor/blaze/src/alterschema/linter/description_trailing_period.h index 31e6bc4..64dbcdb 100644 --- a/vendor/core/src/extension/alterschema/linter/description_trailing_period.h +++ b/vendor/blaze/src/alterschema/linter/description_trailing_period.h @@ -16,7 +16,7 @@ class DescriptionTrailingPeriod final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/description_trim.h b/vendor/blaze/src/alterschema/linter/description_trim.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/description_trim.h rename to vendor/blaze/src/alterschema/linter/description_trim.h index 7b17442..a3ab176 100644 --- a/vendor/core/src/extension/alterschema/linter/description_trim.h +++ b/vendor/blaze/src/alterschema/linter/description_trim.h @@ -16,7 +16,7 @@ class DescriptionTrim final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/duplicate_examples.h b/vendor/blaze/src/alterschema/linter/duplicate_examples.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/duplicate_examples.h rename to vendor/blaze/src/alterschema/linter/duplicate_examples.h index 8471bd3..fadda50 100644 --- a/vendor/core/src/extension/alterschema/linter/duplicate_examples.h +++ b/vendor/blaze/src/alterschema/linter/duplicate_examples.h @@ -15,7 +15,7 @@ class DuplicateExamples final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/common/else_empty.h b/vendor/blaze/src/alterschema/linter/else_empty.h similarity index 100% rename from vendor/core/src/extension/alterschema/common/else_empty.h rename to vendor/blaze/src/alterschema/linter/else_empty.h diff --git a/vendor/core/src/extension/alterschema/linter/enum_to_const.h b/vendor/blaze/src/alterschema/linter/enum_to_const.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/enum_to_const.h rename to vendor/blaze/src/alterschema/linter/enum_to_const.h index 7e1ffb2..e147622 100644 --- a/vendor/core/src/extension/alterschema/linter/enum_to_const.h +++ b/vendor/blaze/src/alterschema/linter/enum_to_const.h @@ -15,7 +15,7 @@ class EnumToConst final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_const.h b/vendor/blaze/src/alterschema/linter/equal_numeric_bounds_to_const.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_const.h rename to vendor/blaze/src/alterschema/linter/equal_numeric_bounds_to_const.h index 603d569..9b45a5d 100644 --- a/vendor/core/src/extension/alterschema/linter/equal_numeric_bounds_to_const.h +++ b/vendor/blaze/src/alterschema/linter/equal_numeric_bounds_to_const.h @@ -16,7 +16,7 @@ class EqualNumericBoundsToConst final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any({ Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/linter/forbid_empty_enum.h b/vendor/blaze/src/alterschema/linter/forbid_empty_enum.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/forbid_empty_enum.h rename to vendor/blaze/src/alterschema/linter/forbid_empty_enum.h index 279d20f..44b3155 100644 --- a/vendor/core/src/extension/alterschema/linter/forbid_empty_enum.h +++ b/vendor/blaze/src/alterschema/linter/forbid_empty_enum.h @@ -15,7 +15,7 @@ class ForbidEmptyEnum final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/blaze/src/alterschema/linter/incoherent_min_max_contains.h b/vendor/blaze/src/alterschema/linter/incoherent_min_max_contains.h new file mode 100644 index 0000000..9c89155 --- /dev/null +++ b/vendor/blaze/src/alterschema/linter/incoherent_min_max_contains.h @@ -0,0 +1,33 @@ +class IncoherentMinMaxContains final : public SchemaTransformRule { +public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; + IncoherentMinMaxContains() + : SchemaTransformRule{ + "incoherent_min_max_contains", + "`minContains` greater than `maxContains` makes the schema " + "unsatisfiable"} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Validation, + Vocabularies::Known::JSON_Schema_2019_09_Validation}) && + schema.is_object() && schema.defines("contains") && + schema.defines("minContains") && + schema.at("minContains").is_integer() && + schema.defines("maxContains") && + schema.at("maxContains").is_integer() && + schema.at("minContains").to_integer() > + schema.at("maxContains").to_integer()); + return APPLIES_TO_KEYWORDS("minContains", "maxContains"); + } +}; diff --git a/vendor/core/src/extension/alterschema/linter/invalid_external_ref.h b/vendor/blaze/src/alterschema/linter/invalid_external_ref.h similarity index 100% rename from vendor/core/src/extension/alterschema/linter/invalid_external_ref.h rename to vendor/blaze/src/alterschema/linter/invalid_external_ref.h diff --git a/vendor/core/src/extension/alterschema/linter/items_array_default.h b/vendor/blaze/src/alterschema/linter/items_array_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/items_array_default.h rename to vendor/blaze/src/alterschema/linter/items_array_default.h index 0afbc43..3c4fbf5 100644 --- a/vendor/core/src/extension/alterschema/linter/items_array_default.h +++ b/vendor/blaze/src/alterschema/linter/items_array_default.h @@ -15,7 +15,7 @@ class ItemsArrayDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2019_09_Applicator, Vocabularies::Known::JSON_Schema_Draft_7, diff --git a/vendor/core/src/extension/alterschema/linter/items_schema_default.h b/vendor/blaze/src/alterschema/linter/items_schema_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/items_schema_default.h rename to vendor/blaze/src/alterschema/linter/items_schema_default.h index aee24af..22be12b 100644 --- a/vendor/core/src/extension/alterschema/linter/items_schema_default.h +++ b/vendor/blaze/src/alterschema/linter/items_schema_default.h @@ -18,7 +18,7 @@ class ItemsSchemaDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, diff --git a/vendor/core/src/extension/alterschema/linter/multiple_of_default.h b/vendor/blaze/src/alterschema/linter/multiple_of_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/multiple_of_default.h rename to vendor/blaze/src/alterschema/linter/multiple_of_default.h index 6915657..2d12384 100644 --- a/vendor/core/src/extension/alterschema/linter/multiple_of_default.h +++ b/vendor/blaze/src/alterschema/linter/multiple_of_default.h @@ -15,7 +15,7 @@ class MultipleOfDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, diff --git a/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h b/vendor/blaze/src/alterschema/linter/pattern_properties_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/pattern_properties_default.h rename to vendor/blaze/src/alterschema/linter/pattern_properties_default.h index 6decf0e..2e6fb13 100644 --- a/vendor/core/src/extension/alterschema/linter/pattern_properties_default.h +++ b/vendor/blaze/src/alterschema/linter/pattern_properties_default.h @@ -16,7 +16,7 @@ class PatternPropertiesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/linter/properties_default.h b/vendor/blaze/src/alterschema/linter/properties_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/properties_default.h rename to vendor/blaze/src/alterschema/linter/properties_default.h index dd43363..5e84894 100644 --- a/vendor/core/src/extension/alterschema/linter/properties_default.h +++ b/vendor/blaze/src/alterschema/linter/properties_default.h @@ -16,7 +16,7 @@ class PropertiesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/linter/property_names_default.h b/vendor/blaze/src/alterschema/linter/property_names_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/property_names_default.h rename to vendor/blaze/src/alterschema/linter/property_names_default.h index 479ca09..cbc9635 100644 --- a/vendor/core/src/extension/alterschema/linter/property_names_default.h +++ b/vendor/blaze/src/alterschema/linter/property_names_default.h @@ -19,7 +19,7 @@ class PropertyNamesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/linter/property_names_type_default.h b/vendor/blaze/src/alterschema/linter/property_names_type_default.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/property_names_type_default.h rename to vendor/blaze/src/alterschema/linter/property_names_type_default.h index 4ced15c..46626e2 100644 --- a/vendor/core/src/extension/alterschema/linter/property_names_type_default.h +++ b/vendor/blaze/src/alterschema/linter/property_names_type_default.h @@ -16,7 +16,7 @@ class PropertyNamesTypeDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, diff --git a/vendor/core/src/extension/alterschema/linter/simple_properties_identifiers.h b/vendor/blaze/src/alterschema/linter/simple_properties_identifiers.h similarity index 97% rename from vendor/core/src/extension/alterschema/linter/simple_properties_identifiers.h rename to vendor/blaze/src/alterschema/linter/simple_properties_identifiers.h index 7f4f30d..35661f1 100644 --- a/vendor/core/src/extension/alterschema/linter/simple_properties_identifiers.h +++ b/vendor/blaze/src/alterschema/linter/simple_properties_identifiers.h @@ -18,7 +18,7 @@ class SimplePropertiesIdentifiers final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator, diff --git a/vendor/core/src/extension/alterschema/common/then_empty.h b/vendor/blaze/src/alterschema/linter/then_empty.h similarity index 100% rename from vendor/core/src/extension/alterschema/common/then_empty.h rename to vendor/blaze/src/alterschema/linter/then_empty.h diff --git a/vendor/core/src/extension/alterschema/linter/title_description_equal.h b/vendor/blaze/src/alterschema/linter/title_description_equal.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/title_description_equal.h rename to vendor/blaze/src/alterschema/linter/title_description_equal.h index 01320ba..7969909 100644 --- a/vendor/core/src/extension/alterschema/linter/title_description_equal.h +++ b/vendor/blaze/src/alterschema/linter/title_description_equal.h @@ -16,7 +16,7 @@ class TitleDescriptionEqual final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/title_trailing_period.h b/vendor/blaze/src/alterschema/linter/title_trailing_period.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/title_trailing_period.h rename to vendor/blaze/src/alterschema/linter/title_trailing_period.h index bf7b9ad..14d9326 100644 --- a/vendor/core/src/extension/alterschema/linter/title_trailing_period.h +++ b/vendor/blaze/src/alterschema/linter/title_trailing_period.h @@ -16,7 +16,7 @@ class TitleTrailingPeriod final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/title_trim.h b/vendor/blaze/src/alterschema/linter/title_trim.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/title_trim.h rename to vendor/blaze/src/alterschema/linter/title_trim.h index 3846fc3..40d9e09 100644 --- a/vendor/core/src/extension/alterschema/linter/title_trim.h +++ b/vendor/blaze/src/alterschema/linter/title_trim.h @@ -15,7 +15,7 @@ class TitleTrim final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/top_level_description.h b/vendor/blaze/src/alterschema/linter/top_level_description.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/top_level_description.h rename to vendor/blaze/src/alterschema/linter/top_level_description.h index d1da356..d26d076 100644 --- a/vendor/core/src/extension/alterschema/linter/top_level_description.h +++ b/vendor/blaze/src/alterschema/linter/top_level_description.h @@ -16,7 +16,7 @@ class TopLevelDescription final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(location.pointer.empty()); ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/top_level_examples.h b/vendor/blaze/src/alterschema/linter/top_level_examples.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/top_level_examples.h rename to vendor/blaze/src/alterschema/linter/top_level_examples.h index c92c407..f4ae68b 100644 --- a/vendor/core/src/extension/alterschema/linter/top_level_examples.h +++ b/vendor/blaze/src/alterschema/linter/top_level_examples.h @@ -16,7 +16,7 @@ class TopLevelExamples final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(location.pointer.empty()); ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/top_level_title.h b/vendor/blaze/src/alterschema/linter/top_level_title.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/top_level_title.h rename to vendor/blaze/src/alterschema/linter/top_level_title.h index cc7fa2c..c6360da 100644 --- a/vendor/core/src/extension/alterschema/linter/top_level_title.h +++ b/vendor/blaze/src/alterschema/linter/top_level_title.h @@ -16,7 +16,7 @@ class TopLevelTitle final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(location.pointer.empty()); ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Meta_Data, diff --git a/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h b/vendor/blaze/src/alterschema/linter/unevaluated_items_default.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h rename to vendor/blaze/src/alterschema/linter/unevaluated_items_default.h index 6ce4570..9d24ef1 100644 --- a/vendor/core/src/extension/alterschema/linter/unevaluated_items_default.h +++ b/vendor/blaze/src/alterschema/linter/unevaluated_items_default.h @@ -19,7 +19,7 @@ class UnevaluatedItemsDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, diff --git a/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h b/vendor/blaze/src/alterschema/linter/unevaluated_properties_default.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h rename to vendor/blaze/src/alterschema/linter/unevaluated_properties_default.h index b7fff5b..6b7ade0 100644 --- a/vendor/core/src/extension/alterschema/linter/unevaluated_properties_default.h +++ b/vendor/blaze/src/alterschema/linter/unevaluated_properties_default.h @@ -19,7 +19,7 @@ class UnevaluatedPropertiesDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &location, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, diff --git a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_modern.h b/vendor/blaze/src/alterschema/linter/unnecessary_allof_ref_wrapper_modern.h similarity index 97% rename from vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_modern.h rename to vendor/blaze/src/alterschema/linter/unnecessary_allof_ref_wrapper_modern.h index ade6bd7..2615b2d 100644 --- a/vendor/core/src/extension/alterschema/common/unnecessary_allof_ref_wrapper_modern.h +++ b/vendor/blaze/src/alterschema/linter/unnecessary_allof_ref_wrapper_modern.h @@ -15,7 +15,7 @@ class UnnecessaryAllOfRefWrapperModern final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator})); diff --git a/vendor/core/src/extension/alterschema/common/unnecessary_allof_wrapper.h b/vendor/blaze/src/alterschema/linter/unnecessary_allof_wrapper.h similarity index 85% rename from vendor/core/src/extension/alterschema/common/unnecessary_allof_wrapper.h rename to vendor/blaze/src/alterschema/linter/unnecessary_allof_wrapper.h index 33c78e0..8762db1 100644 --- a/vendor/core/src/extension/alterschema/common/unnecessary_allof_wrapper.h +++ b/vendor/blaze/src/alterschema/linter/unnecessary_allof_wrapper.h @@ -27,6 +27,14 @@ class UnnecessaryAllOfWrapper final : public SchemaTransformRule { std::unordered_set dependency_blocked; for (const auto &entry : schema.as_object()) { + if ((entry.first == "unevaluatedProperties" || + entry.first == "unevaluatedItems") && + vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator})) { + continue; + } + for (const auto &dependency : walker(entry.first, vocabularies).dependencies) { dependency_blocked.emplace(dependency); @@ -70,6 +78,14 @@ class UnnecessaryAllOfWrapper final : public SchemaTransformRule { continue; } + if (vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}) && + (entry.defines("unevaluatedProperties") || + entry.defines("unevaluatedItems"))) { + continue; + } + for (const auto &keyword_entry : entry.as_object()) { const auto &keyword{keyword_entry.first}; const auto &metadata{walker(keyword, vocabularies)}; @@ -101,9 +117,15 @@ class UnnecessaryAllOfWrapper final : public SchemaTransformRule { locations.push_back(Pointer{KEYWORD, index - 1, keyword}); elevated.emplace(keyword); - for (const auto &dependency : metadata.dependencies) { - if (!entry.defines(std::string{dependency})) { - dependency_blocked.emplace(dependency); + if (!(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, + Vocabularies::Known::JSON_Schema_2019_09_Applicator}) && + (keyword == "unevaluatedProperties" || + keyword == "unevaluatedItems"))) { + for (const auto &dependency : metadata.dependencies) { + if (!entry.defines(std::string{dependency})) { + dependency_blocked.emplace(dependency); + } } } } diff --git a/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h b/vendor/blaze/src/alterschema/linter/unsatisfiable_max_contains.h similarity index 95% rename from vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h rename to vendor/blaze/src/alterschema/linter/unsatisfiable_max_contains.h index 6ff7c0f..29522c9 100644 --- a/vendor/core/src/extension/alterschema/linter/unsatisfiable_max_contains.h +++ b/vendor/blaze/src/alterschema/linter/unsatisfiable_max_contains.h @@ -17,7 +17,7 @@ class UnsatisfiableMaxContains final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h b/vendor/blaze/src/alterschema/linter/unsatisfiable_min_properties.h similarity index 96% rename from vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h rename to vendor/blaze/src/alterschema/linter/unsatisfiable_min_properties.h index 30c72c7..4616ca8 100644 --- a/vendor/core/src/extension/alterschema/linter/unsatisfiable_min_properties.h +++ b/vendor/blaze/src/alterschema/linter/unsatisfiable_min_properties.h @@ -16,7 +16,7 @@ class UnsatisfiableMinProperties final : public SchemaTransformRule { const sourcemeta::core::SchemaFrame::Location &, const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const - -> sourcemeta::core::SchemaTransformRule::Result override { + -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF( vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, diff --git a/vendor/blaze/src/alterschema/linter/valid_default.h b/vendor/blaze/src/alterschema/linter/valid_default.h new file mode 100644 index 0000000..75af825 --- /dev/null +++ b/vendor/blaze/src/alterschema/linter/valid_default.h @@ -0,0 +1,115 @@ +class ValidDefault final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ValidDefault(Compiler compiler = default_schema_compiler) + : SchemaTransformRule{"valid_default", "Only set a `default` value that " + "validates against the schema"}, + compiler_{std::move(compiler)} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &root, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const + -> SchemaTransformRule::Result override { + using Known = Vocabularies::Known; + // Technically, the `default` keyword goes back to Draft 1, but Blaze + // only supports Draft 4 and later + ONLY_CONTINUE_IF( + vocabularies.contains_any( + {Known::JSON_Schema_2020_12_Meta_Data, + Known::JSON_Schema_2019_09_Meta_Data, Known::JSON_Schema_Draft_7, + Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_4}) && + schema.is_object() && schema.defines("default")); + + if (vocabularies.contains_any({Known::JSON_Schema_Draft_7, + Known::JSON_Schema_Draft_6, + Known::JSON_Schema_Draft_4})) { + ONLY_CONTINUE_IF(!schema.defines("$ref")); + } + + const auto &instance{schema.at("default")}; + + if (frame.standalone()) { + const auto base{frame.uri(location.pointer)}; + assert(base.has_value()); + Template schema_template; + try { + schema_template = compile(root, walker, resolver, this->compiler_, + frame, base.value().get(), Mode::Exhaustive); + } catch (...) { + return false; + } + SimpleOutput output{instance}; + Evaluator evaluator; + const auto result{ + evaluator.validate(schema_template, instance, std::ref(output))}; + if (result) { + return false; + } + + std::ostringstream message; + for (const auto &entry : output) { + message << entry.message << "\n"; + message << " at instance location \""; + sourcemeta::core::stringify(entry.instance_location, message); + message << "\"\n"; + message << " at evaluate path \""; + sourcemeta::core::stringify(entry.evaluate_path, message); + message << "\"\n"; + } + + return {{{"default"}}, std::move(message).str()}; + } + + const auto &root_base_dialect{ + frame.traverse(frame.root()).value_or(location).get().base_dialect}; + std::string_view default_id{location.base}; + if (!sourcemeta::core::identify(root, root_base_dialect).empty() || + default_id.empty()) { + default_id = ""; + } + + sourcemeta::core::WeakPointer base; + const auto subschema{ + sourcemeta::core::wrap(root, frame, location, resolver, base)}; + Template schema_template; + try { + schema_template = compile(subschema, walker, resolver, this->compiler_, + Mode::Exhaustive, location.dialect, default_id); + } catch (...) { + return false; + } + SimpleOutput output{instance, base}; + Evaluator evaluator; + const auto result{ + evaluator.validate(schema_template, instance, std::ref(output))}; + if (result) { + return false; + } + + std::ostringstream message; + for (const auto &entry : output) { + message << entry.message << "\n"; + message << " at instance location \""; + sourcemeta::core::stringify(entry.instance_location, message); + message << "\"\n"; + message << " at evaluate path \""; + sourcemeta::core::stringify(entry.evaluate_path, message); + message << "\"\n"; + } + + return {{{"default"}}, std::move(message).str()}; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("default"); + } + +private: + const Compiler compiler_; +}; diff --git a/vendor/blaze/src/alterschema/linter/valid_examples.h b/vendor/blaze/src/alterschema/linter/valid_examples.h new file mode 100644 index 0000000..a1e039e --- /dev/null +++ b/vendor/blaze/src/alterschema/linter/valid_examples.h @@ -0,0 +1,132 @@ +class ValidExamples final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + ValidExamples(Compiler compiler = default_schema_compiler) + : SchemaTransformRule{"valid_examples", + "Only include instances in the `examples` array " + "that validate against the schema"}, + compiler_{std::move(compiler)} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &root, + const sourcemeta::core::Vocabularies &vocabularies, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) const + -> SchemaTransformRule::Result override { + using Known = Vocabularies::Known; + ONLY_CONTINUE_IF( + vocabularies.contains_any({Known::JSON_Schema_2020_12_Meta_Data, + Known::JSON_Schema_2019_09_Meta_Data, + Known::JSON_Schema_Draft_7, + Known::JSON_Schema_Draft_6}) && + schema.is_object() && schema.defines("examples") && + schema.at("examples").is_array() && !schema.at("examples").empty()); + + if (vocabularies.contains_any({Known::JSON_Schema_Draft_7, + Known::JSON_Schema_Draft_6, + Known::JSON_Schema_Draft_4})) { + ONLY_CONTINUE_IF(!schema.defines("$ref")); + } + + // TODO(C++23): Use std::views::enumerate when available in libc++ + std::size_t cursor{0}; + + if (frame.standalone()) { + const auto base{frame.uri(location.pointer)}; + assert(base.has_value()); + Template schema_template; + try { + schema_template = compile(root, walker, resolver, this->compiler_, + frame, base.value().get(), Mode::Exhaustive); + } catch (...) { + return false; + } + + for (const auto &example : schema.at("examples").as_array()) { + SimpleOutput output{example}; + Evaluator evaluator; + const auto result{ + evaluator.validate(schema_template, example, std::ref(output))}; + if (!result) { + std::ostringstream message; + message << "Invalid example instance at index " << cursor << "\n"; + for (const auto &entry : output) { + message << " " << entry.message << "\n"; + message << " " + << " at instance location \""; + sourcemeta::core::stringify(entry.instance_location, message); + message << "\"\n"; + message << " " + << " at evaluate path \""; + sourcemeta::core::stringify(entry.evaluate_path, message); + message << "\"\n"; + } + + return {{{"examples", cursor}}, std::move(message).str()}; + } + + cursor += 1; + } + + return false; + } + + const auto &root_base_dialect{ + frame.traverse(frame.root()).value_or(location).get().base_dialect}; + std::string_view default_id{location.base}; + if (!sourcemeta::core::identify(root, root_base_dialect).empty() || + default_id.empty()) { + default_id = ""; + } + + sourcemeta::core::WeakPointer base; + const auto subschema{ + sourcemeta::core::wrap(root, frame, location, resolver, base)}; + Template schema_template; + try { + schema_template = compile(subschema, walker, resolver, this->compiler_, + Mode::Exhaustive, location.dialect, default_id); + } catch (...) { + return false; + } + + for (const auto &example : schema.at("examples").as_array()) { + SimpleOutput output{example, base}; + Evaluator evaluator; + const auto result{ + evaluator.validate(schema_template, example, std::ref(output))}; + if (!result) { + std::ostringstream message; + message << "Invalid example instance at index " << cursor << "\n"; + for (const auto &entry : output) { + message << " " << entry.message << "\n"; + message << " " + << " at instance location \""; + sourcemeta::core::stringify(entry.instance_location, message); + message << "\"\n"; + message << " " + << " at evaluate path \""; + sourcemeta::core::stringify(entry.evaluate_path, message); + message << "\"\n"; + } + + return {{{"examples", cursor}}, std::move(message).str()}; + } + + cursor += 1; + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("examples"); + } + +private: + const Compiler compiler_; +}; diff --git a/vendor/blaze/src/alterschema/schema_rule.cc b/vendor/blaze/src/alterschema/schema_rule.cc new file mode 100644 index 0000000..6c71c38 --- /dev/null +++ b/vendor/blaze/src/alterschema/schema_rule.cc @@ -0,0 +1,105 @@ +#include +#include +#include +#include +#include + +#include + +#include // assert +#include // std::ref +#include // std::ostringstream +#include // std::string +#include // std::string_view +#include // std::move + +namespace sourcemeta::blaze { + +static constexpr std::string_view NAME_PATTERN{"^[a-z0-9_/]+$"}; + +static auto validate_name(const std::string_view name) -> void { + static const auto pattern{ + sourcemeta::core::to_regex(std::string{NAME_PATTERN})}; + assert(pattern.has_value()); + if (name.empty()) [[unlikely]] { + throw SchemaRuleInvalidNameError(name, + "The schema rule name must not be empty"); + } + + if (!sourcemeta::core::matches(pattern.value(), std::string{name})) + [[unlikely]] { + throw SchemaRuleInvalidNamePatternError(name, NAME_PATTERN); + } +} + +static auto extract_description(const sourcemeta::core::JSON &schema) + -> std::string { + if (!schema.defines("description")) { + return ""; + } + + if (schema.at("description").is_string()) { + return schema.at("description").to_string(); + } + + std::ostringstream result; + sourcemeta::core::stringify(schema.at("description"), result); + return std::move(result).str(); +} + +static auto extract_title(const sourcemeta::core::JSON &schema) -> std::string { + if (!schema.defines("title")) [[unlikely]] { + throw SchemaRuleMissingNameError{}; + } + + if (!schema.at("title").is_string()) [[unlikely]] { + std::ostringstream result; + sourcemeta::core::stringify(schema.at("title"), result); + throw SchemaRuleInvalidNameError(std::move(result).str(), + "The schema rule title is not a string"); + } + + auto title{schema.at("title").to_string()}; + validate_name(title); + return title; +} + +SchemaRule::SchemaRule(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const Compiler &compiler, + const std::string_view default_dialect, + const std::optional &tweaks) + : SchemaTransformRule{extract_title(schema), extract_description(schema)}, + template_{compile(schema, walker, resolver, compiler, Mode::Exhaustive, + default_dialect, "", "", tweaks)} {}; + +auto SchemaRule::condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result { + SimpleOutput output{schema}; + Evaluator evaluator; + const auto result{ + evaluator.validate(this->template_, schema, std::ref(output))}; + if (result) { + return false; + } + + if (output.cbegin() != output.cend()) { + if (output.cbegin()->instance_location.empty()) { + return {{}, std::string{output.cbegin()->message}}; + } + + return {{sourcemeta::core::to_pointer(output.cbegin()->instance_location)}, + std::string{output.cbegin()->message}}; + } else { + return true; + } +} + +} // namespace sourcemeta::blaze diff --git a/vendor/core/src/core/jsonschema/transformer.cc b/vendor/blaze/src/alterschema/transformer.cc similarity index 67% rename from vendor/core/src/core/jsonschema/transformer.cc rename to vendor/blaze/src/alterschema/transformer.cc index e7ba51f..f891e37 100644 --- a/vendor/core/src/core/jsonschema/transformer.cc +++ b/vendor/blaze/src/alterschema/transformer.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -40,11 +41,11 @@ auto check_rules( const sourcemeta::core::JSON &schema, const sourcemeta::core::SchemaFrame &frame, const std::vector, bool, bool>> + std::unique_ptr, bool, bool>> &rules, const sourcemeta::core::SchemaWalker &walker, const sourcemeta::core::SchemaResolver &resolver, - const sourcemeta::core::SchemaTransformer::Callback &callback, + const sourcemeta::blaze::SchemaTransformer::Callback &callback, const sourcemeta::core::JSON::String &exclude_keyword, const bool non_mutating_only) -> std::pair { std::unordered_set void { - - // If we use the default id when there is already one, framing will duplicate - // the locations leading to duplicate check reports if (!sourcemeta::core::identify(schema, resolver, default_dialect).empty()) { frame.analyse(schema, walker, resolver, default_dialect); } else { @@ -120,7 +116,7 @@ auto analyse_frame(sourcemeta::core::SchemaFrame &frame, } // namespace -namespace sourcemeta::core { +namespace sourcemeta::blaze { SchemaTransformRule::SchemaTransformRule(const std::string_view name, const std::string_view message) @@ -139,26 +135,32 @@ auto SchemaTransformRule::message() const noexcept -> std::string_view { return this->message_; } -auto SchemaTransformRule::transform(JSON &, const Result &) const -> void { +auto SchemaTransformRule::transform(core::JSON &, const Result &) const + -> void { throw SchemaAbortError("This rule cannot be automatically transformed"); } auto SchemaTransformRule::rereference(const std::string_view reference, - const Pointer &origin, const Pointer &, - const Pointer &) const -> Pointer { + const core::Pointer &origin, + const core::Pointer &, + const core::Pointer &) const + -> core::Pointer { throw SchemaBrokenReferenceError(reference, origin, "The reference broke after transformation"); } -auto SchemaTransformRule::check( - const JSON &schema, const JSON &root, const Vocabularies &vocabularies, - const SchemaWalker &walker, const SchemaResolver &resolver, - const SchemaFrame &frame, const SchemaFrame::Location &location, - const JSON::String &exclude_keyword) const -> SchemaTransformRule::Result { +auto SchemaTransformRule::check(const core::JSON &schema, + const core::JSON &root, + const core::Vocabularies &vocabularies, + const core::SchemaWalker &walker, + const core::SchemaResolver &resolver, + const core::SchemaFrame &frame, + const core::SchemaFrame::Location &location, + const core::JSON::String &exclude_keyword) const + -> SchemaTransformRule::Result { auto result{this->condition(schema, root, vocabularies, frame, location, walker, resolver)}; - // Support rule exclusion if (result.applies && !exclude_keyword.empty() && schema.is_object()) { const auto *exclude_value{schema.try_at(exclude_keyword)}; if (exclude_value != nullptr && @@ -173,38 +175,42 @@ auto SchemaTransformRule::check( return result; } -auto SchemaTransformer::check(const JSON &schema, const SchemaWalker &walker, - const SchemaResolver &resolver, +auto SchemaTransformer::check(const core::JSON &schema, + const core::SchemaWalker &walker, + const core::SchemaResolver &resolver, const SchemaTransformer::Callback &callback, std::string_view default_dialect, std::string_view default_id, - const JSON::String &exclude_keyword) const + const core::JSON::String &exclude_keyword) const -> std::pair { - SchemaFrame frame{SchemaFrame::Mode::References}; + core::SchemaFrame frame{core::SchemaFrame::Mode::References}; analyse_frame(frame, schema, walker, resolver, default_dialect, default_id); return check_rules(schema, frame, this->rules, walker, resolver, callback, exclude_keyword, false); } -auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, - const SchemaResolver &resolver, +auto SchemaTransformer::apply(core::JSON &schema, + const core::SchemaWalker &walker, + const core::SchemaResolver &resolver, const SchemaTransformer::Callback &callback, std::string_view default_dialect, std::string_view default_id, - const JSON::String &exclude_keyword) const + const core::JSON::String &exclude_keyword) const -> std::pair { assert(!this->rules.empty()); - std::unordered_set, - ProcessedRuleHasher> + std::unordered_set< + std::tuple, + ProcessedRuleHasher> processed_rules; - SchemaFrame frame{SchemaFrame::Mode::References}; + core::SchemaFrame frame{core::SchemaFrame::Mode::References}; struct PotentiallyBrokenReference { - Pointer origin; - JSON::String original; - JSON::String destination; - Pointer target_pointer; + core::Pointer origin; + core::JSON::String original; + core::JSON::String destination; + core::JSON::String fragment; + core::Pointer target_pointer; std::size_t target_relative_pointer; }; @@ -212,34 +218,34 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, while (true) { if (frame.empty()) { + if (schema.is_boolean()) { + break; + } + analyse_frame(frame, schema, walker, resolver, default_dialect, default_id); } - std::unordered_set visited; + std::unordered_set visited; bool applied{false}; for (const auto &entry : frame.locations()) { - if (entry.second.type != SchemaFrame::LocationType::Resource && - entry.second.type != SchemaFrame::LocationType::Subschema) { + if (entry.second.type != core::SchemaFrame::LocationType::Resource && + entry.second.type != core::SchemaFrame::LocationType::Subschema) { continue; } - // Framing may report resource twice or more given default identifiers and - // nested resources, risking reporting the same errors twice const auto [visited_iterator, inserted] = - visited.insert(to_pointer(entry.second.pointer)); + visited.insert(core::to_pointer(entry.second.pointer)); if (!inserted) { continue; } const auto &entry_pointer{*visited_iterator}; - auto ¤t{get(schema, entry_pointer)}; + auto ¤t{core::get(schema, entry_pointer)}; const auto current_vocabularies{ frame.vocabularies(entry.second, resolver)}; for (const auto &[rule, mutates, reframe_after_transform] : this->rules) { - // Only process mutating rules in the main loop. - // Non-mutating rules will be processed once at the end. if (!mutates) { continue; } @@ -252,10 +258,6 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, continue; } - // Collect reference information BEFORE invalidating the frame. - // We need to save this data because after the transform, the old - // frame's views may point to invalid memory, and a new frame won't - // have location entries for paths that no longer exist. potentially_broken_references.clear(); for (const auto &reference : frame.references()) { const auto destination{frame.traverse(reference.second.destination)}; @@ -267,10 +269,11 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, const auto &target{destination.value().get()}; potentially_broken_references.push_back( - {to_pointer(reference.first.second), - JSON::String{reference.second.original}, - reference.second.destination, to_pointer(target.pointer), - target.relative_pointer}); + {core::to_pointer(reference.first.second), + core::JSON::String{reference.second.original}, + reference.second.destination, + core::JSON::String{reference.second.fragment.value()}, + core::to_pointer(target.pointer), target.relative_pointer}); } rule->transform(current, outcome); @@ -281,18 +284,26 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, if (reframe_after_transform) { analyse_frame(frame, schema, walker, resolver, default_dialect, default_id); + } else if (current.is_boolean()) { + std::tuple mark{ + ¤t, rule->name(), current.fast_hash()}; + if (processed_rules.contains(mark)) { + throw SchemaTransformRuleProcessedTwiceError(rule->name(), + entry_pointer); + } + + processed_rules.emplace(std::move(mark)); + frame.reset(); + goto blaze_transformer_start_again; } - const auto new_location{frame.traverse(to_weak_pointer(entry_pointer))}; - // The location should still exist after transform + const auto new_location{ + frame.traverse(core::to_weak_pointer(entry_pointer))}; assert(new_location.has_value()); - // Get vocabularies from the new frame const auto new_vocabularies{ frame.vocabularies(new_location.value().get(), resolver)}; - // The condition must always be false after applying the - // transformation in order to avoid infinite loops if (rule->check(current, schema, new_vocabularies, walker, resolver, frame, new_location.value().get(), exclude_keyword) .applies) { @@ -301,41 +312,39 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, throw std::runtime_error(error.str()); } - // Identify and fix broken references using the saved data bool references_fixed{false}; for (const auto &saved_reference : potentially_broken_references) { - // The destination still exists, so we don't have to do anything - if (try_get(schema, saved_reference.target_pointer)) { + if (core::try_get(schema, saved_reference.target_pointer)) { continue; } - // If the source no longer exists, we don't need to fix the reference - if (!try_get(schema, saved_reference.origin.initial())) { + if (!core::try_get(schema, saved_reference.origin.initial())) { continue; } - const auto new_fragment{rule->rereference( + const auto new_relative{rule->rereference( saved_reference.destination, saved_reference.origin, saved_reference.target_pointer.slice( saved_reference.target_relative_pointer), entry_pointer.slice( new_location.value().get().relative_pointer))}; - - // Note we use the base from the original reference before any - // canonicalisation takes place so that we don't overly change - // user's references when only fixing up their pointer fragments - URI original{saved_reference.original}; - original.fragment(to_string(new_fragment)); - set(schema, saved_reference.origin, JSON{original.recompose()}); + const auto new_fragment{ + saved_reference.fragment == + core::to_string(saved_reference.target_pointer) + ? saved_reference.target_pointer + .slice(0, saved_reference.target_relative_pointer) + .concat(new_relative) + : new_relative}; + + core::URI original{saved_reference.original}; + original.fragment(core::to_string(new_fragment)); + core::set(schema, saved_reference.origin, + core::JSON{original.recompose()}); references_fixed = true; } - std::tuple mark{ - ¤t, rule->name(), - // Allow applying the same rule to the same location if the schema - // has changed, which means we are still "making progress". The - // hashing is not perfect, but its enough - current.fast_hash()}; + std::tuple mark{ + ¤t, rule->name(), current.fast_hash()}; if (processed_rules.contains(mark)) { throw SchemaTransformRuleProcessedTwiceError(rule->name(), entry_pointer); @@ -343,29 +352,30 @@ auto SchemaTransformer::apply(JSON &schema, const SchemaWalker &walker, processed_rules.emplace(std::move(mark)); - // If we fixed references, the schema changed again, so we need to - // invalidate the frame. Otherwise, we can reuse it for the next - // iteration. if (references_fixed) { frame.reset(); } if (references_fixed || reframe_after_transform) { - goto core_transformer_start_again; + goto blaze_transformer_start_again; } } } - core_transformer_start_again: + blaze_transformer_start_again: if (!applied) { break; } } - if (frame.empty()) { + if (frame.empty() && !schema.is_boolean()) { analyse_frame(frame, schema, walker, resolver, default_dialect, default_id); } + if (frame.empty()) { + return {true, static_cast(100)}; + } + return check_rules(schema, frame, this->rules, walker, resolver, callback, exclude_keyword, true); } @@ -376,4 +386,4 @@ auto SchemaTransformer::remove(const std::string_view name) -> bool { }) > 0; } -} // namespace sourcemeta::core +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/compiler/CMakeLists.txt b/vendor/blaze/src/compiler/CMakeLists.txt new file mode 100644 index 0000000..a95786c --- /dev/null +++ b/vendor/blaze/src/compiler/CMakeLists.txt @@ -0,0 +1,30 @@ +sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME compiler + FOLDER "Blaze/Compiler" + PRIVATE_HEADERS error.h unevaluated.h + SOURCES + compile.cc compile_json.cc + compile_helpers.h postprocess.h + default_compiler.cc unevaluated.cc + default_compiler_2020_12.h + default_compiler_2019_09.h + default_compiler_draft7.h + default_compiler_draft6.h + default_compiler_draft4.h + default_compiler_openapi.h) + +if(BLAZE_INSTALL) + sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME compiler) +endif() + +target_link_libraries(sourcemeta_blaze_compiler PUBLIC + sourcemeta::core::json) +target_link_libraries(sourcemeta_blaze_compiler PRIVATE + sourcemeta::core::regex) +target_link_libraries(sourcemeta_blaze_compiler PUBLIC + sourcemeta::core::jsonpointer) +target_link_libraries(sourcemeta_blaze_compiler PRIVATE + sourcemeta::core::uri) +target_link_libraries(sourcemeta_blaze_compiler PUBLIC + sourcemeta::core::jsonschema) +target_link_libraries(sourcemeta_blaze_compiler PUBLIC + sourcemeta::blaze::evaluator) diff --git a/vendor/blaze/src/compiler/compile.cc b/vendor/blaze/src/compiler/compile.cc new file mode 100644 index 0000000..65b1fcd --- /dev/null +++ b/vendor/blaze/src/compiler/compile.cc @@ -0,0 +1,506 @@ +#include +#include + +#include + +#include // std::move, std::sort, std::unique +#include // assert +// TODO(C++23): Consider std::flat_map/std::flat_set when available in libc++ +#include // std::map +#include // std::set +#include // std::string_view +#include // std::unordered_map +#include // std::move, std::pair +#include // std::vector + +#include "compile_helpers.h" +#include "postprocess.h" + +namespace { + +auto compile_subschema(const sourcemeta::blaze::Context &context, + const sourcemeta::blaze::SchemaContext &schema_context, + const sourcemeta::blaze::DynamicContext &dynamic_context, + const std::string_view default_dialect) + -> sourcemeta::blaze::Instructions { + using namespace sourcemeta::blaze; + assert(is_schema(schema_context.schema)); + + // Handle boolean schemas earlier on, as nobody should be able to + // override what these mean. + if (schema_context.schema.is_boolean()) { + if (schema_context.schema.to_boolean()) { + return {}; + } else { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionFail, context, + schema_context, + {.keyword = KEYWORD_EMPTY, + .base_schema_location = dynamic_context.base_schema_location, + .base_instance_location = dynamic_context.base_instance_location}, + ValueNone{})}; + } + } + + Instructions steps; + for (const auto &entry : sourcemeta::core::SchemaKeywordIterator{ + schema_context.schema, context.walker, context.resolver, + default_dialect}) { + assert(entry.pointer.back().is_property()); + const auto &keyword{entry.pointer.back().to_property()}; + // Bases must not contain fragments + assert(!schema_context.base.fragment().has_value()); + for (auto &&step : context.compiler( + context, + {.relative_pointer = schema_context.relative_pointer.concat( + make_weak_pointer(keyword)), + .schema = schema_context.schema, + .vocabularies = entry.vocabularies, + .base = schema_context.base, + .is_property_name = schema_context.is_property_name}, + {.keyword = keyword, + .base_schema_location = dynamic_context.base_schema_location, + .base_instance_location = dynamic_context.base_instance_location}, + steps)) { + // Just a sanity check to ensure every keyword location is indeed valid + assert(context.frame.locations().contains( + {sourcemeta::core::SchemaReferenceType::Static, + context.extra[step.extra_index].keyword_location})); + steps.push_back(std::move(step)); + } + } + + return steps; +} + +// TODO: Somehow move this logic up to `SchemaFrame` +auto schema_frame_populate_target_types( + const sourcemeta::core::SchemaFrame &frame, + std::unordered_map> &target_types) + -> void { + for (const auto &reference : frame.references()) { + if (!reference.first.second.empty() && + reference.first.second.back().is_property() && + reference.first.second.back().to_property() == "$schema") { + continue; + } + + const auto reference_location{frame.traverse(reference.first.second)}; + assert(reference_location.has_value()); + auto &context{target_types[reference.second.destination]}; + if (reference_location->get().property_name) { + context.first = true; + } else { + context.second = true; + } + } + + std::unordered_map + destination_pointers; + for (const auto &[destination, _] : target_types) { + const auto destination_location{frame.traverse(destination)}; + if (destination_location.has_value()) { + destination_pointers.emplace(destination, + &destination_location->get().pointer); + } + } + + std::unordered_map> + references_within; + for (const auto &reference : frame.references()) { + if (!reference.first.second.empty() && + reference.first.second.back().is_property() && + reference.first.second.back().to_property() == "$schema") { + continue; + } + + for (const auto &[destination, destination_pointer] : + destination_pointers) { + if (reference.first.second.starts_with(*destination_pointer) && + reference.first.second.size() > destination_pointer->size()) { + references_within[destination].push_back(reference.second.destination); + } + } + } + + bool changed{true}; + while (changed) { + changed = false; + for (const auto &[current_destination, context] : target_types) { + if (!context.first) { + continue; + } + + const auto iterator{references_within.find(current_destination)}; + if (iterator == references_within.end()) { + continue; + } + + for (const auto &referenced_destination : iterator->second) { + auto &next_context{target_types[referenced_destination]}; + if (!next_context.first) { + next_context.first = true; + changed = true; + } + } + } + } +} + +} // namespace + +namespace sourcemeta::blaze { + +auto compile(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const Compiler &compiler, + const sourcemeta::core::SchemaFrame &frame, + const std::string_view entrypoint, const Mode mode, + const std::optional &tweaks) -> Template { + assert(is_schema(schema)); + const auto effective_tweaks{tweaks.value_or(Tweaks{})}; + + const auto maybe_entrypoint_location{frame.traverse(entrypoint)}; + if (!maybe_entrypoint_location.has_value()) [[unlikely]] { + throw CompilerInvalidEntryPoint{ + entrypoint, "The given entry point URI does not exist in the schema"}; + } + + const auto &entrypoint_location{maybe_entrypoint_location->get()}; + if (entrypoint_location.type == + sourcemeta::core::SchemaFrame::LocationType::Pointer) [[unlikely]] { + throw CompilerInvalidEntryPoint{ + entrypoint, "The given entry point URI is not a valid subschema"}; + } + + /////////////////////////////////////////////////////////////////// + // (1) Determine all the schema resources in the schema + /////////////////////////////////////////////////////////////////// + + std::vector resources; + for (const auto &entry : frame.locations()) { + if (entry.second.type == + sourcemeta::core::SchemaFrame::LocationType::Resource) { + resources.push_back(entry.first.second); + } + } + + // Rule out any duplicates as we will use this list as the + // source for a perfect hash function on schema resources. + std::ranges::sort(resources); + auto [first, last] = std::ranges::unique(resources); + resources.erase(first, last); + assert(resources.size() == + std::set(resources.cbegin(), resources.cend()).size()); + + /////////////////////////////////////////////////////////////////// + // (2) Check if the schema relies on dynamic scopes + /////////////////////////////////////////////////////////////////// + + bool uses_dynamic_scopes{false}; + for (const auto &reference : frame.references()) { + // Check whether dynamic referencing takes places in this schema. If not, + // we can avoid the overhead of keeping track of dynamics scopes, etc + if (reference.first.first == + sourcemeta::core::SchemaReferenceType::Dynamic) { + uses_dynamic_scopes = true; + break; + } + } + + /////////////////////////////////////////////////////////////////// + // (3) Plan which static references we will precompile + /////////////////////////////////////////////////////////////////// + + std::unordered_map> target_types; + schema_frame_populate_target_types(frame, target_types); + + std::map< + std::tuple, + std::pair> + targets_map; + targets_map.emplace( + std::make_tuple(sourcemeta::core::SchemaReferenceType::Static, entrypoint, + false), + std::make_pair(0, nullptr)); + + for (const auto &reference : frame.references()) { + // Ignore meta-schema references + if (!reference.first.second.empty() && + reference.first.second.back().is_property() && + reference.first.second.back().to_property() == "$schema") { + continue; + } + + auto reference_origin{frame.traverse(reference.first.second)}; + assert(reference_origin.has_value()); + while (reference_origin->get().type == + sourcemeta::core::SchemaFrame::LocationType::Pointer && + reference_origin->get().parent.has_value()) { + reference_origin = frame.traverse(reference_origin->get().parent.value()); + assert(reference_origin.has_value()); + } + + // Skip unreachable targets + if (reference_origin->get().type != + sourcemeta::core::SchemaFrame::LocationType::Pointer && + !frame.is_reachable(entrypoint_location, reference_origin->get(), + walker, resolver)) { + continue; + } + + assert(target_types.contains(reference.second.destination)); + const auto &[needs_name, + needs_instance]{target_types.at(reference.second.destination)}; + + if (needs_name) { + targets_map.emplace( + std::make_tuple(reference.first.first, + std::string_view{reference.second.destination}, true), + std::make_pair(targets_map.size(), &reference.first.second)); + } + + if (needs_instance) { + targets_map.emplace( + std::make_tuple(reference.first.first, + std::string_view{reference.second.destination}, + false), + std::make_pair(targets_map.size(), &reference.first.second)); + } + } + + // Also add dynamic anchors that may not be directly referenced + // but could be used as override targets during dynamic resolution + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Anchor || + entry.first.first != sourcemeta::core::SchemaReferenceType::Dynamic) { + continue; + } + + // Skip unreachable dynamic anchors + if (!frame.is_reachable(entrypoint_location, entry.second, walker, + resolver)) { + continue; + } + + targets_map.emplace(std::make_tuple(entry.first.first, + std::string_view{entry.first.second}, + false), + std::make_pair(targets_map.size(), nullptr)); + } + + /////////////////////////////////////////////////////////////////// + // (4) Build the global compilation context + /////////////////////////////////////////////////////////////////// + + auto unevaluated{ + sourcemeta::blaze::unevaluated(schema, frame, walker, resolver)}; + + std::vector instruction_extra; + const Context context{.root = schema, + .frame = frame, + .resources = std::move(resources), + .walker = walker, + .resolver = resolver, + .compiler = compiler, + .mode = mode, + .uses_dynamic_scopes = uses_dynamic_scopes, + .unevaluated = std::move(unevaluated), + .tweaks = effective_tweaks, + .targets = std::move(targets_map), + .extra = instruction_extra}; + + /////////////////////////////////////////////////////////////////// + // (5) Build labels map for dynamic anchors + /////////////////////////////////////////////////////////////////// + + std::vector> labels_map; + if (uses_dynamic_scopes) { + for (const auto &entry : context.frame.locations()) { + // We are only trying to find dynamic anchors + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Anchor || + entry.first.first != sourcemeta::core::SchemaReferenceType::Dynamic) { + continue; + } + + // Skip unreachable dynamic anchors + if (!context.frame.is_reachable(entrypoint_location, entry.second, + context.walker, context.resolver)) { + continue; + } + + // Compute the hash for this dynamic anchor + const sourcemeta::core::URI anchor_uri{entry.first.second}; + const auto label{Evaluator::hash( + schema_resource_id( + context.resources, + anchor_uri.recompose_without_fragment().value_or("")), + anchor_uri.fragment().value_or(""))}; + + // Find the index in targets for this dynamic anchor + const auto key{ + std::make_tuple(sourcemeta::core::SchemaReferenceType::Dynamic, + std::string_view{entry.first.second}, false)}; + assert(context.targets.contains(key)); + const auto index{context.targets.at(key).first}; + assert(index < context.targets.size()); + + labels_map.emplace_back(label, index); + } + } + + /////////////////////////////////////////////////////////////////// + // (6) Compile targets for static references + /////////////////////////////////////////////////////////////////// + + std::vector compiled_targets; + compiled_targets.resize(context.targets.size()); + for (const auto &[destination, target_info] : context.targets) { + const auto &[reference_type, destination_uri, is_property_name] = + destination; + const auto &[index, reference_pointer] = target_info; + const auto location{context.frame.traverse(destination_uri)}; + assert(location.has_value()); + const auto &entry{location->get()}; + + if (entry.type != sourcemeta::core::SchemaFrame::LocationType::Subschema && + entry.type != sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.type != sourcemeta::core::SchemaFrame::LocationType::Anchor) + [[unlikely]] { + assert(reference_pointer != nullptr); + const auto parent_size{entry.parent ? entry.parent->size() : 0}; + throw CompilerReferenceTargetNotSchemaError( + destination_uri, + to_pointer(entry.pointer.slice( + 0, std::min(parent_size + 1, entry.pointer.size())))); + } + + auto subschema{sourcemeta::core::get(context.root, entry.pointer)}; + auto nested_vocabularies{sourcemeta::core::vocabularies( + subschema, context.resolver, entry.dialect)}; + const auto nested_relative_pointer{ + entry.pointer.slice(entry.relative_pointer)}; + const sourcemeta::core::URI nested_base{entry.base}; + + const SchemaContext schema_context{ + .relative_pointer = nested_relative_pointer, + .schema = std::move(subschema), + .vocabularies = std::move(nested_vocabularies), + .base = nested_base, + .is_property_name = is_property_name}; + + compiled_targets[index] = + compile(context, schema_context, relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer, destination_uri); + } + + /////////////////////////////////////////////////////////////////// + // (7) Postprocess compiled targets + /////////////////////////////////////////////////////////////////// + + if (mode == Mode::FastValidation) { + postprocess(compiled_targets, instruction_extra, effective_tweaks, + uses_dynamic_scopes); + } + + /////////////////////////////////////////////////////////////////// + // (8) Return final template + /////////////////////////////////////////////////////////////////// + + const bool track{ + context.mode != Mode::FastValidation || + requires_evaluation(context, entrypoint_location.pointer) || + // TODO: This expression should go away if we start properly compiling + // `unevaluatedItems` like we compile `unevaluatedProperties` + std::ranges::any_of(context.unevaluated, [](const auto &dependency) { + return dependency.first.ends_with("unevaluatedItems"); + })}; + return {.dynamic = uses_dynamic_scopes, + .track = track, + .targets = std::move(compiled_targets), + .labels = std::move(labels_map), + .extra = std::move(instruction_extra)}; +} + +auto compile(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const Compiler &compiler, const Mode mode, + const std::string_view default_dialect, + const std::string_view default_id, + const std::string_view entrypoint, + const std::optional &tweaks) -> Template { + assert(is_schema(schema)); + + // Make sure the input schema is bundled, otherwise we won't be able to + // resolve remote references here + const sourcemeta::core::JSON result{sourcemeta::core::bundle( + schema, walker, resolver, default_dialect, default_id)}; + + sourcemeta::core::SchemaFrame frame{ + sourcemeta::core::SchemaFrame::Mode::References}; + frame.analyse(result, walker, resolver, default_dialect, default_id); + return compile(result, walker, resolver, compiler, frame, + entrypoint.empty() ? frame.root() : entrypoint, mode, tweaks); +} + +auto compile(const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const sourcemeta::core::WeakPointer &schema_suffix, + const sourcemeta::core::WeakPointer &instance_suffix, + const std::optional uri) -> Instructions { + // Determine URI of the destination after recursion + const std::string destination{ + uri.has_value() + ? sourcemeta::core::URI::canonicalize(uri.value()) + : to_uri(schema_context.relative_pointer.concat(schema_suffix), + schema_context.base) + .canonicalize() + .recompose()}; + + // Otherwise the recursion attempt is non-sense + if (!context.frame.locations().contains( + {sourcemeta::core::SchemaReferenceType::Static, destination})) + [[unlikely]] { + throw sourcemeta::core::SchemaReferenceError( + destination, to_pointer(schema_context.relative_pointer), + "The target of the reference does not exist in the schema"); + } + + const auto &entry{context.frame.locations().at( + {sourcemeta::core::SchemaReferenceType::Static, destination})}; + const auto &new_schema{get(context.root, entry.pointer)}; + assert(is_schema(new_schema)); + + const sourcemeta::core::WeakPointer destination_pointer{ + dynamic_context.keyword.empty() + ? dynamic_context.base_schema_location.concat(schema_suffix) + : dynamic_context.base_schema_location + .concat(make_weak_pointer(dynamic_context.keyword)) + .concat(schema_suffix)}; + + const auto new_relative_pointer{entry.pointer.slice(entry.relative_pointer)}; + const sourcemeta::core::URI new_base{ + sourcemeta::core::URI{entry.base}.recompose_without_fragment().value_or( + "")}; + + return compile_subschema( + context, + {.relative_pointer = new_relative_pointer, + .schema = new_schema, + .vocabularies = + vocabularies(new_schema, context.resolver, entry.dialect), + .base = new_base, + .is_property_name = schema_context.is_property_name}, + {.keyword = dynamic_context.keyword, + .base_schema_location = destination_pointer, + .base_instance_location = + dynamic_context.base_instance_location.concat(instance_suffix)}, + entry.dialect); +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/compiler/compile_helpers.h b/vendor/blaze/src/compiler/compile_helpers.h new file mode 100644 index 0000000..6de7873 --- /dev/null +++ b/vendor/blaze/src/compiler/compile_helpers.h @@ -0,0 +1,360 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_COMPILE_HELPERS_H_ +#define SOURCEMETA_BLAZE_COMPILER_COMPILE_HELPERS_H_ + +#include +#include + +#include // std::ranges::find, std::ranges::any_of +#include // assert +#include // std::cref +#include // std::distance +#include // std::regex, std::regex_match, std::smatch +#include // std::declval, std::move + +namespace sourcemeta::blaze { + +// Static keyword strings for use in DynamicContext references +static const sourcemeta::core::JSON::String KEYWORD_EMPTY{}; +static const sourcemeta::core::JSON::String KEYWORD_PROPERTIES{"properties"}; +static const sourcemeta::core::JSON::String KEYWORD_THEN{"then"}; +static const sourcemeta::core::JSON::String KEYWORD_ELSE{"else"}; + +// Helper to create a single-element WeakPointer from a property name reference +inline auto make_weak_pointer(const std::string &property) + -> sourcemeta::core::WeakPointer { + sourcemeta::core::WeakPointer result; + result.push_back(std::cref(property)); + return result; +} + +// Helper to create a two-element WeakPointer from property name and index +inline auto make_weak_pointer(const std::string &property, + const std::size_t index) + -> sourcemeta::core::WeakPointer { + sourcemeta::core::WeakPointer result; + result.push_back(std::cref(property)); + result.push_back(index); + return result; +} + +// Helper to create a two-element WeakPointer from two property names +inline auto make_weak_pointer(const std::string &property1, + const std::string &property2) + -> sourcemeta::core::WeakPointer { + sourcemeta::core::WeakPointer result; + result.push_back(std::cref(property1)); + result.push_back(std::cref(property2)); + return result; +} + +inline auto relative_dynamic_context() -> DynamicContext { + return {.keyword = KEYWORD_EMPTY, + .base_schema_location = sourcemeta::core::empty_weak_pointer, + .base_instance_location = sourcemeta::core::empty_weak_pointer}; +} + +inline auto schema_resource_id(const std::vector &resources, + const std::string_view resource) -> std::size_t { + const auto iterator{std::ranges::find( + resources, sourcemeta::core::URI::canonicalize(resource))}; + if (iterator == resources.cend()) { + assert(resource.empty()); + return 0; + } + + return 1 + + static_cast(std::distance(resources.cbegin(), iterator)); +} + +// Instantiate a value-oriented step with a custom resource +inline auto make_with_resource(const InstructionIndex type, + const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Value &value, const std::string &resource) + -> Instruction { + const auto schema_location{ + dynamic_context.keyword.empty() + ? to_pointer(dynamic_context.base_schema_location) + : to_pointer(dynamic_context.base_schema_location) + .concat({dynamic_context.keyword})}; + const auto extra_index{context.extra.size()}; + context.extra.push_back( + {.relative_schema_location = schema_location, + .keyword_location = + to_uri(schema_context.relative_pointer, schema_context.base) + .recompose(), + .schema_resource = schema_resource_id(context.resources, resource)}); + return {.type = type, + .relative_instance_location = + to_pointer(dynamic_context.base_instance_location), + .value = value, + .children = {}, + .extra_index = extra_index}; +} + +// Instantiate a value-oriented step +inline auto make(const InstructionIndex type, const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Value &value) + -> Instruction { + return make_with_resource(type, context, schema_context, dynamic_context, + value, schema_context.base.recompose()); +} + +// Instantiate an applicator step +inline auto make(const InstructionIndex type, const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, Value &&value, + Instructions &&children) -> Instruction { + const auto schema_location{ + dynamic_context.keyword.empty() + ? to_pointer(dynamic_context.base_schema_location) + : to_pointer(dynamic_context.base_schema_location) + .concat({dynamic_context.keyword})}; + const auto extra_index{context.extra.size()}; + context.extra.push_back( + {.relative_schema_location = schema_location, + .keyword_location = + to_uri(schema_context.relative_pointer, schema_context.base) + .recompose(), + .schema_resource = schema_resource_id(context.resources, + schema_context.base.recompose())}); + return {.type = type, + .relative_instance_location = + to_pointer(dynamic_context.base_instance_location), + .value = std::move(value), + .children = std::move(children), + .extra_index = extra_index}; +} + +inline auto unroll(const Context &context, const Instruction &step, + const sourcemeta::core::WeakPointer &base_instance_location = + sourcemeta::core::empty_weak_pointer) -> Instruction { + auto source_extra{context.extra[step.extra_index]}; + const auto extra_index{context.extra.size()}; + context.extra.push_back(std::move(source_extra)); + return {.type = step.type, + .relative_instance_location = + to_pointer(base_instance_location) + .concat(step.relative_instance_location), + .value = step.value, + .children = {}, + .extra_index = extra_index}; +} + +inline auto rephrase(const Context &context, const InstructionIndex type, + const Instruction &step) -> Instruction { + auto source_extra{context.extra[step.extra_index]}; + const auto extra_index{context.extra.size()}; + context.extra.push_back(std::move(source_extra)); + return {.type = type, + .relative_instance_location = step.relative_instance_location, + .value = step.value, + .children = {}, + .extra_index = extra_index}; +} + +inline auto +unsigned_integer_property(const sourcemeta::core::JSON &document, + const sourcemeta::core::JSON::String &property) + -> std::optional { + if (document.defines(property) && document.at(property).is_integer()) { + const auto value{document.at(property).to_integer()}; + assert(value >= 0); + return static_cast(value); + } + + return std::nullopt; +} + +inline auto +unsigned_integer_property(const sourcemeta::core::JSON &document, + const sourcemeta::core::JSON::String &property, + const std::size_t otherwise) -> std::size_t { + return unsigned_integer_property(document, property).value_or(otherwise); +} + +inline auto static_frame_entry(const Context &context, + const SchemaContext &schema_context) + -> const sourcemeta::core::SchemaFrame::Location & { + const auto current{ + to_uri(schema_context.relative_pointer, schema_context.base).recompose()}; + const auto type{sourcemeta::core::SchemaReferenceType::Static}; + assert(context.frame.locations().contains({type, current})); + return context.frame.locations().at({type, current}); +} + +inline auto walk_subschemas(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context) -> auto { + const auto &entry{static_frame_entry(context, schema_context)}; + return sourcemeta::core::SchemaIterator{ + schema_context.schema.at(dynamic_context.keyword), context.walker, + context.resolver, entry.dialect}; +} + +// TODO: Get rid of this given the new Core regex optimisations +inline auto pattern_as_prefix(const std::string &pattern) + -> std::optional { + static const std::regex starts_with_regex{R"(^\^([a-zA-Z0-9-_/]+)$)"}; + std::smatch matches; + if (std::regex_match(pattern, matches, starts_with_regex)) { + return matches[1].str(); + } else { + return std::nullopt; + } +} + +inline auto find_adjacent(const Context &context, + const SchemaContext &schema_context, + const std::set &vocabularies, + const std::string &keyword, + const sourcemeta::core::JSON::Type type) -> auto { + std::vector possible_keyword_uris; + possible_keyword_uris.push_back( + to_uri(schema_context.relative_pointer.initial().concat( + make_weak_pointer(keyword)), + schema_context.base) + .recompose()); + + // TODO: Do something similar with `allOf` + + // Attempt to statically follow references + static const std::string ref_keyword{"$ref"}; + if (schema_context.schema.defines("$ref")) { + const auto reference_type{sourcemeta::core::SchemaReferenceType::Static}; + const auto destination_uri{ + to_uri(schema_context.relative_pointer.initial().concat( + make_weak_pointer(ref_keyword)), + schema_context.base) + .recompose()}; + assert( + context.frame.locations().contains({reference_type, destination_uri})); + const auto &destination{ + context.frame.locations().at({reference_type, destination_uri})}; + assert(context.frame.references().contains( + {reference_type, destination.pointer})); + const auto &reference{ + context.frame.references().at({reference_type, destination.pointer})}; + const auto keyword_uri{ + sourcemeta::core::to_uri( + sourcemeta::core::to_pointer( + std::string{reference.fragment.value_or("")}) + .concat({keyword})) + .resolve_from(sourcemeta::core::URI{reference.base})}; + + // TODO: When this logic is used by + // `unevaluatedProperties`/`unevaluatedItems`, how can we let the + // applicators we detect here know that they have already been taken into + // consideration and thus do not have to track evaluation? + possible_keyword_uris.push_back(keyword_uri.recompose()); + } + + std::vector> result; + + for (const auto &possible_keyword_uri : possible_keyword_uris) { + if (!context.frame.locations().contains( + {sourcemeta::core::SchemaReferenceType::Static, + possible_keyword_uri})) { + continue; + } + + const auto &frame_entry{context.frame.locations().at( + {sourcemeta::core::SchemaReferenceType::Static, possible_keyword_uri})}; + const auto &subschema{ + sourcemeta::core::get(context.root, frame_entry.pointer)}; + const auto &subschema_vocabularies{sourcemeta::core::vocabularies( + subschema, context.resolver, frame_entry.dialect)}; + + if (std::ranges::any_of(vocabularies, + [&subschema_vocabularies](const auto &vocabulary) { + return subschema_vocabularies.contains( + vocabulary); + }) && + subschema.type() == type) { + result.emplace_back(subschema); + } + } + + return result; +} + +inline auto recursive_template_size(const Instructions &steps) -> std::size_t { + std::size_t result{steps.size()}; + for (const auto &variant : steps) { + result += recursive_template_size(variant.children); + } + + return result; +} + +inline auto make_property(const ValueString &property) -> ValueProperty { + static const sourcemeta::core::PropertyHashJSON hasher; + return {property, hasher(property)}; +} + +inline auto requires_evaluation(const Context &context, + const sourcemeta::core::WeakPointer &pointer) + -> bool { + for (const auto &unevaluated : context.unevaluated) { + if (unevaluated.second.unresolved || + unevaluated.second.dynamic_dependencies.contains(pointer)) { + return true; + } + + for (const auto &dependency : unevaluated.second.dynamic_dependencies) { + if (dependency.starts_with(pointer)) { + return true; + } + } + } + + return false; +} + +inline auto requires_evaluation(const Context &context, + const SchemaContext &schema_context) -> bool { + const auto &entry{static_frame_entry(context, schema_context)}; + return requires_evaluation(context, entry.pointer); +} + +// TODO: Elevate to Core and test + +inline auto +is_circular(const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::WeakPointer &reference_origin, + const sourcemeta::core::SchemaFrame::ReferencesEntry &reference, + std::unordered_set &visited) -> bool { + if (visited.contains(reference.destination)) { + return false; + } + visited.insert(reference.destination); + + const auto destination_location{frame.traverse(reference.destination)}; + if (!destination_location.has_value()) { + return false; + } + + const auto &destination_pointer{destination_location->get().pointer}; + if (reference_origin.starts_with(destination_pointer) || + destination_pointer.starts_with(reference_origin)) { + return true; + } + + for (const auto &ref_entry : frame.references()) { + if (ref_entry.first.first == + sourcemeta::core::SchemaReferenceType::Static && + ref_entry.first.second.starts_with(destination_pointer)) { + if (is_circular(frame, reference_origin, ref_entry.second, visited)) { + return true; + } + } + } + + return false; +} + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/compiler/compile_json.cc b/vendor/blaze/src/compiler/compile_json.cc new file mode 100644 index 0000000..89a847b --- /dev/null +++ b/vendor/blaze/src/compiler/compile_json.cc @@ -0,0 +1,83 @@ +#include + +#include // assert +#include // std::visit + +namespace { +auto to_json(const sourcemeta::blaze::Instruction &instruction, + const std::vector &extra) + -> sourcemeta::core::JSON { + // Note that we purposely avoid objects to help consumers avoid potentially + // expensive hash-map or flat-map lookups when parsing back + auto result{sourcemeta::core::JSON::make_array()}; + result.push_back(sourcemeta::core::to_json(instruction.type)); + + const auto &meta{extra[instruction.extra_index]}; + result.push_back(sourcemeta::core::to_json(meta.relative_schema_location)); + result.push_back( + sourcemeta::core::to_json(instruction.relative_instance_location)); + result.push_back(sourcemeta::core::to_json(meta.keyword_location)); + result.push_back(sourcemeta::core::to_json(meta.schema_resource)); + + // Note that we purposely avoid objects to help consumers avoid potentially + // expensive hash-map or flat-map lookups when parsing back + auto value{sourcemeta::core::JSON::make_array()}; + const auto value_index{instruction.value.index()}; + value.push_back(sourcemeta::core::to_json(value_index)); + // Don't encode empty values, which tend to happen a lot + if (value_index != 0) { + value.push_back(std::visit( + [](const auto &variant) { return sourcemeta::core::to_json(variant); }, + instruction.value)); + } + assert(value.is_array()); + assert(!value.empty()); + assert(value.at(0).is_integer()); + result.push_back(std::move(value)); + + if (!instruction.children.empty()) { + auto children_json{sourcemeta::core::JSON::make_array()}; + result.push_back(sourcemeta::core::to_json( + instruction.children, [&extra](const auto &subinstruction) { + return to_json(subinstruction, extra); + })); + } + + return result; +} +} // namespace + +namespace sourcemeta::blaze { + +auto to_json(const Template &schema_template) -> sourcemeta::core::JSON { + // Note that we purposely avoid objects to help consumers avoid potentially + // expensive hash-map or flat-map lookups when parsing back + auto result{sourcemeta::core::JSON::make_array()}; + result.push_back(sourcemeta::core::JSON{ + static_cast(sourcemeta::blaze::JSON_VERSION)}); + result.push_back(sourcemeta::core::JSON{schema_template.dynamic}); + result.push_back(sourcemeta::core::JSON{schema_template.track}); + + auto targets{sourcemeta::core::JSON::make_array()}; + for (const auto &target : schema_template.targets) { + targets.push_back(sourcemeta::core::to_json( + target, [&schema_template](const auto &instruction) { + return ::to_json(instruction, schema_template.extra); + })); + } + + result.push_back(std::move(targets)); + + auto labels{sourcemeta::core::JSON::make_array()}; + for (const auto &[key, value] : schema_template.labels) { + auto pair{sourcemeta::core::JSON::make_array()}; + pair.push_back(sourcemeta::core::JSON{static_cast(key)}); + pair.push_back(sourcemeta::core::JSON{static_cast(value)}); + labels.push_back(std::move(pair)); + } + result.push_back(std::move(labels)); + + return result; +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/compiler/default_compiler.cc b/vendor/blaze/src/compiler/default_compiler.cc new file mode 100644 index 0000000..54c66f3 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler.cc @@ -0,0 +1,566 @@ +#include + +#include "default_compiler_2019_09.h" +#include "default_compiler_2020_12.h" +#include "default_compiler_draft4.h" +#include "default_compiler_draft6.h" +#include "default_compiler_draft7.h" +#include "default_compiler_openapi.h" + +#include // assert +#include // std::string +#include // std::unordered_set + +auto sourcemeta::blaze::default_schema_compiler( + const sourcemeta::blaze::Context &context, + const sourcemeta::blaze::SchemaContext &schema_context, + const sourcemeta::blaze::DynamicContext &dynamic_context, + const sourcemeta::blaze::Instructions ¤t) + -> sourcemeta::blaze::Instructions { + assert(!dynamic_context.keyword.empty()); + + using Known = sourcemeta::core::Vocabularies::Known; + static std::unordered_set + SUPPORTED_VOCABULARIES{Known::JSON_Schema_2020_12_Core, + Known::JSON_Schema_2020_12_Applicator, + Known::JSON_Schema_2020_12_Unevaluated, + Known::JSON_Schema_2020_12_Validation, + Known::JSON_Schema_2020_12_Meta_Data, + Known::JSON_Schema_2020_12_Format_Annotation, + Known::JSON_Schema_2020_12_Content, + Known::JSON_Schema_2019_09_Core, + Known::JSON_Schema_2019_09_Applicator, + Known::JSON_Schema_2019_09_Validation, + Known::JSON_Schema_2019_09_Meta_Data, + Known::JSON_Schema_2019_09_Format, + Known::JSON_Schema_2019_09_Content, + Known::JSON_Schema_2019_09_Hyper_Schema, + Known::JSON_Schema_Draft_7, + Known::JSON_Schema_Draft_7_Hyper, + Known::JSON_Schema_Draft_6, + Known::JSON_Schema_Draft_6_Hyper, + Known::JSON_Schema_Draft_4, + Known::JSON_Schema_Draft_4_Hyper, + Known::OpenAPI_3_1_Base, + Known::OpenAPI_3_2_Base}; + + schema_context.vocabularies.throw_if_any_unsupported( + SUPPORTED_VOCABULARIES, "Cannot compile unsupported vocabulary"); + + using namespace sourcemeta::blaze; + +#define COMPILE(vocabulary, _keyword, handler) \ + if (schema_context.vocabularies.contains(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)) && \ + 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) && \ + schema_context.schema.is_object() && \ + schema_context.schema.defines(_keyword)) { \ + return {}; \ + } + + // ******************************************** + // 2020-12 + // ******************************************** + + COMPILE(Known::JSON_Schema_2020_12_Core, "$dynamicRef", + compiler_2020_12_core_dynamicref); + + COMPILE(Known::JSON_Schema_2020_12_Applicator, "prefixItems", + compiler_2020_12_applicator_prefixitems); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "items", + compiler_2020_12_applicator_items); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "contains", + compiler_2020_12_applicator_contains); + + // Same as 2019-09 + + COMPILE(Known::JSON_Schema_2020_12_Validation, "dependentRequired", + compiler_2019_09_validation_dependentrequired); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "dependentSchemas", + compiler_2019_09_applicator_dependentschemas); + + COMPILE(Known::JSON_Schema_2020_12_Applicator, "properties", + compiler_2019_09_applicator_properties); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "patternProperties", + compiler_2019_09_applicator_patternproperties); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "additionalProperties", + compiler_2019_09_applicator_additionalproperties); + COMPILE(Known::JSON_Schema_2020_12_Unevaluated, "unevaluatedProperties", + compiler_2019_09_applicator_unevaluatedproperties); + COMPILE(Known::JSON_Schema_2020_12_Unevaluated, "unevaluatedItems", + compiler_2019_09_applicator_unevaluateditems); + COMPILE(Known::JSON_Schema_2020_12_Content, "contentEncoding", + compiler_2019_09_content_contentencoding); + COMPILE(Known::JSON_Schema_2020_12_Content, "contentMediaType", + compiler_2019_09_content_contentmediatype); + COMPILE(Known::JSON_Schema_2020_12_Content, "contentSchema", + compiler_2019_09_content_contentschema); + COMPILE(Known::JSON_Schema_2020_12_Format_Annotation, "format", + compiler_2019_09_format_format); + + // Same as Draft 7 + + COMPILE(Known::JSON_Schema_2020_12_Applicator, "if", + compiler_draft7_applicator_if); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "then", + compiler_draft7_applicator_then); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "else", + compiler_draft7_applicator_else); + + // Same as Draft 6 + + COMPILE(Known::JSON_Schema_2020_12_Applicator, "propertyNames", + compiler_draft6_validation_propertynames); + + COMPILE(Known::JSON_Schema_2020_12_Validation, "type", + compiler_draft6_validation_type); + COMPILE(Known::JSON_Schema_2020_12_Validation, "const", + compiler_draft6_validation_const); + COMPILE(Known::JSON_Schema_2020_12_Validation, "exclusiveMaximum", + compiler_draft6_validation_exclusivemaximum); + COMPILE(Known::JSON_Schema_2020_12_Validation, "exclusiveMinimum", + compiler_draft6_validation_exclusiveminimum); + + // Same as Draft 4 + + // As per compatibility optional test + COMPILE(Known::JSON_Schema_2020_12_Applicator, "dependencies", + compiler_draft4_applicator_dependencies); + + COMPILE(Known::JSON_Schema_2020_12_Core, "$ref", compiler_draft4_core_ref); + + COMPILE(Known::JSON_Schema_2020_12_Applicator, "allOf", + compiler_draft4_applicator_allof); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "anyOf", + compiler_draft4_applicator_anyof); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "oneOf", + compiler_draft4_applicator_oneof); + COMPILE(Known::JSON_Schema_2020_12_Applicator, "not", + compiler_draft4_applicator_not); + + COMPILE(Known::JSON_Schema_2020_12_Validation, "enum", + compiler_draft4_validation_enum); + COMPILE(Known::JSON_Schema_2020_12_Validation, "uniqueItems", + compiler_draft4_validation_uniqueitems); + COMPILE(Known::JSON_Schema_2020_12_Validation, "maxItems", + compiler_draft4_validation_maxitems); + COMPILE(Known::JSON_Schema_2020_12_Validation, "minItems", + compiler_draft4_validation_minitems); + COMPILE(Known::JSON_Schema_2020_12_Validation, "required", + compiler_draft4_validation_required); + COMPILE(Known::JSON_Schema_2020_12_Validation, "maxProperties", + compiler_draft4_validation_maxproperties); + COMPILE(Known::JSON_Schema_2020_12_Validation, "minProperties", + compiler_draft4_validation_minproperties); + COMPILE(Known::JSON_Schema_2020_12_Validation, "maximum", + compiler_draft4_validation_maximum); + COMPILE(Known::JSON_Schema_2020_12_Validation, "minimum", + compiler_draft4_validation_minimum); + COMPILE(Known::JSON_Schema_2020_12_Validation, "multipleOf", + compiler_draft4_validation_multipleof); + COMPILE(Known::JSON_Schema_2020_12_Validation, "maxLength", + compiler_draft4_validation_maxlength); + COMPILE(Known::JSON_Schema_2020_12_Validation, "minLength", + compiler_draft4_validation_minlength); + COMPILE(Known::JSON_Schema_2020_12_Validation, "pattern", + compiler_draft4_validation_pattern); + + // ******************************************** + // 2019-09 + // ******************************************** + + COMPILE(Known::JSON_Schema_2019_09_Core, "$recursiveRef", + compiler_2019_09_core_recursiveref); + COMPILE(Known::JSON_Schema_2019_09_Validation, "dependentRequired", + compiler_2019_09_validation_dependentrequired); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "dependentSchemas", + compiler_2019_09_applicator_dependentschemas); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "contains", + compiler_2019_09_applicator_contains); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "unevaluatedItems", + compiler_2019_09_applicator_unevaluateditems); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "unevaluatedProperties", + compiler_2019_09_applicator_unevaluatedproperties); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "items", + compiler_2019_09_applicator_items); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "additionalItems", + compiler_2019_09_applicator_additionalitems); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "properties", + compiler_2019_09_applicator_properties); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "patternProperties", + compiler_2019_09_applicator_patternproperties); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "additionalProperties", + compiler_2019_09_applicator_additionalproperties); + COMPILE(Known::JSON_Schema_2019_09_Content, "contentEncoding", + compiler_2019_09_content_contentencoding); + COMPILE(Known::JSON_Schema_2019_09_Content, "contentMediaType", + compiler_2019_09_content_contentmediatype); + COMPILE(Known::JSON_Schema_2019_09_Content, "contentSchema", + compiler_2019_09_content_contentschema); + COMPILE(Known::JSON_Schema_2019_09_Format, "format", + compiler_2019_09_format_format); + + // Same as Draft 7 + + COMPILE(Known::JSON_Schema_2019_09_Applicator, "if", + compiler_draft7_applicator_if); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "then", + compiler_draft7_applicator_then); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "else", + compiler_draft7_applicator_else); + + // Same as Draft 6 + + COMPILE(Known::JSON_Schema_2019_09_Applicator, "propertyNames", + compiler_draft6_validation_propertynames); + COMPILE(Known::JSON_Schema_2019_09_Validation, "type", + compiler_draft6_validation_type); + COMPILE(Known::JSON_Schema_2019_09_Validation, "const", + compiler_draft6_validation_const); + COMPILE(Known::JSON_Schema_2019_09_Validation, "exclusiveMaximum", + compiler_draft6_validation_exclusivemaximum); + COMPILE(Known::JSON_Schema_2019_09_Validation, "exclusiveMinimum", + compiler_draft6_validation_exclusiveminimum); + + // Same as Draft 4 + + // As per compatibility optional test + COMPILE(Known::JSON_Schema_2019_09_Applicator, "dependencies", + compiler_draft4_applicator_dependencies); + + COMPILE(Known::JSON_Schema_2019_09_Core, "$ref", compiler_draft4_core_ref); + + COMPILE(Known::JSON_Schema_2019_09_Applicator, "allOf", + compiler_draft4_applicator_allof); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "anyOf", + compiler_draft4_applicator_anyof); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "oneOf", + compiler_draft4_applicator_oneof); + COMPILE(Known::JSON_Schema_2019_09_Applicator, "not", + compiler_draft4_applicator_not); + + COMPILE(Known::JSON_Schema_2019_09_Validation, "enum", + compiler_draft4_validation_enum); + COMPILE(Known::JSON_Schema_2019_09_Validation, "uniqueItems", + compiler_draft4_validation_uniqueitems); + COMPILE(Known::JSON_Schema_2019_09_Validation, "maxItems", + compiler_draft4_validation_maxitems); + COMPILE(Known::JSON_Schema_2019_09_Validation, "minItems", + compiler_draft4_validation_minitems); + COMPILE(Known::JSON_Schema_2019_09_Validation, "required", + compiler_draft4_validation_required); + COMPILE(Known::JSON_Schema_2019_09_Validation, "maxProperties", + compiler_draft4_validation_maxproperties); + COMPILE(Known::JSON_Schema_2019_09_Validation, "minProperties", + compiler_draft4_validation_minproperties); + COMPILE(Known::JSON_Schema_2019_09_Validation, "maximum", + compiler_draft4_validation_maximum); + COMPILE(Known::JSON_Schema_2019_09_Validation, "minimum", + compiler_draft4_validation_minimum); + COMPILE(Known::JSON_Schema_2019_09_Validation, "multipleOf", + compiler_draft4_validation_multipleof); + COMPILE(Known::JSON_Schema_2019_09_Validation, "maxLength", + compiler_draft4_validation_maxlength); + COMPILE(Known::JSON_Schema_2019_09_Validation, "minLength", + compiler_draft4_validation_minlength); + COMPILE(Known::JSON_Schema_2019_09_Validation, "pattern", + compiler_draft4_validation_pattern); + + // ******************************************** + // DRAFT 7 + // ******************************************** + + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "$ref", compiler_draft4_core_ref); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_7, "$ref"); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_7_Hyper, "$ref"); + + // Any + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "if", compiler_draft7_applicator_if); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "then", compiler_draft7_applicator_then); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "else", compiler_draft7_applicator_else); + + // Same as Draft 6 + + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "type", compiler_draft6_validation_type); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "const", compiler_draft6_validation_const); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "contains", compiler_draft6_applicator_contains); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "propertyNames", compiler_draft6_validation_propertynames); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "exclusiveMaximum", compiler_draft6_validation_exclusivemaximum); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "exclusiveMinimum", compiler_draft6_validation_exclusiveminimum); + + // Same as Draft 4 + + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "allOf", compiler_draft4_applicator_allof); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "anyOf", compiler_draft4_applicator_anyof); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "oneOf", compiler_draft4_applicator_oneof); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "not", compiler_draft4_applicator_not); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "enum", compiler_draft4_validation_enum); + + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "items", compiler_draft4_applicator_items); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "additionalItems", compiler_draft4_applicator_additionalitems); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "uniqueItems", compiler_draft4_validation_uniqueitems); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "maxItems", compiler_draft4_validation_maxitems); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "minItems", compiler_draft4_validation_minitems); + + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "required", compiler_draft4_validation_required); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "maxProperties", compiler_draft4_validation_maxproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "minProperties", compiler_draft4_validation_minproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "properties", compiler_draft4_applicator_properties); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "patternProperties", + compiler_draft4_applicator_patternproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "additionalProperties", + compiler_draft4_applicator_additionalproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "dependencies", compiler_draft4_applicator_dependencies); + + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "maximum", compiler_draft4_validation_maximum); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "minimum", compiler_draft4_validation_minimum); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "multipleOf", compiler_draft4_validation_multipleof); + + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "maxLength", compiler_draft4_validation_maxlength); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "minLength", compiler_draft4_validation_minlength); + COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, + "pattern", compiler_draft4_validation_pattern); + + // ******************************************** + // DRAFT 6 + // ******************************************** + + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "$ref", compiler_draft4_core_ref); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_6, "$ref"); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_6_Hyper, "$ref"); + + // Any + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "type", compiler_draft6_validation_type); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "const", compiler_draft6_validation_const); + + // Array + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "contains", compiler_draft6_applicator_contains); + + // Object + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "propertyNames", compiler_draft6_validation_propertynames); + + // Number + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "exclusiveMaximum", compiler_draft6_validation_exclusivemaximum); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "exclusiveMinimum", compiler_draft6_validation_exclusiveminimum); + + // Same as Draft 4 + + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "allOf", compiler_draft4_applicator_allof); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "anyOf", compiler_draft4_applicator_anyof); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "oneOf", compiler_draft4_applicator_oneof); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "not", compiler_draft4_applicator_not); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "enum", compiler_draft4_validation_enum); + + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "items", compiler_draft4_applicator_items); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "additionalItems", compiler_draft4_applicator_additionalitems); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "uniqueItems", compiler_draft4_validation_uniqueitems); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "maxItems", compiler_draft4_validation_maxitems); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "minItems", compiler_draft4_validation_minitems); + + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "required", compiler_draft4_validation_required); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "maxProperties", compiler_draft4_validation_maxproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "minProperties", compiler_draft4_validation_minproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "properties", compiler_draft4_applicator_properties); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "patternProperties", + compiler_draft4_applicator_patternproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "additionalProperties", + compiler_draft4_applicator_additionalproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "dependencies", compiler_draft4_applicator_dependencies); + + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "maximum", compiler_draft4_validation_maximum); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "minimum", compiler_draft4_validation_minimum); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "multipleOf", compiler_draft4_validation_multipleof); + + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "maxLength", compiler_draft4_validation_maxlength); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "minLength", compiler_draft4_validation_minlength); + COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, + "pattern", compiler_draft4_validation_pattern); + + // ******************************************** + // DRAFT 4 + // ******************************************** + + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "$ref", compiler_draft4_core_ref); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_4, "$ref"); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_4_Hyper, "$ref"); + + // Applicators + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "allOf", compiler_draft4_applicator_allof); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "anyOf", compiler_draft4_applicator_anyof); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "oneOf", compiler_draft4_applicator_oneof); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "not", compiler_draft4_applicator_not); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "properties", compiler_draft4_applicator_properties); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "patternProperties", + compiler_draft4_applicator_patternproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "additionalProperties", + compiler_draft4_applicator_additionalproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "items", compiler_draft4_applicator_items); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "additionalItems", compiler_draft4_applicator_additionalitems); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "dependencies", compiler_draft4_applicator_dependencies); + + // Any + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "type", compiler_draft4_validation_type); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "enum", compiler_draft4_validation_enum); + + // Object + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "required", compiler_draft4_validation_required); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "maxProperties", compiler_draft4_validation_maxproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "minProperties", compiler_draft4_validation_minproperties); + + // Array + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "uniqueItems", compiler_draft4_validation_uniqueitems); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "maxItems", compiler_draft4_validation_maxitems); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "minItems", compiler_draft4_validation_minitems); + + // String + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "pattern", compiler_draft4_validation_pattern); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "maxLength", compiler_draft4_validation_maxlength); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "minLength", compiler_draft4_validation_minlength); + + // Number + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "maximum", compiler_draft4_validation_maximum); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "minimum", compiler_draft4_validation_minimum); + COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + "multipleOf", compiler_draft4_validation_multipleof); + + // ******************************************** + // OpenAPI + // ******************************************** + + COMPILE_ANY(Known::OpenAPI_3_1_Base, Known::OpenAPI_3_2_Base, "discriminator", + compiler_openapi_noop); + COMPILE_ANY(Known::OpenAPI_3_1_Base, Known::OpenAPI_3_2_Base, "xml", + compiler_openapi_noop); + COMPILE_ANY(Known::OpenAPI_3_1_Base, Known::OpenAPI_3_2_Base, "externalDocs", + compiler_openapi_noop); + COMPILE_ANY(Known::OpenAPI_3_1_Base, Known::OpenAPI_3_2_Base, "example", + compiler_openapi_noop); + +#undef COMPILE +#undef COMPILE_ANY +#undef STOP_IF_SIBLING_KEYWORD + + if ((schema_context.vocabularies.contains(Known::JSON_Schema_2019_09_Core) || + schema_context.vocabularies.contains(Known::JSON_Schema_2020_12_Core)) && + !dynamic_context.keyword.starts_with('$') && + dynamic_context.keyword != "definitions") { + + // We handle these keywords as part of "contains" + if ((schema_context.vocabularies.contains( + Known::JSON_Schema_2019_09_Validation) || + schema_context.vocabularies.contains( + Known::JSON_Schema_2020_12_Validation)) && + (dynamic_context.keyword == "minContains" || + dynamic_context.keyword == "maxContains")) { + return {}; + } + + if (context.mode == Mode::FastValidation || + schema_context.is_property_name) { + return {}; + } + + return internal::compiler_2019_09_core_annotation(context, schema_context, + dynamic_context, current); + } + + return {}; +} diff --git a/vendor/blaze/src/compiler/default_compiler_2019_09.h b/vendor/blaze/src/compiler/default_compiler_2019_09.h new file mode 100644 index 0000000..8b76fd0 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler_2019_09.h @@ -0,0 +1,532 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_2019_09_H_ +#define SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_2019_09_H_ + +#include + +#include "compile_helpers.h" +#include "default_compiler_draft4.h" + +namespace internal { +using namespace sourcemeta::blaze; + +auto compiler_2019_09_applicator_dependentschemas( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_object()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + Instructions children; + + // To guarantee order + std::vector dependents; + for (const auto &entry : + schema_context.schema.at(dynamic_context.keyword).as_object()) { + dependents.push_back(entry.first); + } + std::ranges::sort(dependents); + + for (const auto &dependent : dependents) { + const auto &dependency{ + schema_context.schema.at(dynamic_context.keyword).at(dependent)}; + if (!is_schema(dependency)) { + continue; + } + + if (!dependency.is_boolean() || !dependency.to_boolean()) { + children.push_back(make( + sourcemeta::blaze::InstructionIndex::LogicalWhenDefines, context, + schema_context, relative_dynamic_context(), make_property(dependent), + compile(context, schema_context, relative_dynamic_context(), + sourcemeta::blaze::make_weak_pointer(dependent)))); + } + } + + // TODO: Is this wrapper really necessary? + return {make(sourcemeta::blaze::InstructionIndex::LogicalWhenType, context, + schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Object, std::move(children))}; +} + +auto compiler_2019_09_validation_dependentrequired( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_object()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + ValueStringMap dependencies; + for (const auto &entry : + schema_context.schema.at(dynamic_context.keyword).as_object()) { + if (!entry.second.is_array()) { + continue; + } + + std::vector properties; + for (const auto &property : entry.second.as_array()) { + assert(property.is_string()); + properties.push_back(property.to_string()); + } + + if (!properties.empty()) { + dependencies.emplace(entry.first, properties); + } + } + + if (dependencies.empty()) { + return {}; + } + + return { + make(sourcemeta::blaze::InstructionIndex::AssertionPropertyDependencies, + context, schema_context, dynamic_context, std::move(dependencies))}; +} + +auto compiler_2019_09_core_annotation(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + return {make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; +} + +auto compiler_2019_09_applicator_contains_with_options( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &, + const bool annotate, const bool track_evaluation) -> Instructions { + if (schema_context.is_property_name) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + std::size_t minimum{1}; + if (schema_context.schema.defines("minContains")) { + if (schema_context.schema.at("minContains").is_integer() && + schema_context.schema.at("minContains").is_positive()) { + minimum = static_cast( + schema_context.schema.at("minContains").to_integer()); + } else if (schema_context.schema.at("minContains").is_real() && + schema_context.schema.at("minContains").is_positive()) { + minimum = static_cast( + schema_context.schema.at("minContains").as_integer()); + } + } + + std::optional maximum; + if (schema_context.schema.defines("maxContains")) { + if (schema_context.schema.at("maxContains").is_integer() && + schema_context.schema.at("maxContains").is_positive()) { + maximum = schema_context.schema.at("maxContains").to_integer(); + } else if (schema_context.schema.at("maxContains").is_real() && + schema_context.schema.at("maxContains").is_positive()) { + maximum = schema_context.schema.at("maxContains").as_integer(); + } + } + + if (maximum.has_value() && minimum > maximum.value()) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionFail, context, + schema_context, dynamic_context, ValueNone{})}; + } + + if (minimum == 0 && !maximum.has_value() && !track_evaluation) { + return {}; + } + + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + if (annotate) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::AnnotationBasenameToParent, + context, schema_context, relative_dynamic_context(), ValueNone{})); + + // TODO: If after emitting the above annotation, the number of annotations + // for the current schema location + instance location is equal to the + // array size (which means we annotated all of the items), then emit + // an annotation "true" + } + + if (track_evaluation) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::ControlEvaluate, context, + schema_context, relative_dynamic_context(), ValuePointer{})); + } + + if (children.empty()) { + // We still need to check the instance is not empty + return {make(sourcemeta::blaze::InstructionIndex::AssertionArraySizeGreater, + context, schema_context, dynamic_context, + ValueUnsignedInteger{0})}; + } + + return {make(sourcemeta::blaze::InstructionIndex::LoopContains, context, + schema_context, dynamic_context, + ValueRange{minimum, maximum, annotate || track_evaluation}, + std::move(children))}; +} + +auto compiler_2019_09_applicator_contains(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions ¤t) + -> Instructions { + return compiler_2019_09_applicator_contains_with_options( + context, schema_context, dynamic_context, current, false, false); +} + +auto compiler_2019_09_applicator_additionalproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + + -> Instructions { + return compiler_draft4_applicator_additionalproperties_with_options( + context, schema_context, dynamic_context, + context.mode == Mode::Exhaustive, + requires_evaluation(context, schema_context)); +} + +auto compiler_2019_09_applicator_items(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + // TODO: Be smarter about how we treat `unevaluatedItems` like how we do for + // `unevaluatedProperties` + const bool track{ + std::ranges::any_of(context.unevaluated, [](const auto &dependency) { + return dependency.first.ends_with("unevaluatedItems"); + })}; + + if (schema_context.schema.at(dynamic_context.keyword).is_array()) { + return compiler_draft4_applicator_items_with_options( + context, schema_context, dynamic_context, + context.mode == Mode::Exhaustive, track); + } + + return compiler_draft4_applicator_items_with_options( + context, schema_context, dynamic_context, + context.mode == Mode::Exhaustive, + track && !schema_context.schema.defines("unevaluatedItems")); +} + +auto compiler_2019_09_applicator_additionalitems( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + // TODO: Be smarter about how we treat `unevaluatedItems` like how we do for + // `unevaluatedProperties` + const bool track{ + std::ranges::any_of(context.unevaluated, [](const auto &dependency) { + return dependency.first.ends_with("unevaluatedItems"); + })}; + + return compiler_draft4_applicator_additionalitems_with_options( + context, schema_context, dynamic_context, + context.mode == Mode::Exhaustive, + track && !schema_context.schema.defines("unevaluatedItems")); +} + +auto compiler_2019_09_applicator_unevaluateditems( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + const auto current_uri{ + to_uri(schema_context.relative_pointer, schema_context.base).recompose()}; + assert(context.unevaluated.contains(current_uri)); + const auto &dependencies{context.unevaluated.at(current_uri)}; + + for (const auto &dependency : dependencies.static_dependencies) { + assert(!dependency.empty()); + assert(dependency.back().is_property()); + const auto &keyword{dependency.back().to_property()}; + const auto &subschema{sourcemeta::core::get(context.root, dependency)}; + // NOLINTBEGIN(bugprone-branch-clone) + if (keyword == "items" && sourcemeta::core::is_schema(subschema)) { + return {}; + } else if (keyword == "additionalItems" || keyword == "unevaluatedItems") { + return {}; + } + // NOLINTEND(bugprone-branch-clone) + } + + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + if (context.mode == Mode::Exhaustive) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::AnnotationToParent, context, + schema_context, relative_dynamic_context(), + sourcemeta::core::JSON{true})); + } + + if (children.empty()) { + if (dependencies.dynamic_dependencies.empty() && !dependencies.unresolved && + !requires_evaluation(context, schema_context)) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::Evaluate, context, + schema_context, dynamic_context, ValueNone{})}; + } + + // TODO: Attempt to short-circuit evaluation tracking by looking at sibling + // and adjacent keywords like we do for `unevaluatedProperties` + + return {make(sourcemeta::blaze::InstructionIndex::LoopItemsUnevaluated, + context, schema_context, dynamic_context, ValueNone{}, + std::move(children))}; +} + +auto compiler_2019_09_applicator_unevaluatedproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + if (context.mode == Mode::Exhaustive) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::AnnotationBasenameToParent, + context, schema_context, relative_dynamic_context(), ValueNone{})); + } + + ValueStringSet filter_strings; + ValueStrings filter_prefixes; + std::vector filter_regexes; + + const auto current_uri{ + to_uri(schema_context.relative_pointer, schema_context.base).recompose()}; + assert(context.unevaluated.contains(current_uri)); + const auto &dependencies{context.unevaluated.at(current_uri)}; + + for (const auto &dependency : dependencies.static_dependencies) { + assert(!dependency.empty()); + assert(dependency.back().is_property()); + const auto &keyword{dependency.back().to_property()}; + const auto &subschema{sourcemeta::core::get(context.root, dependency)}; + if (keyword == "properties") { + if (subschema.is_object()) { + for (const auto &property : subschema.as_object()) { + filter_strings.insert(property.first); + } + } + } else if (keyword == "patternProperties") { + if (subschema.is_object()) { + for (const auto &property : subschema.as_object()) { + const auto maybe_prefix{pattern_as_prefix(property.first)}; + if (maybe_prefix.has_value()) { + filter_prefixes.push_back(maybe_prefix.value()); + } else { + static const std::string pattern_properties_keyword{ + "patternProperties"}; + filter_regexes.push_back( + {parse_regex(property.first, schema_context.base, + schema_context.relative_pointer.initial().concat( + sourcemeta::blaze::make_weak_pointer( + pattern_properties_keyword))), + property.first}); + } + } + } + } else if (keyword == "additionalProperties" || + keyword == "unevaluatedProperties") { + return {}; + } + } + + if (dependencies.dynamic_dependencies.empty() && !dependencies.unresolved && + !requires_evaluation(context, schema_context)) { + if (children.empty()) { + return {}; + } else if (!filter_strings.empty() || !filter_prefixes.empty() || + !filter_regexes.empty()) { + return {make(sourcemeta::blaze::InstructionIndex::LoopPropertiesExcept, + context, schema_context, dynamic_context, + ValuePropertyFilter{std::move(filter_strings), + std::move(filter_prefixes), + std::move(filter_regexes)}, + std::move(children))}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::LoopProperties, context, + schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } + } + + if (children.empty()) { + return {make(sourcemeta::blaze::InstructionIndex::Evaluate, context, + schema_context, dynamic_context, ValueNone{})}; + } else if (!filter_strings.empty() || !filter_prefixes.empty() || + !filter_regexes.empty()) { + return {make( + sourcemeta::blaze::InstructionIndex::LoopPropertiesUnevaluatedExcept, + context, schema_context, dynamic_context, + ValuePropertyFilter{std::move(filter_strings), + std::move(filter_prefixes), + std::move(filter_regexes)}, + std::move(children))}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::LoopPropertiesUnevaluated, + context, schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } +} + +auto compiler_2019_09_core_recursiveref(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions ¤t) + -> Instructions { + const auto &entry{static_frame_entry(context, schema_context)}; + // In this case, just behave as a normal static reference + if (!context.frame.references().contains( + {sourcemeta::core::SchemaReferenceType::Dynamic, entry.pointer})) { + return compiler_draft4_core_ref(context, schema_context, dynamic_context, + current); + } + + return {make(sourcemeta::blaze::InstructionIndex::ControlDynamicAnchorJump, + context, schema_context, dynamic_context, "")}; +} + +auto compiler_2019_09_applicator_properties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions ¤t) + -> Instructions { + return compiler_draft4_applicator_properties_with_options( + context, schema_context, dynamic_context, current, + context.mode == Mode::Exhaustive, + requires_evaluation(context, schema_context)); +} + +auto compiler_2019_09_applicator_patternproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + return compiler_draft4_applicator_patternproperties_with_options( + context, schema_context, dynamic_context, + context.mode == Mode::Exhaustive, + requires_evaluation(context, schema_context)); +} + +auto compiler_2019_09_content_contentencoding( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (context.mode == Mode::FastValidation) { + return {}; + } + + Instructions children{ + make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + + return {make(sourcemeta::blaze::InstructionIndex::ControlGroupWhenType, + context, schema_context, relative_dynamic_context(), + ValueType::String, std::move(children))}; +} + +auto compiler_2019_09_content_contentmediatype( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (context.mode == Mode::FastValidation) { + return {}; + } + + Instructions children{ + make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + + return {make(sourcemeta::blaze::InstructionIndex::ControlGroupWhenType, + context, schema_context, relative_dynamic_context(), + ValueType::String, std::move(children))}; +} + +auto compiler_2019_09_content_contentschema( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (context.mode == Mode::FastValidation) { + return {}; + } + + // The `contentSchema` keyword does nothing without `contentMediaType` + if (!schema_context.schema.defines("contentMediaType")) { + return {}; + } + + Instructions children{ + make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + + return {make(sourcemeta::blaze::InstructionIndex::ControlGroupWhenType, + context, schema_context, relative_dynamic_context(), + ValueType::String, std::move(children))}; +} + +auto compiler_2019_09_format_format(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (context.mode == Mode::FastValidation) { + return {}; + } + + Instructions children{ + make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + + return {make(sourcemeta::blaze::InstructionIndex::ControlGroupWhenType, + context, schema_context, relative_dynamic_context(), + ValueType::String, std::move(children))}; +} + +} // namespace internal +#endif diff --git a/vendor/blaze/src/compiler/default_compiler_2020_12.h b/vendor/blaze/src/compiler/default_compiler_2020_12.h new file mode 100644 index 0000000..f91aba2 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler_2020_12.h @@ -0,0 +1,118 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_2020_12_H_ +#define SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_2020_12_H_ + +#include +#include + +#include "compile_helpers.h" +#include "default_compiler_draft4.h" + +namespace internal { +using namespace sourcemeta::blaze; + +auto compiler_2020_12_applicator_prefixitems( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + // TODO: Be smarter about how we treat `unevaluatedItems` like how we do for + // `unevaluatedProperties` + const bool track{ + std::ranges::any_of(context.unevaluated, [](const auto &dependency) { + return dependency.first.ends_with("unevaluatedItems"); + })}; + + return compiler_draft4_applicator_items_array( + context, schema_context, dynamic_context, + context.mode == Mode::Exhaustive, track); +} + +auto compiler_2020_12_applicator_items(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + const auto cursor{(schema_context.schema.defines("prefixItems") && + schema_context.schema.at("prefixItems").is_array()) + ? schema_context.schema.at("prefixItems").size() + : 0}; + + // TODO: Be smarter about how we treat `unevaluatedItems` like how we do for + // `unevaluatedProperties` + const bool track{ + std::ranges::any_of(context.unevaluated, [](const auto &dependency) { + return dependency.first.ends_with("unevaluatedItems"); + })}; + + return compiler_draft4_applicator_additionalitems_from_cursor( + context, schema_context, dynamic_context, cursor, + context.mode == Mode::Exhaustive, + track && !schema_context.schema.defines("unevaluatedItems")); +} + +auto compiler_2020_12_applicator_contains(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions ¤t) + -> Instructions { + // TODO: Be smarter about how we treat `unevaluatedItems` like how we do for + // `unevaluatedProperties` + const bool track{ + std::ranges::any_of(context.unevaluated, [](const auto &dependency) { + return dependency.first.ends_with("unevaluatedItems"); + })}; + + return compiler_2019_09_applicator_contains_with_options( + context, schema_context, dynamic_context, current, + context.mode == Mode::Exhaustive, track); +} + +auto compiler_2020_12_core_dynamicref(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions ¤t) + -> Instructions { + const auto &entry{static_frame_entry(context, schema_context)}; + // In this case, just behave as a normal static reference + if (!context.frame.references().contains( + {sourcemeta::core::SchemaReferenceType::Dynamic, entry.pointer})) { + return compiler_draft4_core_ref(context, schema_context, dynamic_context, + current); + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_string()); + sourcemeta::core::URI reference{ + schema_context.schema.at(dynamic_context.keyword).to_string()}; + reference.resolve_from(schema_context.base); + reference.canonicalize(); + // We handle the non-anchor variant by not treating it as a dynamic reference + assert(reference.fragment().has_value()); + + // TODO: Here we can potentially optimize `$dynamicRef` as a static reference + // if we determine (by traversing the frame) that the given dynamic anchor + // is only defined once. That means there is only one schema resource, and + // the jump and be always statically determined + + // Note we don't need to even care about the static part of the dynamic + // reference (if any), as even if we jump first there, we will still + // look for the oldest dynamic anchor in the schema resource chain. + + if (reference.is_fragment_only()) { + return {make(sourcemeta::blaze::InstructionIndex::ControlDynamicAnchorJump, + context, schema_context, dynamic_context, + std::string{reference.fragment().value()})}; + } else { + const auto base_resource{reference.recompose_without_fragment()}; + assert(base_resource.has_value()); + + // If the dynamic reference has a static component, we need to make sure we + // append such static part as a resource before we begin the lookup + return {make_with_resource( + sourcemeta::blaze::InstructionIndex::ControlDynamicAnchorJump, context, + schema_context, dynamic_context, + // TODO: The amount of possible anchors is known at compile time. + // We could convert it into integers like we do for resources + std::string{reference.fragment().value()}, base_resource.value())}; + } +} + +} // namespace internal +#endif diff --git a/vendor/blaze/src/compiler/default_compiler_draft4.h b/vendor/blaze/src/compiler/default_compiler_draft4.h new file mode 100644 index 0000000..4a0d965 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler_draft4.h @@ -0,0 +1,2414 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT4_H_ +#define SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT4_H_ + +#include +#include + +#include + +#include // std::sort, std::ranges::any_of, std::ranges::all_of, std::find_if, std::ranges::none_of +#include // assert +#include // std::set +#include // std::move, std::to_underlying + +#include "compile_helpers.h" + +static auto parse_regex(const std::string &pattern, + const sourcemeta::core::URI &base, + const sourcemeta::core::WeakPointer &schema_location) + -> sourcemeta::core::Regex { + const auto result{sourcemeta::core::to_regex(pattern)}; + if (!result.has_value()) [[unlikely]] { + throw sourcemeta::blaze::CompilerInvalidRegexError( + base, to_pointer(schema_location), pattern); + } + + return result.value(); +} + +static auto +relative_schema_location_size(const sourcemeta::blaze::Context &context, + const sourcemeta::blaze::Instruction &step) + -> std::size_t { + return context.extra[step.extra_index].relative_schema_location.size(); +} + +static auto +defines_direct_enumeration(const sourcemeta::blaze::Instructions &steps) + -> std::optional { + const auto iterator{std::ranges::find_if(steps, [](const auto &step) { + return step.type == sourcemeta::blaze::InstructionIndex::AssertionEqual || + step.type == sourcemeta::blaze::InstructionIndex::AssertionEqualsAny; + })}; + + if (iterator == steps.cend()) { + return std::nullopt; + } + + return std::distance(steps.cbegin(), iterator); +} + +static auto is_inside_disjunctor(const sourcemeta::core::WeakPointer &pointer) + -> bool { + return pointer.size() > 2 && pointer.at(pointer.size() - 2).is_index() && + pointer.at(pointer.size() - 3).is_property() && + (pointer.at(pointer.size() - 3).to_property() == "oneOf" || + pointer.at(pointer.size() - 3).to_property() == "anyOf"); +} + +static auto json_array_to_string_set(const sourcemeta::core::JSON &document) + -> sourcemeta::blaze::ValueStringSet { + sourcemeta::blaze::ValueStringSet result; + for (const auto &value : document.as_array()) { + assert(value.is_string()); + result.insert(value.to_string()); + } + + return result; +} + +static auto +is_closed_properties_required(const sourcemeta::core::JSON &schema, + const sourcemeta::blaze::ValueStringSet &required) + -> bool { + return !schema.defines("patternProperties") && + schema.defines("additionalProperties") && + schema.at("additionalProperties").is_boolean() && + !schema.at("additionalProperties").to_boolean() && + schema.defines("properties") && schema.at("properties").is_object() && + schema.at("properties").size() == required.size() && + std::ranges::all_of(required, [&schema](const auto &property) { + return schema.at("properties") + .defines(property.first, property.second); + }); +} + +static auto +compile_properties(const sourcemeta::blaze::Context &context, + const sourcemeta::blaze::SchemaContext &schema_context, + const sourcemeta::blaze::DynamicContext &dynamic_context, + const sourcemeta::blaze::Instructions &) + -> std::vector> { + std::vector> + properties; + for (const auto &entry : schema_context.schema.at("properties").as_object()) { + properties.emplace_back( + entry.first, + compile(context, schema_context, dynamic_context, + sourcemeta::blaze::make_weak_pointer(entry.first), + sourcemeta::blaze::make_weak_pointer(entry.first))); + } + + // In many cases, `properties` have some subschemas that are small + // and some subschemas that are large. To attempt to improve performance, + // we prefer to evaluate smaller subschemas first, in the hope of failing + // earlier without spending a lot of time on other subschemas + if (context.tweaks.properties_reorder) { + std::ranges::sort(properties, [&context](const auto &left, + const auto &right) { + const auto left_size{recursive_template_size(left.second)}; + const auto right_size{recursive_template_size(right.second)}; + if (left_size == right_size) { + const auto left_direct_enumeration{ + defines_direct_enumeration(left.second)}; + const auto right_direct_enumeration{ + defines_direct_enumeration(right.second)}; + + // Enumerations always take precedence + if (left_direct_enumeration.has_value() && + right_direct_enumeration.has_value()) { + // If both options have a direct enumeration, we choose + // the one with the shorter relative schema location + return relative_schema_location_size( + context, left.second.at(left_direct_enumeration.value())) < + relative_schema_location_size( + context, + right.second.at(right_direct_enumeration.value())); + } else if (left_direct_enumeration.has_value()) { + return true; + } else if (right_direct_enumeration.has_value()) { + return false; + } + + return left.first < right.first; + } else { + return left_size < right_size; + } + }); + } + + return properties; +} + +static auto to_string_hashes( + std::vector> + &hashes) -> sourcemeta::blaze::ValueStringHashes { + assert(!hashes.empty()); + std::ranges::sort(hashes, [](const auto &left, const auto &right) { + return left.first.size() < right.first.size(); + }); + + sourcemeta::blaze::ValueStringHashes result; + // The idea with the table of contents is as follows: each index + // marks the starting and end positions for a string where the size + // is equal to the index. + result.second.resize(hashes.back().first.size() + 1, std::make_pair(0, 0)); + // TODO(C++23): Use std::views::enumerate when available in libc++ + for (std::size_t index = 0; index < hashes.size(); index++) { + result.first.emplace_back(hashes[index].second, hashes[index].first); + const auto string_size{hashes[index].first.size()}; + // We leave index 0 to represent the empty string + const auto position{index + 1}; + const auto lower_bound{ + result.second[string_size].first == 0 + ? position + : std::min(result.second[string_size].first, position)}; + const auto upper_bound{ + result.second[string_size].second == 0 + ? position + : std::max(result.second[string_size].second, position)}; + assert(lower_bound <= upper_bound); + assert(lower_bound > 0 && upper_bound > 0); + assert(string_size < result.second.size()); + result.second[string_size] = std::make_pair(lower_bound, upper_bound); + } + + assert(result.second.size() == hashes.back().first.size() + 1); + return result; +} + +namespace internal { +using namespace sourcemeta::blaze; + +auto compiler_draft4_core_ref(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + const auto &entry{static_frame_entry(context, schema_context)}; + const auto type{sourcemeta::core::SchemaReferenceType::Static}; + const auto reference{context.frame.reference(type, entry.pointer)}; + if (!reference.has_value()) [[unlikely]] { + throw sourcemeta::core::SchemaReferenceError( + schema_context.schema.at(dynamic_context.keyword).to_string(), + to_pointer(schema_context.relative_pointer), + "Could not resolve schema reference"); + } + + const auto key{std::make_tuple(type, + std::string_view{reference->get().destination}, + schema_context.is_property_name)}; + assert(context.targets.contains(key)); + return {make(sourcemeta::blaze::InstructionIndex::ControlJump, context, + schema_context, dynamic_context, + ValueUnsignedInteger{context.targets.at(key).first})}; +} + +auto compiler_draft4_validation_type(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (schema_context.schema.at(dynamic_context.keyword).is_string()) { + const auto &type{ + schema_context.schema.at(dynamic_context.keyword).to_string()}; + if (type == "null") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_null(); })) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Null)}; + } else if (type == "boolean") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_boolean(); })) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Boolean)}; + } else if (type == "object") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minProperties", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxProperties")}; + + if (context.mode == Mode::FastValidation) { + if (maximum.has_value() && minimum == 0) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeObjectUpper, + context, schema_context, dynamic_context, + ValueUnsignedInteger{maximum.value()})}; + } else if (minimum > 0 || maximum.has_value()) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeObjectBounded, + context, schema_context, dynamic_context, + ValueRange{minimum, maximum, false})}; + } + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_object(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("required")) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Object)}; + } else if (type == "array") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minItems", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxItems")}; + + if (context.mode == Mode::FastValidation) { + if (maximum.has_value() && minimum == 0) { + return { + make(sourcemeta::blaze::InstructionIndex::AssertionTypeArrayUpper, + context, schema_context, dynamic_context, + ValueUnsignedInteger{maximum.value()})}; + } else if (minimum > 0 || maximum.has_value()) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeArrayBounded, + context, schema_context, dynamic_context, + ValueRange{minimum, maximum, false})}; + } + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_array(); })) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Array)}; + } else if (type == "number") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_number(); })) { + return {}; + } + + ValueTypes types{}; + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Real)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; + } else if (type == "integer") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_integer(); })) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Integer)}; + } else if (type == "string") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minLength", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxLength")}; + + if (context.mode == Mode::FastValidation) { + if (maximum.has_value() && minimum == 0) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeStringUpper, + context, schema_context, dynamic_context, + ValueUnsignedInteger{maximum.value()})}; + } else if (minimum > 0 || maximum.has_value()) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeStringBounded, + context, schema_context, dynamic_context, + ValueRange{minimum, maximum, false})}; + } + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_string(); })) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::String)}; + } else { + return {}; + } + } else if (schema_context.schema.at(dynamic_context.keyword).is_array() && + schema_context.schema.at(dynamic_context.keyword).size() == 1 && + schema_context.schema.at(dynamic_context.keyword) + .front() + .is_string()) { + const auto &type{ + schema_context.schema.at(dynamic_context.keyword).front().to_string()}; + if (type == "null") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Null)}; + } else if (type == "boolean") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Boolean)}; + } else if (type == "object") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Object)}; + } else if (type == "array") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Array)}; + } else if (type == "number") { + ValueTypes types{}; + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Real)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; + } else if (type == "integer") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Integer)}; + } else if (type == "string") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::String)}; + } else { + return {}; + } + } else if (schema_context.schema.at(dynamic_context.keyword).is_array()) { + ValueTypes types{}; + for (const auto &type : + schema_context.schema.at(dynamic_context.keyword).as_array()) { + assert(type.is_string()); + const auto &type_string{type.to_string()}; + if (type_string == "null") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Null)); + } else if (type_string == "boolean") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Boolean)); + } else if (type_string == "object") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Object)); + } else if (type_string == "array") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Array)); + } else if (type_string == "number") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Real)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } else if (type_string == "integer") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + } else if (type_string == "string") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::String)); + } + } + + assert(types.any()); + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; + } + + return {}; +} + +auto compiler_draft4_validation_required(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions ¤t) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_array()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + const auto assume_object{schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == + "object"}; + + if (schema_context.schema.at(dynamic_context.keyword).empty()) { + return {}; + } else if (schema_context.schema.at(dynamic_context.keyword).size() > 1) { + ValueStringSet properties_set{json_array_to_string_set( + schema_context.schema.at(dynamic_context.keyword))}; + if (properties_set.size() == 1) { + if (assume_object) { + return { + make(sourcemeta::blaze::InstructionIndex::AssertionDefinesStrict, + context, schema_context, dynamic_context, + make_property(properties_set.begin()->first))}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::AssertionDefines, + context, schema_context, dynamic_context, + make_property(properties_set.begin()->first))}; + } + } else if (is_closed_properties_required(schema_context.schema, + properties_set)) { + if (context.mode == Mode::FastValidation && assume_object) { + static const std::string properties_keyword{"properties"}; + const SchemaContext new_schema_context{ + .relative_pointer = + schema_context.relative_pointer.initial().concat( + sourcemeta::blaze::make_weak_pointer(properties_keyword)), + .schema = schema_context.schema, + .vocabularies = schema_context.vocabularies, + .base = schema_context.base, + .is_property_name = schema_context.is_property_name}; + const DynamicContext new_dynamic_context{ + .keyword = KEYWORD_PROPERTIES, + .base_schema_location = sourcemeta::core::empty_weak_pointer, + .base_instance_location = sourcemeta::core::empty_weak_pointer}; + auto properties{compile_properties(context, new_schema_context, + new_dynamic_context, current)}; + if (std::ranges::all_of(properties, [](const auto &property) { + return property.second.size() == 1 && + property.second.front().type == + InstructionIndex::AssertionTypeStrict; + })) { + std::set types; + for (const auto &property : properties) { + types.insert(std::get(property.second.front().value)); + } + + if (types.size() == 1) { + // Handled in `properties` + return {}; + } + } + + sourcemeta::core::PropertyHashJSON hasher; + if (context.mode == Mode::FastValidation && + properties_set.size() == 3 && + std::ranges::all_of(properties_set, + [&hasher](const auto &property) { + return hasher.is_perfect(property.second); + })) { + std::vector> hashes; + for (const auto &property : properties_set) { + hashes.emplace_back(property.first, property.second); + } + + return {make(sourcemeta::blaze::InstructionIndex:: + AssertionDefinesExactlyStrictHash3, + context, schema_context, dynamic_context, + to_string_hashes(hashes))}; + } + + return {make( + sourcemeta::blaze::InstructionIndex::AssertionDefinesExactlyStrict, + context, schema_context, dynamic_context, + std::move(properties_set))}; + } else { + return { + make(sourcemeta::blaze::InstructionIndex::AssertionDefinesExactly, + context, schema_context, dynamic_context, + std::move(properties_set))}; + } + } else if (assume_object) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionDefinesAllStrict, + context, schema_context, dynamic_context, std::move(properties_set))}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::AssertionDefinesAll, + context, schema_context, dynamic_context, + std::move(properties_set))}; + } + } else if (assume_object) { + assert( + schema_context.schema.at(dynamic_context.keyword).front().is_string()); + return {make(sourcemeta::blaze::InstructionIndex::AssertionDefinesStrict, + context, schema_context, dynamic_context, + make_property(schema_context.schema.at(dynamic_context.keyword) + .front() + .to_string()))}; + } else { + assert( + schema_context.schema.at(dynamic_context.keyword).front().is_string()); + return {make(sourcemeta::blaze::InstructionIndex::AssertionDefines, context, + schema_context, dynamic_context, + make_property(schema_context.schema.at(dynamic_context.keyword) + .front() + .to_string()))}; + } +} + +auto compiler_draft4_applicator_allof(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_array()) { + return {}; + } + + assert(!schema_context.schema.at(dynamic_context.keyword).empty()); + + Instructions children; + + if (context.mode == Mode::FastValidation && + // TODO: Make this work with `$dynamicRef` + !context.uses_dynamic_scopes) { + for (std::uint64_t index = 0; + index < schema_context.schema.at(dynamic_context.keyword).size(); + index++) { + for (auto &&step : compile( + context, schema_context, dynamic_context, + {static_cast(index)})) { + children.push_back(std::move(step)); + } + } + + return children; + } else { + for (std::uint64_t index = 0; + index < schema_context.schema.at(dynamic_context.keyword).size(); + index++) { + for (auto &&step : compile( + context, schema_context, relative_dynamic_context(), + {static_cast(index)})) { + children.push_back(std::move(step)); + } + } + + return {make(sourcemeta::blaze::InstructionIndex::LogicalAnd, context, + schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } +} + +auto compiler_draft4_applicator_anyof(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_array()) { + return {}; + } + + assert(!schema_context.schema.at(dynamic_context.keyword).empty()); + + Instructions disjunctors; + for (std::uint64_t index = 0; + index < schema_context.schema.at(dynamic_context.keyword).size(); + index++) { + disjunctors.push_back(make( + sourcemeta::blaze::InstructionIndex::ControlGroup, context, + schema_context, relative_dynamic_context(), ValueNone{}, + compile( + context, schema_context, relative_dynamic_context(), + {static_cast(index)}))); + } + + if (context.mode == Mode::FastValidation && + std::ranges::all_of(disjunctors, [](const auto &instruction) { + return instruction.children.size() == 1 && + (instruction.children.front().type == + sourcemeta::blaze::InstructionIndex::AssertionTypeStrict || + instruction.children.front().type == + sourcemeta::blaze::InstructionIndex:: + AssertionTypeStrictAny); + })) { + ValueTypes types{}; + for (const auto &instruction : disjunctors) { + if (instruction.children.front().type == + sourcemeta::blaze::InstructionIndex::AssertionTypeStrict) { + const auto &value{ + *std::get_if(&instruction.children.front().value)}; + types.set(static_cast(value)); + } + + if (instruction.children.front().type == + sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny) { + const auto &value{ + *std::get_if(&instruction.children.front().value)}; + types |= value; + } + } + + assert(types.any()); + const auto popcount{types.count()}; + if (popcount > 1) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; + } else { + std::uint8_t type_index{0}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + type_index = bit; + break; + } + } + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + static_cast(type_index))}; + } + } + + const auto requires_exhaustive{context.mode == Mode::Exhaustive || + requires_evaluation(context, schema_context)}; + + return {make(sourcemeta::blaze::InstructionIndex::LogicalOr, context, + schema_context, dynamic_context, + ValueBoolean{requires_exhaustive}, std::move(disjunctors))}; +} + +auto compiler_draft4_applicator_oneof(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_array()) { + return {}; + } + + assert(!schema_context.schema.at(dynamic_context.keyword).empty()); + + Instructions disjunctors; + for (std::uint64_t index = 0; + index < schema_context.schema.at(dynamic_context.keyword).size(); + index++) { + disjunctors.push_back(make( + sourcemeta::blaze::InstructionIndex::ControlGroup, context, + schema_context, relative_dynamic_context(), ValueNone{}, + compile( + context, schema_context, relative_dynamic_context(), + {static_cast(index)}))); + } + + const auto requires_exhaustive{context.mode == Mode::Exhaustive || + requires_evaluation(context, schema_context)}; + + return {make(sourcemeta::blaze::InstructionIndex::LogicalXor, context, + schema_context, dynamic_context, + ValueBoolean{requires_exhaustive}, std::move(disjunctors))}; +} + +// There are two ways to compile `properties` depending on whether +// most of the properties are marked as required using `required` +// or whether most of the properties are optional. Each shines +// in the corresponding case. +auto properties_as_loop(const Context &context, + const SchemaContext &schema_context, + const sourcemeta::core::JSON &properties) -> bool { + if (context.tweaks.properties_always_unroll) { + return false; + } + + using Known = sourcemeta::core::Vocabularies::Known; + const auto size{properties.size()}; + const auto imports_validation_vocabulary = + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_4) || + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_6) || + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_7) || + schema_context.vocabularies.contains( + Known::JSON_Schema_2019_09_Validation) || + schema_context.vocabularies.contains( + Known::JSON_Schema_2020_12_Validation); + const auto imports_const = + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_6) || + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_7) || + schema_context.vocabularies.contains( + Known::JSON_Schema_2019_09_Validation) || + schema_context.vocabularies.contains( + Known::JSON_Schema_2020_12_Validation); + std::set required; + if (imports_validation_vocabulary && + schema_context.schema.defines("required") && + schema_context.schema.at("required").is_array()) { + for (const auto &property : + schema_context.schema.at("required").as_array()) { + if (property.is_string() && + // Only count the required property if its indeed in "properties" + properties.defines(property.to_string())) { + required.insert(property.to_string()); + } + } + } + + const auto ¤t_entry{static_frame_entry(context, schema_context)}; + const auto inside_disjunctor{ + is_inside_disjunctor(schema_context.relative_pointer) || + // Check if any reference from `anyOf` or `oneOf` points to us + std::ranges::any_of( + context.frame.references(), + [&context, ¤t_entry](const auto &reference) { + if (!context.frame.locations().contains( + {sourcemeta::core::SchemaReferenceType::Static, + reference.second.destination})) { + return false; + } + + const auto &target{ + context.frame.locations() + .at({sourcemeta::core::SchemaReferenceType::Static, + reference.second.destination}) + .pointer}; + return is_inside_disjunctor(reference.first.second) && + current_entry.pointer.initial() == target; + })}; + + if (!inside_disjunctor && + schema_context.schema.defines("additionalProperties") && + schema_context.schema.at("additionalProperties").is_boolean() && + !schema_context.schema.at("additionalProperties").to_boolean() && + // If all properties are required, we should still unroll + required.size() < size) { + return true; + } + + return + // This strategy only makes sense if most of the properties are "optional" + required.size() <= (size / 4) && + // If `properties` only defines a relatively small amount of properties, + // then its probably still faster to unroll + size > 5 && + // Always unroll inside `oneOf` or `anyOf`, to have a + // better chance at quickly short-circuiting + (!inside_disjunctor || + std::ranges::none_of(properties.as_object(), [&](const auto &pair) { + return pair.second.is_object() && + ((imports_validation_vocabulary && + pair.second.defines("enum")) || + (imports_const && pair.second.defines("const"))); + })); +} + +auto is_integer_type_check(const Instruction &instruction) -> bool { + return (instruction.type == InstructionIndex::AssertionType || + instruction.type == InstructionIndex::AssertionTypeStrict) && + std::get(instruction.value) == + sourcemeta::core::JSON::Type::Integer; +} + +auto has_strict_integer_type(const Instructions &children) -> bool { + for (const auto &child : children) { + if (child.type == InstructionIndex::AssertionTypeStrict && + std::get(child.value) == + sourcemeta::core::JSON::Type::Integer) { + return true; + } + } + + return false; +} + +auto is_integer_type_bounded_pattern(const Instructions &children) -> bool { + if (children.size() != 3) { + return false; + } + + bool has_type{false}; + bool has_min{false}; + bool has_max{false}; + for (const auto &child : children) { + if (is_integer_type_check(child)) { + has_type = true; + } else if (child.type == InstructionIndex::AssertionGreaterEqual && + std::get(child.value).is_integer()) { + has_min = true; + } else if (child.type == InstructionIndex::AssertionLessEqual && + std::get(child.value).is_integer()) { + has_max = true; + } + } + + return has_type && has_min && has_max; +} + +auto is_integer_type_lower_bound_pattern(const Instructions &children) -> bool { + if (children.size() != 2) { + return false; + } + + bool has_type{false}; + bool has_min{false}; + for (const auto &child : children) { + if (is_integer_type_check(child)) { + has_type = true; + } else if (child.type == InstructionIndex::AssertionGreaterEqual && + std::get(child.value).is_integer()) { + has_min = true; + } + } + + return has_type && has_min; +} + +auto extract_integer_lower_bound(const Instructions &children) -> std::int64_t { + for (const auto &child : children) { + if (child.type == InstructionIndex::AssertionGreaterEqual) { + return std::get(child.value).to_integer(); + } + } + + return 0; +} + +auto extract_integer_bounds(const Instructions &children) + -> ValueIntegerBounds { + std::int64_t minimum{0}; + std::int64_t maximum{0}; + for (const auto &child : children) { + if (child.type == InstructionIndex::AssertionGreaterEqual) { + minimum = std::get(child.value).to_integer(); + } else if (child.type == InstructionIndex::AssertionLessEqual) { + maximum = std::get(child.value).to_integer(); + } + } + + return {minimum, maximum}; +} + +auto compiler_draft4_applicator_properties_with_options( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions ¤t, + const bool annotate, const bool track_evaluation) -> Instructions { + if (schema_context.is_property_name) { + return {}; + } + + if (!schema_context.schema.at(dynamic_context.keyword).is_object()) { + return {}; + } + + if (schema_context.schema.at(dynamic_context.keyword).empty()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + if (properties_as_loop(context, schema_context, + schema_context.schema.at(dynamic_context.keyword))) { + ValueNamedIndexes indexes; + Instructions children; + std::size_t cursor = 0; + + for (auto &&[name, substeps] : compile_properties( + context, schema_context, relative_dynamic_context(), current)) { + indexes.emplace(name, cursor); + + if (track_evaluation) { + substeps.push_back(make( + sourcemeta::blaze::InstructionIndex::ControlEvaluate, context, + schema_context, relative_dynamic_context(), ValuePointer{name})); + } + + if (annotate) { + substeps.push_back( + make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, relative_dynamic_context(), + sourcemeta::core::JSON{name})); + } + + // Note that the evaluator completely ignores this wrapper anyway + children.push_back(make(sourcemeta::blaze::InstructionIndex::ControlGroup, + context, schema_context, + relative_dynamic_context(), ValueNone{}, + std::move(substeps))); + cursor += 1; + } + + if (context.mode == Mode::FastValidation && !track_evaluation && + !schema_context.schema.defines("patternProperties") && + schema_context.schema.defines("additionalProperties") && + schema_context.schema.at("additionalProperties").is_boolean() && + !schema_context.schema.at("additionalProperties").to_boolean()) { + return { + make(sourcemeta::blaze::InstructionIndex::LoopPropertiesMatchClosed, + context, schema_context, dynamic_context, std::move(indexes), + std::move(children))}; + } + + return {make(sourcemeta::blaze::InstructionIndex::LoopPropertiesMatch, + context, schema_context, dynamic_context, std::move(indexes), + std::move(children))}; + } + + Instructions children; + + const auto effective_dynamic_context{context.mode == Mode::FastValidation + ? dynamic_context + : relative_dynamic_context()}; + + const auto assume_object{schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == + "object"}; + + auto properties{compile_properties(context, schema_context, + effective_dynamic_context, current)}; + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("additionalProperties") && + schema_context.schema.at("additionalProperties").is_boolean() && + !schema_context.schema.at("additionalProperties").to_boolean() && + // TODO: Check that the validation vocabulary is present + schema_context.schema.defines("required") && + schema_context.schema.at("required").is_array() && + schema_context.schema.at("required").size() == + schema_context.schema.at(dynamic_context.keyword).size() && + std::ranges::all_of(properties, [&schema_context](const auto &property) { + return schema_context.schema.at("required") + .contains(sourcemeta::core::JSON{property.first}); + })) { + if (std::ranges::all_of(properties, [](const auto &property) { + return property.second.size() == 1 && + property.second.front().type == + InstructionIndex::AssertionTypeStrict; + })) { + std::set types; + for (const auto &property : properties) { + types.insert(std::get(property.second.front().value)); + } + + if (types.size() == 1 && + !schema_context.schema.defines("patternProperties")) { + if (schema_context.schema.defines("required") && assume_object) { + auto required_copy = schema_context.schema.at("required"); + std::ranges::sort(required_copy.as_array()); + ValueStringSet required{json_array_to_string_set(required_copy)}; + if (is_closed_properties_required(schema_context.schema, required)) { + sourcemeta::core::PropertyHashJSON hasher; + std::vector> + perfect_hashes; + for (const auto &entry : required) { + assert(required.contains(entry.first, entry.second)); + if (hasher.is_perfect(entry.second)) { + perfect_hashes.emplace_back(entry.first, entry.second); + } + } + + if (perfect_hashes.size() == required.size()) { + return {make(sourcemeta::blaze::InstructionIndex:: + LoopPropertiesExactlyTypeStrictHash, + context, schema_context, dynamic_context, + ValueTypedHashes{*types.cbegin(), + to_string_hashes(perfect_hashes)})}; + } + + return {make( + sourcemeta::blaze::InstructionIndex:: + LoopPropertiesExactlyTypeStrict, + context, schema_context, dynamic_context, + ValueTypedProperties{*types.cbegin(), std::move(required)})}; + } + } + + return { + make(sourcemeta::blaze::InstructionIndex::LoopPropertiesTypeStrict, + context, schema_context, dynamic_context, *types.cbegin())}; + } + } + + if (std::ranges::all_of(properties, [](const auto &property) { + return property.second.size() == 1 && + property.second.front().type == + InstructionIndex::AssertionType; + })) { + std::set types; + for (const auto &property : properties) { + types.insert(std::get(property.second.front().value)); + } + + if (types.size() == 1) { + return {make(sourcemeta::blaze::InstructionIndex::LoopPropertiesType, + context, schema_context, dynamic_context, + *types.cbegin())}; + } + } + } + + auto attempt_object_fusion{context.mode == Mode::FastValidation && + !annotate && !track_evaluation && assume_object}; + if (attempt_object_fusion) { + for (const auto &entry : schema_context.schema.as_object()) { + const auto &keyword{entry.first}; + if (keyword == "type" || keyword == "required" || + keyword == dynamic_context.keyword) { + continue; + } + + if (keyword == "additionalProperties" && entry.second.is_boolean() && + entry.second.to_boolean()) { + continue; + } + + const auto &keyword_type{ + context.walker(keyword, schema_context.vocabularies).type}; + using enum sourcemeta::core::SchemaKeywordType; + if (keyword_type == Assertion || keyword_type == Annotation || + keyword_type == Unknown || keyword_type == Comment || + keyword_type == Other || keyword_type == LocationMembers) { + continue; + } + + attempt_object_fusion = false; + break; + } + } + ValueObjectProperties fusion_entries; + Instructions fusion_children; + bool fusion_possible{attempt_object_fusion}; + + for (auto &&[name, substeps] : properties) { + if (annotate) { + substeps.push_back( + make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, effective_dynamic_context, + sourcemeta::core::JSON{name})); + } + + // Optimize `properties` where its subschemas just include a type check + + if (context.mode == Mode::FastValidation && track_evaluation && + substeps.size() == 1 && + substeps.front().type == InstructionIndex::AssertionTypeStrict) { + children.push_back(rephrase(context, + sourcemeta::blaze::InstructionIndex:: + AssertionPropertyTypeStrictEvaluate, + substeps.front())); + } else if (context.mode == Mode::FastValidation && track_evaluation && + substeps.size() == 1 && + substeps.front().type == InstructionIndex::AssertionType) { + children.push_back(rephrase( + context, + sourcemeta::blaze::InstructionIndex::AssertionPropertyTypeEvaluate, + substeps.front())); + } else if (context.mode == Mode::FastValidation && track_evaluation && + substeps.size() == 1 && + substeps.front().type == + InstructionIndex::AssertionTypeStrictAny) { + children.push_back(rephrase(context, + sourcemeta::blaze::InstructionIndex:: + AssertionPropertyTypeStrictAnyEvaluate, + substeps.front())); + + // NOLINTBEGIN(bugprone-branch-clone) + } else if (!fusion_possible && context.mode == Mode::FastValidation && + substeps.size() == 1 && + substeps.front().type == + InstructionIndex::AssertionPropertyTypeStrict) { + children.push_back( + unroll(context, substeps.front(), + effective_dynamic_context.base_instance_location)); + } else if (!fusion_possible && context.mode == Mode::FastValidation && + substeps.size() == 1 && + substeps.front().type == + InstructionIndex::AssertionPropertyType) { + children.push_back( + unroll(context, substeps.front(), + effective_dynamic_context.base_instance_location)); + } else if (!fusion_possible && context.mode == Mode::FastValidation && + substeps.size() == 1 && + substeps.front().type == + InstructionIndex::AssertionPropertyTypeStrictAny) { + children.push_back( + unroll(context, substeps.front(), + effective_dynamic_context.base_instance_location)); + // NOLINTEND(bugprone-branch-clone) + + } else { + if (track_evaluation) { + auto new_base_instance_location{ + effective_dynamic_context.base_instance_location}; + new_base_instance_location.push_back({name}); + substeps.push_back( + make(sourcemeta::blaze::InstructionIndex::Evaluate, context, + schema_context, + DynamicContext{ + .keyword = effective_dynamic_context.keyword, + .base_schema_location = + effective_dynamic_context.base_schema_location, + .base_instance_location = new_base_instance_location}, + ValueNone{})); + } + + if (context.mode == Mode::FastValidation && !substeps.empty()) { + if (is_integer_type_bounded_pattern(substeps)) { + auto bounds = extract_integer_bounds(substeps); + const auto index = + has_strict_integer_type(substeps) + ? InstructionIndex::AssertionTypeIntegerBoundedStrict + : InstructionIndex::AssertionTypeIntegerBounded; + auto instance_location = substeps.front().relative_instance_location; + substeps.clear(); + auto fused = make(index, context, schema_context, + relative_dynamic_context(), bounds); + fused.relative_instance_location = std::move(instance_location); + substeps.push_back(std::move(fused)); + } else if (is_integer_type_lower_bound_pattern(substeps)) { + const auto minimum = extract_integer_lower_bound(substeps); + const auto index = + has_strict_integer_type(substeps) + ? InstructionIndex::AssertionTypeIntegerLowerBoundStrict + : InstructionIndex::AssertionTypeIntegerLowerBound; + auto instance_location = substeps.front().relative_instance_location; + substeps.clear(); + auto fused = + make(index, context, schema_context, relative_dynamic_context(), + ValueIntegerBounds{minimum, 0}); + fused.relative_instance_location = std::move(instance_location); + substeps.push_back(std::move(fused)); + } else if (substeps.size() == 2) { + bool has_items_bounded{false}; + bool has_array_type{false}; + std::size_t items_index{0}; + std::size_t array_index{0}; + for (std::size_t step_index = 0; step_index < 2; step_index++) { + if (substeps[step_index].type == + InstructionIndex::LoopItemsIntegerBounded) { + has_items_bounded = true; + items_index = step_index; + } else if (substeps[step_index].type == + InstructionIndex::AssertionTypeArrayBounded) { + has_array_type = true; + array_index = step_index; + } + } + + if (has_items_bounded && has_array_type) { + auto integer_bounds{ + std::get(substeps[items_index].value)}; + auto range{std::get(substeps[array_index].value)}; + auto instance_location = + substeps[items_index].relative_instance_location; + Value fused_value{ + ValueIntegerBoundsWithSize{integer_bounds, std::move(range)}}; + substeps.clear(); + auto fused = + make(InstructionIndex::LoopItemsIntegerBoundedSized, context, + schema_context, effective_dynamic_context, fused_value); + fused.relative_instance_location = std::move(instance_location); + substeps.push_back(std::move(fused)); + } + } + } + + if (fusion_possible && substeps.size() >= 2 && + std::ranges::any_of(substeps, [](const auto &step) { + return step.type == + InstructionIndex::AssertionObjectPropertiesSimple; + })) { + std::erase_if(substeps, [](const auto &step) { + if (step.type == InstructionIndex::AssertionDefinesAllStrict || + step.type == InstructionIndex::AssertionDefinesAll) { + return true; + } + + if ((step.type == InstructionIndex::AssertionTypeStrict || + step.type == InstructionIndex::AssertionType) && + std::get(step.value) == + sourcemeta::core::JSON::Type::Object) { + return true; + } + + return false; + }); + } + + if (fusion_possible && substeps.size() == 1 && + substeps.front().type != InstructionIndex::ControlJump && + substeps.front().type != InstructionIndex::ControlDynamicAnchorJump) { + const auto is_required{ + assume_object && schema_context.schema.defines("required") && + schema_context.schema.at("required").is_array() && + schema_context.schema.at("required") + .contains(sourcemeta::core::JSON{name})}; + auto prop{make_property(name)}; + auto fusion_child{substeps.front()}; + fusion_child.relative_instance_location = {}; + auto fusion_extra{context.extra[fusion_child.extra_index]}; + fusion_extra.relative_schema_location = {}; + fusion_child.extra_index = context.extra.size(); + context.extra.push_back(std::move(fusion_extra)); + + fusion_entries.emplace_back(prop.first, prop.second, is_required); + fusion_children.push_back(std::move(fusion_child)); + } else { + fusion_possible = false; + } + + if (!substeps.empty()) { + // As a performance shortcut + if (effective_dynamic_context.base_instance_location.empty()) { + if (assume_object && + // TODO: Check that the validation vocabulary is present + schema_context.schema.defines("required") && + schema_context.schema.at("required").is_array() && + schema_context.schema.at("required") + .contains(sourcemeta::core::JSON{name})) { + for (auto &&step : substeps) { + children.push_back(std::move(step)); + } + } else { + children.push_back(make(sourcemeta::blaze::InstructionIndex:: + ControlGroupWhenDefinesDirect, + context, schema_context, + effective_dynamic_context, + make_property(name), std::move(substeps))); + } + } else { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::ControlGroupWhenDefines, + context, schema_context, effective_dynamic_context, + make_property(name), std::move(substeps))); + } + } + } + } + + if (context.mode == Mode::FastValidation) { + if (fusion_possible && !fusion_entries.empty()) { + if (schema_context.schema.defines("required") && + schema_context.schema.at("required").is_array()) { + for (const auto &req : + schema_context.schema.at("required").as_array()) { + if (!req.is_string()) { + continue; + } + const auto &req_name{req.to_string()}; + bool already_tracked{false}; + for (const auto &entry : fusion_entries) { + if (std::get<0>(entry) == req_name) { + already_tracked = true; + break; + } + } + if (!already_tracked) { + auto prop{make_property(req_name)}; + fusion_entries.emplace_back(prop.first, prop.second, true); + } + } + } + + return {make(InstructionIndex::AssertionObjectPropertiesSimple, context, + schema_context, dynamic_context, + Value{std::move(fusion_entries)}, + std::move(fusion_children))}; + } + + return children; + } else if (children.empty()) { + return {}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::LogicalWhenType, context, + schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Object, std::move(children))}; + } +} + +auto compiler_draft4_applicator_properties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions ¤t) + -> Instructions { + return compiler_draft4_applicator_properties_with_options( + context, schema_context, dynamic_context, current, false, false); +} + +auto compiler_draft4_applicator_patternproperties_with_options( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const bool annotate, + const bool track_evaluation) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_object()) { + return {}; + } + + if (schema_context.schema.at(dynamic_context.keyword).empty()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + Instructions children; + + // To guarantee ordering + std::vector patterns; + for (auto &entry : + schema_context.schema.at(dynamic_context.keyword).as_object()) { + patterns.push_back(entry.first); + } + + std::ranges::sort(patterns); + + // For each regular expression and corresponding subschema in the object + for (const auto &pattern : patterns) { + auto substeps{compile(context, schema_context, relative_dynamic_context(), + sourcemeta::blaze::make_weak_pointer(pattern))}; + + if (annotate) { + substeps.push_back(make( + sourcemeta::blaze::InstructionIndex::AnnotationBasenameToParent, + context, schema_context, relative_dynamic_context(), ValueNone{})); + } + + if (track_evaluation) { + substeps.push_back( + make(sourcemeta::blaze::InstructionIndex::ControlEvaluate, context, + schema_context, relative_dynamic_context(), ValuePointer{})); + } + + if (context.mode == Mode::FastValidation && !track_evaluation && + patterns.size() == 1 && + (!schema_context.schema.defines("properties") || + (schema_context.schema.at("properties").is_object() && + schema_context.schema.at("properties").empty())) && + schema_context.schema.defines("additionalProperties") && + schema_context.schema.at("additionalProperties").is_boolean() && + !schema_context.schema.at("additionalProperties").to_boolean()) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::LoopPropertiesRegexClosed, + context, schema_context, dynamic_context, + ValueRegex{.first = parse_regex(pattern, schema_context.base, + schema_context.relative_pointer), + .second = pattern}, + std::move(substeps))); + + // If the `patternProperties` subschema for the given pattern does + // nothing, then we can avoid generating an entire loop for it + } else if (!substeps.empty()) { + const auto maybe_prefix{pattern_as_prefix(pattern)}; + if (maybe_prefix.has_value()) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::LoopPropertiesStartsWith, + context, schema_context, dynamic_context, + ValueString{maybe_prefix.value()}, std::move(substeps))); + } else { + children.push_back(make( + sourcemeta::blaze::InstructionIndex::LoopPropertiesRegex, context, + schema_context, dynamic_context, + ValueRegex{.first = parse_regex(pattern, schema_context.base, + schema_context.relative_pointer), + .second = pattern}, + std::move(substeps))); + } + } + } + + return children; +} + +auto compiler_draft4_applicator_patternproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + return compiler_draft4_applicator_patternproperties_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft4_applicator_additionalproperties_with_options( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const bool annotate, + const bool track_evaluation) -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + if (annotate) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::AnnotationBasenameToParent, + context, schema_context, relative_dynamic_context(), ValueNone{})); + } + + ValueStringSet filter_strings; + ValueStrings filter_prefixes; + std::vector filter_regexes; + + if (schema_context.schema.defines("properties") && + schema_context.schema.at("properties").is_object()) { + for (const auto &entry : + schema_context.schema.at("properties").as_object()) { + filter_strings.insert(entry.first); + } + } + + if (schema_context.schema.defines("patternProperties") && + schema_context.schema.at("patternProperties").is_object()) { + for (const auto &entry : + schema_context.schema.at("patternProperties").as_object()) { + const auto maybe_prefix{pattern_as_prefix(entry.first)}; + if (maybe_prefix.has_value()) { + filter_prefixes.push_back(maybe_prefix.value()); + } else { + static const std::string pattern_properties_keyword{ + "patternProperties"}; + filter_regexes.push_back( + {parse_regex(entry.first, schema_context.base, + schema_context.relative_pointer.initial().concat( + sourcemeta::blaze::make_weak_pointer( + pattern_properties_keyword))), + entry.first}); + } + } + } + + // For performance, if a schema sets `additionalProperties: true` (or its + // variants), we don't need to do anything + if (!track_evaluation && children.empty()) { + return {}; + } + + // When `additionalProperties: false` with only `properties` (no + // patternProperties), and `properties` is compiled as a loop + // (LoopPropertiesMatchClosed), that loop already handles rejecting unknown + // properties, so we don't need to emit anything for `additionalProperties` + if (context.mode == Mode::FastValidation && children.size() == 1 && + children.front().type == InstructionIndex::AssertionFail && + !filter_strings.empty() && filter_prefixes.empty() && + filter_regexes.empty() && + properties_as_loop(context, schema_context, + schema_context.schema.at("properties"))) { + return {}; + } + + // When all properties are required and `additionalProperties: false`, + // the `required` keyword compiles to `AssertionDefinesExactly` which already + // checks that the object has exactly the required properties, so we don't + // need to emit anything for `additionalProperties` + if (context.mode == Mode::FastValidation && children.size() == 1 && + children.front().type == InstructionIndex::AssertionFail && + !filter_strings.empty() && filter_prefixes.empty() && + filter_regexes.empty() && schema_context.schema.defines("required") && + schema_context.schema.at("required").is_array() && + is_closed_properties_required( + schema_context.schema, + json_array_to_string_set(schema_context.schema.at("required")))) { + return {}; + } + + if (context.mode == Mode::FastValidation && filter_strings.empty() && + filter_prefixes.empty() && filter_regexes.size() == 1 && + !track_evaluation && !children.empty() && + children.front().type == InstructionIndex::AssertionFail) { + return {}; + } + + if (!filter_strings.empty() || !filter_prefixes.empty() || + !filter_regexes.empty()) { + if (track_evaluation) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::ControlEvaluate, context, + schema_context, relative_dynamic_context(), ValuePointer{})); + } + + return {make(sourcemeta::blaze::InstructionIndex::LoopPropertiesExcept, + context, schema_context, dynamic_context, + ValuePropertyFilter{std::move(filter_strings), + std::move(filter_prefixes), + std::move(filter_regexes)}, + std::move(children))}; + } else if (track_evaluation) { + if (children.empty()) { + return {make(sourcemeta::blaze::InstructionIndex::Evaluate, context, + schema_context, dynamic_context, ValueNone{})}; + } + + return {make(sourcemeta::blaze::InstructionIndex::LoopPropertiesEvaluate, + context, schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } else if (children.size() == 1 && + children.front().type == InstructionIndex::AssertionFail) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionObjectSizeLess, + context, schema_context, dynamic_context, + ValueUnsignedInteger{1})}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::LoopProperties, context, + schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } +} + +auto compiler_draft4_applicator_additionalproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + return compiler_draft4_applicator_additionalproperties_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft4_validation_pattern(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_string()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + + const auto ®ex_string{ + schema_context.schema.at(dynamic_context.keyword).to_string()}; + return { + make(sourcemeta::blaze::InstructionIndex::AssertionRegex, context, + schema_context, dynamic_context, + ValueRegex{.first = parse_regex(regex_string, schema_context.base, + schema_context.relative_pointer), + .second = regex_string})}; +} + +auto compiler_draft4_applicator_not(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + std::size_t subschemas{0}; + for (const auto &subschema : + walk_subschemas(context, schema_context, dynamic_context)) { + if (subschema.pointer.empty()) { + continue; + } + + subschemas += 1; + } + + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + // TODO: Be smarter about how we treat `unevaluatedItems` like how we do for + // `unevaluatedProperties` + const bool track_items{ + std::ranges::any_of(context.unevaluated, [](const auto &dependency) { + return dependency.first.ends_with("unevaluatedItems"); + })}; + + // Only emit a `not` instruction that keeps track of + // evaluation if we really need it. If the "not" subschema + // does not define applicators, then that's an easy case + // we can skip + if (subschemas > 0 && + (requires_evaluation(context, schema_context) || track_items)) { + return {make(sourcemeta::blaze::InstructionIndex::LogicalNotEvaluate, + context, schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::LogicalNot, context, + schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } +} + +auto compiler_draft4_applicator_items_array( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const bool annotate, + const bool track_evaluation) -> Instructions { + if (schema_context.is_property_name) { + return {}; + } + + if (!schema_context.schema.at(dynamic_context.keyword).is_array()) { + return {}; + } + + const auto items_size{ + schema_context.schema.at(dynamic_context.keyword).size()}; + if (items_size == 0) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + // Precompile subschemas + std::vector subschemas; + subschemas.reserve(items_size); + const auto &array{ + schema_context.schema.at(dynamic_context.keyword).as_array()}; + for (auto iterator{array.cbegin()}; iterator != array.cend(); ++iterator) { + subschemas.push_back(compile(context, schema_context, + relative_dynamic_context(), + {subschemas.size()}, {subschemas.size()})); + } + + Instructions children; + for (std::size_t cursor = 0; cursor < items_size; cursor++) { + Instructions subchildren; + for (std::size_t index = 0; index < cursor + 1; index++) { + for (const auto &substep : subschemas.at(index)) { + subchildren.push_back(substep); + } + } + + if (annotate) { + subchildren.push_back( + make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, context, + schema_context, relative_dynamic_context(), + sourcemeta::core::JSON{cursor})); + } + + children.push_back(make(sourcemeta::blaze::InstructionIndex::ControlGroup, + context, schema_context, relative_dynamic_context(), + ValueNone{}, std::move(subchildren))); + } + + Instructions tail; + for (const auto &subschema : subschemas) { + for (const auto &substep : subschema) { + tail.push_back(substep); + } + } + + if (annotate) { + tail.push_back(make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON{children.size() - 1})); + tail.push_back(make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON{true})); + } + + children.push_back(make(sourcemeta::blaze::InstructionIndex::ControlGroup, + context, schema_context, relative_dynamic_context(), + ValueNone{}, std::move(tail))); + + if (track_evaluation) { + return { + make(sourcemeta::blaze::InstructionIndex::AssertionArrayPrefixEvaluate, + context, schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::AssertionArrayPrefix, + context, schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } +} + +auto is_number_type_check(const Instruction &instruction) -> bool { + if (instruction.type != InstructionIndex::AssertionTypeStrictAny) { + return false; + } + + const auto &value{std::get(instruction.value)}; + const auto numeric_count{ + static_cast(value.test( + std::to_underlying(sourcemeta::core::JSON::Type::Integer))) + + static_cast( + value.test(std::to_underlying(sourcemeta::core::JSON::Type::Real))) + + static_cast(value.test( + std::to_underlying(sourcemeta::core::JSON::Type::Decimal)))}; + return numeric_count >= 2 && value.count() == numeric_count; +} + +auto is_integer_bounded_pattern(const Instructions &children) -> bool { + if (children.size() != 3) { + return false; + } + + bool has_type{false}; + bool has_min{false}; + bool has_max{false}; + for (const auto &child : children) { + if (is_number_type_check(child)) { + has_type = true; + } else if (child.type == InstructionIndex::AssertionGreaterEqual) { + if (!std::get(child.value).is_integer()) { + return false; + } + has_min = true; + } else if (child.type == InstructionIndex::AssertionLessEqual) { + if (!std::get(child.value).is_integer()) { + return false; + } + has_max = true; + } + } + + return has_type && has_min && has_max; +} + +auto compiler_draft4_applicator_items_with_options( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const bool annotate, + const bool track_evaluation) -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + if (is_schema(schema_context.schema.at(dynamic_context.keyword))) { + if (annotate || track_evaluation) { + Instructions subchildren{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + Instructions children; + + if (!subchildren.empty()) { + children.push_back(make(sourcemeta::blaze::InstructionIndex::LoopItems, + context, schema_context, dynamic_context, + ValueNone{}, std::move(subchildren))); + } + + if (!annotate && !track_evaluation) { + return children; + } + + Instructions tail; + + if (annotate) { + tail.push_back(make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON{true})); + } + + if (track_evaluation) { + tail.push_back( + make(sourcemeta::blaze::InstructionIndex::ControlEvaluate, context, + schema_context, relative_dynamic_context(), ValuePointer{})); + } + + children.push_back( + make(sourcemeta::blaze::InstructionIndex::LogicalWhenType, context, + schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Array, std::move(tail))); + + return children; + } + + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + if (track_evaluation) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::ControlEvaluate, context, + schema_context, relative_dynamic_context(), ValuePointer{})); + } + + if (children.empty()) { + return {}; + } + + if (context.mode == Mode::FastValidation && children.size() == 3 && + is_integer_bounded_pattern(children)) { + return {make(sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded, + context, schema_context, dynamic_context, + extract_integer_bounds(children))}; + } + + if (context.mode == Mode::FastValidation && children.size() == 1) { + if (children.front().type == InstructionIndex::AssertionTypeStrict) { + return {make(sourcemeta::blaze::InstructionIndex::LoopItemsTypeStrict, + context, schema_context, dynamic_context, + children.front().value)}; + } else if (children.front().type == InstructionIndex::AssertionType) { + return {make(sourcemeta::blaze::InstructionIndex::LoopItemsType, + context, schema_context, dynamic_context, + children.front().value)}; + } else if (children.front().type == + InstructionIndex::AssertionTypeStrictAny) { + return {make( + sourcemeta::blaze::InstructionIndex::LoopItemsTypeStrictAny, + context, schema_context, dynamic_context, children.front().value)}; + } else if (children.front().type == + InstructionIndex::LoopPropertiesExactlyTypeStrictHash) { + auto value_copy = children.front().value; + auto current{make(sourcemeta::blaze::InstructionIndex::LoopItems, + context, schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + if (std::get(value_copy).second.first.size() == 3) { + return {Instruction{.type = sourcemeta::blaze::InstructionIndex:: + LoopItemsPropertiesExactlyTypeStrictHash3, + .relative_instance_location = + current.relative_instance_location, + .value = std::move(value_copy), + .children = {}, + .extra_index = current.extra_index}}; + } + + return {Instruction{.type = sourcemeta::blaze::InstructionIndex:: + LoopItemsPropertiesExactlyTypeStrictHash, + .relative_instance_location = + current.relative_instance_location, + .value = std::move(value_copy), + .children = {}, + .extra_index = current.extra_index}}; + } + } + + return {make(sourcemeta::blaze::InstructionIndex::LoopItems, context, + schema_context, dynamic_context, ValueNone{}, + std::move(children))}; + } + + return compiler_draft4_applicator_items_array( + context, schema_context, dynamic_context, annotate, track_evaluation); +} + +auto compiler_draft4_applicator_items(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + return compiler_draft4_applicator_items_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft4_applicator_additionalitems_from_cursor( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const std::size_t cursor, + const bool annotate, const bool track_evaluation) -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + Instructions subchildren{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + Instructions children; + + if (!subchildren.empty()) { + if (context.mode == Mode::FastValidation && cursor == 0 && !annotate && + !track_evaluation && is_integer_bounded_pattern(subchildren)) { + children.push_back( + make(sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded, + context, schema_context, dynamic_context, + extract_integer_bounds(subchildren))); + return children; + } + + children.push_back(make(sourcemeta::blaze::InstructionIndex::LoopItemsFrom, + context, schema_context, dynamic_context, + ValueUnsignedInteger{cursor}, + std::move(subchildren))); + } + + // Avoid one extra wrapper instruction if possible + if (!annotate && !track_evaluation) { + return children; + } + + Instructions tail; + + if (annotate) { + tail.push_back(make(sourcemeta::blaze::InstructionIndex::AnnotationEmit, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON{true})); + } + + if (track_evaluation) { + tail.push_back(make(sourcemeta::blaze::InstructionIndex::ControlEvaluate, + context, schema_context, relative_dynamic_context(), + ValuePointer{})); + } + + assert(!tail.empty()); + children.push_back( + make(sourcemeta::blaze::InstructionIndex::LogicalWhenArraySizeGreater, + context, schema_context, dynamic_context, + ValueUnsignedInteger{cursor}, std::move(tail))); + + return children; +} + +auto compiler_draft4_applicator_additionalitems_with_options( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const bool annotate, + const bool track_evaluation) -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + assert(schema_context.schema.is_object()); + + // Nothing to do here + if (!schema_context.schema.defines("items") || + schema_context.schema.at("items").is_object()) { + return {}; + } + + const auto cursor{(schema_context.schema.defines("items") && + schema_context.schema.at("items").is_array()) + ? schema_context.schema.at("items").size() + : 0}; + + return compiler_draft4_applicator_additionalitems_from_cursor( + context, schema_context, dynamic_context, cursor, annotate, + track_evaluation); +} + +auto compiler_draft4_applicator_additionalitems( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + return compiler_draft4_applicator_additionalitems_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft4_applicator_dependencies( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + if (!schema_context.schema.at(dynamic_context.keyword).is_object()) { + return {}; + } + + Instructions children; + ValueStringMap dependencies; + + for (const auto &entry : + schema_context.schema.at(dynamic_context.keyword).as_object()) { + if (is_schema(entry.second)) { + if (!entry.second.is_boolean() || !entry.second.to_boolean()) { + children.push_back(make( + sourcemeta::blaze::InstructionIndex::LogicalWhenDefines, context, + schema_context, dynamic_context, make_property(entry.first), + compile(context, schema_context, relative_dynamic_context(), + sourcemeta::blaze::make_weak_pointer(entry.first)))); + } + } else if (entry.second.is_array()) { + std::vector properties; + for (const auto &property : entry.second.as_array()) { + assert(property.is_string()); + properties.push_back(property.to_string()); + } + + if (!properties.empty()) { + dependencies.emplace(entry.first, properties); + } + } + } + + if (!dependencies.empty()) { + children.push_back(make( + sourcemeta::blaze::InstructionIndex::AssertionPropertyDependencies, + context, schema_context, dynamic_context, std::move(dependencies))); + } + + return children; +} + +auto compiler_draft4_validation_enum(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_array()) { + return {}; + } + + if (schema_context.schema.at(dynamic_context.keyword).size() == 1) { + return { + make(sourcemeta::blaze::InstructionIndex::AssertionEqual, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword).front()})}; + } + + std::vector> + perfect_string_hashes; + ValueSet options; + sourcemeta::core::PropertyHashJSON hasher; + for (const auto &option : + schema_context.schema.at(dynamic_context.keyword).as_array()) { + if (option.is_string()) { + const auto hash{hasher(option.to_string())}; + if (hasher.is_perfect(hash)) { + perfect_string_hashes.emplace_back(option.to_string(), hash); + } + } + + options.insert(option); + } + + // Only apply this optimisation on fast validation, as it + // can affect error messages + if (context.mode == Mode::FastValidation && + perfect_string_hashes.size() == options.size()) { + return { + make(sourcemeta::blaze::InstructionIndex::AssertionEqualsAnyStringHash, + context, schema_context, dynamic_context, + to_string_hashes(perfect_string_hashes))}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionEqualsAny, context, + schema_context, dynamic_context, std::move(options))}; +} + +auto compiler_draft4_validation_uniqueitems( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_boolean() || + !schema_context.schema.at(dynamic_context.keyword).to_boolean()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionUnique, context, + schema_context, dynamic_context, ValueNone{})}; +} + +auto compiler_draft4_validation_maxlength(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_integral()) { + return {}; + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + + // We'll handle it at the type level as an optimization + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "string") { + return {}; + } + + return {make( + sourcemeta::blaze::InstructionIndex::AssertionStringSizeLess, context, + schema_context, dynamic_context, + ValueUnsignedInteger{ + static_cast( + schema_context.schema.at(dynamic_context.keyword).as_integer()) + + 1})}; +} + +auto compiler_draft4_validation_minlength(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_integral()) { + return {}; + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "string") { + return {}; + } + + // We'll handle it at the type level as an optimization + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "string") { + return {}; + } + + const auto value{static_cast( + schema_context.schema.at(dynamic_context.keyword).as_integer())}; + if (value <= 0) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionStringSizeGreater, + context, schema_context, dynamic_context, + ValueUnsignedInteger{value - 1})}; +} + +auto compiler_draft4_validation_maxitems(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_integral()) { + return {}; + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + // We'll handle it at the type level as an optimization + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "array") { + return {}; + } + + return {make( + sourcemeta::blaze::InstructionIndex::AssertionArraySizeLess, context, + schema_context, dynamic_context, + ValueUnsignedInteger{ + static_cast( + schema_context.schema.at(dynamic_context.keyword).as_integer()) + + 1})}; +} + +auto compiler_draft4_validation_minitems(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_integral()) { + return {}; + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + // We'll handle it at the type level as an optimization + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "array") { + return {}; + } + + const auto value{ + schema_context.schema.at(dynamic_context.keyword).as_integer()}; + if (value <= 0) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionArraySizeGreater, + context, schema_context, dynamic_context, + ValueUnsignedInteger{static_cast(value - 1)})}; +} + +auto compiler_draft4_validation_maxproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_integral()) { + return {}; + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + // We'll handle it at the type level as an optimization + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "object") { + return {}; + } + + return {make( + sourcemeta::blaze::InstructionIndex::AssertionObjectSizeLess, context, + schema_context, dynamic_context, + ValueUnsignedInteger{ + static_cast( + schema_context.schema.at(dynamic_context.keyword).as_integer()) + + 1})}; +} + +auto compiler_draft4_validation_minproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_integral()) { + return {}; + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + // We'll handle it at the type level as an optimization + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "object") { + return {}; + } + + const auto value{static_cast( + schema_context.schema.at(dynamic_context.keyword).as_integer())}; + if (value <= 0) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionObjectSizeGreater, + context, schema_context, dynamic_context, + ValueUnsignedInteger{value - 1})}; +} + +auto compiler_draft4_validation_maximum(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_number()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + + // TODO: As an optimization, if `minimum` is set to the same number, do + // a single equality assertion + + assert(schema_context.schema.is_object()); + if (schema_context.schema.defines("exclusiveMaximum") && + schema_context.schema.at("exclusiveMaximum").is_boolean() && + schema_context.schema.at("exclusiveMaximum").to_boolean()) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionLess, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::AssertionLessEqual, + context, schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + } +} + +auto compiler_draft4_validation_minimum(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_number()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + + // TODO: As an optimization, if `maximum` is set to the same number, do + // a single equality assertion + + assert(schema_context.schema.is_object()); + if (schema_context.schema.defines("exclusiveMinimum") && + schema_context.schema.at("exclusiveMinimum").is_boolean() && + schema_context.schema.at("exclusiveMinimum").to_boolean()) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionGreater, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + } else { + return {make(sourcemeta::blaze::InstructionIndex::AssertionGreaterEqual, + context, schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; + } +} + +auto compiler_draft4_validation_multipleof( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_number()) { + return {}; + } + + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionDivisible, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; +} + +} // namespace internal +#endif diff --git a/vendor/blaze/src/compiler/default_compiler_draft6.h b/vendor/blaze/src/compiler/default_compiler_draft6.h new file mode 100644 index 0000000..0388199 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler_draft6.h @@ -0,0 +1,432 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT6_H_ +#define SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT6_H_ + +#include + +#include // std::ranges::all_of +#include // std::to_underlying + +#include "compile_helpers.h" + +namespace internal { +using namespace sourcemeta::blaze; + +auto compiler_draft6_validation_type(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions ¤t) + -> Instructions { + if (schema_context.schema.at(dynamic_context.keyword).is_string()) { + const auto &type{ + schema_context.schema.at(dynamic_context.keyword).to_string()}; + if (type == "null") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_null(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("const") && + schema_context.schema.at("const").is_null()) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Null)}; + } else if (type == "boolean") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_boolean(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("const") && + schema_context.schema.at("const").is_boolean()) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Boolean)}; + } else if (type == "object") { + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minProperties", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxProperties")}; + + if (context.mode == Mode::FastValidation) { + if (maximum.has_value() && minimum == 0) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeObjectUpper, + context, schema_context, dynamic_context, + ValueUnsignedInteger{maximum.value()})}; + } else if (minimum > 0 || maximum.has_value()) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeObjectBounded, + context, schema_context, dynamic_context, + ValueRange{minimum, maximum, false})}; + } + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_object(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("const") && + schema_context.schema.at("const").is_object()) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("required")) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Object)}; + } else if (type == "array") { + if (context.mode == Mode::FastValidation && !current.empty() && + (current.back().type == + sourcemeta::blaze::InstructionIndex:: + LoopItemsPropertiesExactlyTypeStrictHash || + current.back().type == + sourcemeta::blaze::InstructionIndex:: + LoopItemsPropertiesExactlyTypeStrictHash3 || + current.back().type == + sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded || + current.back().type == sourcemeta::blaze::InstructionIndex:: + LoopItemsIntegerBoundedSized) && + current.back().relative_instance_location == + to_pointer(dynamic_context.base_instance_location)) { + return {}; + } + + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minItems", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxItems")}; + + if (context.mode == Mode::FastValidation) { + if (maximum.has_value() && minimum == 0) { + return { + make(sourcemeta::blaze::InstructionIndex::AssertionTypeArrayUpper, + context, schema_context, dynamic_context, + ValueUnsignedInteger{maximum.value()})}; + } else if (minimum > 0 || maximum.has_value()) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeArrayBounded, + context, schema_context, dynamic_context, + ValueRange{minimum, maximum, false})}; + } + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_array(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("const") && + schema_context.schema.at("const").is_array()) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Array)}; + } else if (type == "number") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_number(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("const") && + schema_context.schema.at("const").is_number()) { + return {}; + } + + ValueTypes types{}; + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Real)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; + } else if (type == "integer") { + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_integral(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("const") && + (schema_context.schema.at("const").is_integral())) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionType, context, + schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Integer)}; + } else if (type == "string") { + if (schema_context.is_property_name) { + return {}; + } + + const auto minimum{ + unsigned_integer_property(schema_context.schema, "minLength", 0)}; + const auto maximum{ + unsigned_integer_property(schema_context.schema, "maxLength")}; + + if (context.mode == Mode::FastValidation) { + if (maximum.has_value() && minimum == 0) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeStringUpper, + context, schema_context, dynamic_context, + ValueUnsignedInteger{maximum.value()})}; + } else if (minimum > 0 || maximum.has_value()) { + return {make( + sourcemeta::blaze::InstructionIndex::AssertionTypeStringBounded, + context, schema_context, dynamic_context, + ValueRange{minimum, maximum, false})}; + } + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("enum") && + schema_context.schema.at("enum").is_array() && + std::ranges::all_of( + schema_context.schema.at("enum").as_array(), + [](const auto &value) { return value.is_string(); })) { + return {}; + } + + if (context.mode == Mode::FastValidation && + schema_context.schema.defines("const") && + schema_context.schema.at("const").is_string()) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::String)}; + } else { + return {}; + } + } else if (schema_context.schema.at(dynamic_context.keyword).is_array() && + schema_context.schema.at(dynamic_context.keyword).size() == 1 && + schema_context.schema.at(dynamic_context.keyword) + .front() + .is_string()) { + const auto &type{ + schema_context.schema.at(dynamic_context.keyword).front().to_string()}; + if (type == "null") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Null)}; + } else if (type == "boolean") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Boolean)}; + } else if (type == "object") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Object)}; + } else if (type == "array") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Array)}; + } else if (type == "number") { + ValueTypes types{}; + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Real)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; + } else if (type == "integer") { + return {make(sourcemeta::blaze::InstructionIndex::AssertionType, context, + schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Integer)}; + } else if (type == "string") { + if (schema_context.is_property_name) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::String)}; + } else { + return {}; + } + } else if (schema_context.schema.at(dynamic_context.keyword).is_array()) { + ValueTypes types{}; + for (const auto &type : + schema_context.schema.at(dynamic_context.keyword).as_array()) { + assert(type.is_string()); + const auto &type_string{type.to_string()}; + if (type_string == "null") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Null)); + } else if (type_string == "boolean") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Boolean)); + } else if (type_string == "object") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Object)); + } else if (type_string == "array") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Array)); + } else if (type_string == "number") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Real)); + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } else if (type_string == "integer") { + types.set(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + } else if (type_string == "string") { + if (schema_context.is_property_name) { + continue; + } + + types.set(std::to_underlying(sourcemeta::core::JSON::Type::String)); + } + } + + assert(types.any()); + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeAny, context, + schema_context, dynamic_context, types)}; + } + + return {}; +} + +auto compiler_draft6_validation_const(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + return {make(sourcemeta::blaze::InstructionIndex::AssertionEqual, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; +} + +auto compiler_draft6_validation_exclusivemaximum( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_number()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionLess, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; +} + +auto compiler_draft6_validation_exclusiveminimum( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_number()) { + return {}; + } + + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "integer" && + schema_context.schema.at("type").to_string() != "number") { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionGreater, context, + schema_context, dynamic_context, + sourcemeta::core::JSON{ + schema_context.schema.at(dynamic_context.keyword)})}; +} + +auto compiler_draft6_applicator_contains(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "array") { + return {}; + } + + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + if (children.empty()) { + // We still need to check the instance is not empty + return {make(sourcemeta::blaze::InstructionIndex::AssertionArraySizeGreater, + context, schema_context, dynamic_context, + ValueUnsignedInteger{0})}; + } + + return {make(sourcemeta::blaze::InstructionIndex::LoopContains, context, + schema_context, dynamic_context, + ValueRange{1, std::nullopt, false}, std::move(children))}; +} + +auto compiler_draft6_validation_propertynames( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; + } + + // TODO: How can we avoid this copy? + auto nested_schema_context = schema_context; + nested_schema_context.is_property_name = true; + Instructions children{compile(context, nested_schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + if (children.empty()) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::LoopKeys, context, + schema_context, dynamic_context, ValueNone{}, + std::move(children))}; +} + +} // namespace internal +#endif diff --git a/vendor/blaze/src/compiler/default_compiler_draft7.h b/vendor/blaze/src/compiler/default_compiler_draft7.h new file mode 100644 index 0000000..6fa3df3 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler_draft7.h @@ -0,0 +1,96 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT7_H_ +#define SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT7_H_ + +#include + +#include "compile_helpers.h" + +namespace internal { +using namespace sourcemeta::blaze; + +// TODO: Don't generate `if` if neither `then` nor `else` is defined +auto compiler_draft7_applicator_if(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + // `if` + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + + // `then` + std::size_t then_cursor{children.size()}; + if (schema_context.schema.defines("then")) { + const auto destination{ + to_uri(schema_context.relative_pointer.initial().concat( + make_weak_pointer(KEYWORD_THEN)), + schema_context.base) + .recompose()}; + assert(context.frame.locations().contains( + {sourcemeta::core::SchemaReferenceType::Static, destination})); + DynamicContext new_dynamic_context{ + .keyword = KEYWORD_THEN, + .base_schema_location = dynamic_context.base_schema_location, + .base_instance_location = sourcemeta::core::empty_weak_pointer}; + for (auto &&step : + compile(context, schema_context, new_dynamic_context, + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer, destination)) { + children.push_back(std::move(step)); + } + + // In this case, `if` did nothing, so we can short-circuit + if (then_cursor == 0) { + return children; + } + } + + // `else` + std::size_t else_cursor{0}; + if (schema_context.schema.defines("else")) { + else_cursor = children.size(); + const auto destination{ + to_uri(schema_context.relative_pointer.initial().concat( + make_weak_pointer(KEYWORD_ELSE)), + schema_context.base) + .recompose()}; + assert(context.frame.locations().contains( + {sourcemeta::core::SchemaReferenceType::Static, destination})); + DynamicContext new_dynamic_context{ + .keyword = KEYWORD_ELSE, + .base_schema_location = dynamic_context.base_schema_location, + .base_instance_location = sourcemeta::core::empty_weak_pointer}; + for (auto &&step : + compile(context, schema_context, new_dynamic_context, + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer, destination)) { + children.push_back(std::move(step)); + } + } + + return {make(sourcemeta::blaze::InstructionIndex::LogicalCondition, context, + schema_context, dynamic_context, + ValueIndexPair{then_cursor, else_cursor}, std::move(children))}; +} + +// We handle `then` as part of `if` +// TODO: Stop collapsing this keyword on exhaustive mode for debuggability +// purposes +auto compiler_draft7_applicator_then(const Context &, const SchemaContext &, + const DynamicContext &, + const Instructions &) -> Instructions { + return {}; +} + +// We handle `else` as part of `if` +// TODO: Stop collapsing this keyword on exhaustive mode for debuggability +// purposes +auto compiler_draft7_applicator_else(const Context &, const SchemaContext &, + const DynamicContext &, + const Instructions &) -> Instructions { + return {}; +} + +} // namespace internal +#endif diff --git a/vendor/blaze/src/compiler/default_compiler_openapi.h b/vendor/blaze/src/compiler/default_compiler_openapi.h new file mode 100644 index 0000000..b118fc1 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler_openapi.h @@ -0,0 +1,17 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_OPENAPI_H_ +#define SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_OPENAPI_H_ + +#include + +namespace internal { +using namespace sourcemeta::blaze; + +auto compiler_openapi_noop(const Context &, const SchemaContext &, + const DynamicContext &, const Instructions &) + -> Instructions { + return {}; +} + +} // namespace internal + +#endif diff --git a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h new file mode 100644 index 0000000..86b198c --- /dev/null +++ b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h @@ -0,0 +1,217 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_COMPILE_H_ +#define SOURCEMETA_BLAZE_COMPILER_COMPILE_H_ + +#ifndef SOURCEMETA_BLAZE_COMPILER_EXPORT +#include +#endif + +#include +#include + +#include + +#include +#include +#include +#include + +#include // std::size_t +#include // std::uint8_t +#include // std::function +#include // std::map +#include // std::optional, std::nullopt +#include // std::string +#include // std::string_view +#include // std::tuple +#include // std::unordered_map +#include // std::vector + +/// @defgroup compiler Compiler +/// @brief Compile a JSON Schema into a set of low-level instructions for fast +/// evaluation + +namespace sourcemeta::blaze { + +/// @ingroup compiler +/// The schema compiler context is the current subschema information you have at +/// your disposal to implement a keyword +struct SchemaContext { + // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) + /// The schema location relative to the base URI + const sourcemeta::core::WeakPointer &relative_pointer; + /// The current subschema + const sourcemeta::core::JSON &schema; + /// The schema vocabularies in use + const sourcemeta::core::Vocabularies &vocabularies; + /// The schema base URI + const sourcemeta::core::URI &base; + // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) + /// Whether the current schema targets a property name + bool is_property_name; +}; + +/// @ingroup compiler +/// The dynamic compiler context is the read-write information you have at your +/// disposal to implement a keyword +struct DynamicContext { + // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) + /// The schema keyword + const sourcemeta::core::JSON::String &keyword; + /// The schema base keyword path + const sourcemeta::core::WeakPointer &base_schema_location; + /// The base instance location that the keyword must be evaluated to + const sourcemeta::core::WeakPointer &base_instance_location; + // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +#if !defined(DOXYGEN) +struct Context; +#endif + +/// @ingroup compiler +/// A compiler is represented as a function that maps a keyword compiler +/// contexts into a compiler template. You can provide your own to implement +/// your own keywords +using Compiler = + // TODO(C++23): Use std::move_only_function when available in libc++ + std::function; + +/// @ingroup evaluator +/// Represents the mode of compilation +enum class Mode : std::uint8_t { + /// Attempt to get to a boolean result as fast as possible + FastValidation, + /// Perform exhaustive evaluation, including annotations + Exhaustive +}; + +/// @ingroup compiler +/// Advanced knobs that you can tweak for higher control and optimisations +struct Tweaks { + /// Always unroll `properties` in a logical AND operation + bool properties_always_unroll{false}; + /// Attempt to re-order `properties` subschemas to evaluate cheaper ones first + bool properties_reorder{true}; + /// Inline jump targets with fewer instructions than this threshold + std::size_t target_inline_threshold{50}; +}; + +/// @ingroup compiler +/// The static compiler context is the information you have at your +/// disposal to implement a keyword that will never change throughout +/// the compilation process +struct Context { + // NOLINTBEGIN(cppcoreguidelines-avoid-const-or-ref-data-members) + /// The root schema resource + const sourcemeta::core::JSON &root; + /// The reference frame of the entire schema + const sourcemeta::core::SchemaFrame &frame; + /// The set of all schema resources in the schema without duplicates + const std::vector resources; + /// The schema walker in use + const sourcemeta::core::SchemaWalker &walker; + /// The schema resolver in use + const sourcemeta::core::SchemaResolver &resolver; + /// The schema compiler in use + const Compiler &compiler; + /// The mode of the schema compiler + const Mode mode; + /// Whether the schema makes use of dynamic scoping + const bool uses_dynamic_scopes; + /// The list of unevaluated entries and their dependencies + const SchemaUnevaluatedEntries unevaluated; + /// The set of tweaks for the compiler + const Tweaks tweaks; + /// All possible reference targets (key includes is_property_name context) + const std::map< + std::tuple, + std::pair> + targets; + /// Accumulator for instruction extra data during compilation + std::vector &extra; + // NOLINTEND(cppcoreguidelines-avoid-const-or-ref-data-members) +}; + +/// @ingroup compiler +/// A default compiler that aims to implement every keyword for official JSON +/// Schema dialects. +auto SOURCEMETA_BLAZE_COMPILER_EXPORT default_schema_compiler( + const Context &, const SchemaContext &, const DynamicContext &, + const Instructions &) -> Instructions; + +/// @ingroup compiler +/// +/// This function compiles an input JSON Schema into a template that can be +/// later evaluated. For example: +/// +/// ```cpp +/// #include +/// +/// #include +/// #include +/// +/// const sourcemeta::core::JSON schema = +/// sourcemeta::core::parse_json(R"JSON({ +/// "$schema": "https://json-schema.org/draft/2020-12/schema", +/// "type": "string" +/// })JSON"); +/// +/// const auto schema_template{sourcemeta::blaze::compile( +/// schema, sourcemeta::core::schema_walker, +/// sourcemeta::core::schema_resolver, +/// sourcemeta::core::default_schema_compiler)}; +/// +/// // Evaluate or encode +/// ``` +auto SOURCEMETA_BLAZE_COMPILER_EXPORT +compile(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, + const Compiler &compiler, const Mode mode = Mode::FastValidation, + const std::string_view default_dialect = "", + const std::string_view default_id = "", + const std::string_view entrypoint = "", + const std::optional &tweaks = std::nullopt) -> Template; + +/// @ingroup compiler +/// +/// This function compiles an input JSON Schema into a template that can be +/// later evaluated, but given an existing schema frame. The schema frame must +/// contain reference information for the given schema and the input schema must +/// be bundled. If those pre-conditions are not met, you will hit undefined +/// behavior. +/// +/// Don't use this function unless you know what you are doing. +auto SOURCEMETA_BLAZE_COMPILER_EXPORT compile( + const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver, const Compiler &compiler, + const sourcemeta::core::SchemaFrame &frame, + const std::string_view entrypoint, const Mode mode = Mode::FastValidation, + const std::optional &tweaks = std::nullopt) -> Template; + +/// @ingroup compiler +/// +/// This function compiles a single subschema into a compiler template as +/// determined by the given pointer. If a URI is given, the compiler will +/// attempt to jump to that corresponding frame entry. Otherwise, it will +/// navigate within the current keyword. This function is not meant to be used +/// directly, but instead as a building block for supporting applicators on +/// compiler functions. +auto SOURCEMETA_BLAZE_COMPILER_EXPORT +compile(const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const sourcemeta::core::WeakPointer &schema_suffix, + const sourcemeta::core::WeakPointer &instance_suffix = + sourcemeta::core::empty_weak_pointer, + std::optional uri = std::nullopt) -> Instructions; + +/// @ingroup compiler +/// Serialise a template as JSON +auto SOURCEMETA_BLAZE_COMPILER_EXPORT to_json(const Template &schema_template) + -> sourcemeta::core::JSON; + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_error.h b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_error.h new file mode 100644 index 0000000..c3e00fd --- /dev/null +++ b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_error.h @@ -0,0 +1,157 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_ERROR_H +#define SOURCEMETA_BLAZE_COMPILER_ERROR_H + +#ifndef SOURCEMETA_BLAZE_COMPILER_EXPORT +#include +#endif + +#include +#include + +#include // std::exception +#include // std::string +#include // std::string_view +#include // std::move + +namespace sourcemeta::blaze { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup jsonschema +/// An error that represents a schema compilation failure event +class SOURCEMETA_BLAZE_COMPILER_EXPORT CompilerError : public std::exception { +public: + CompilerError(sourcemeta::core::URI base, + sourcemeta::core::Pointer schema_location, const char *message) + : base_{std::move(base)}, schema_location_{std::move(schema_location)}, + message_{message} {} + CompilerError(sourcemeta::core::URI base, + sourcemeta::core::Pointer schema_location, + std::string message) = delete; + CompilerError(sourcemeta::core::URI base, + sourcemeta::core::Pointer schema_location, + std::string &&message) = delete; + CompilerError(sourcemeta::core::URI base, + sourcemeta::core::Pointer schema_location, + std::string_view message) = delete; + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_; + } + + [[nodiscard]] auto base() const noexcept -> const sourcemeta::core::URI & { + return this->base_; + } + + [[nodiscard]] auto location() const noexcept + -> const sourcemeta::core::Pointer & { + return this->schema_location_; + } + +private: + sourcemeta::core::URI base_; + sourcemeta::core::Pointer schema_location_; + const char *message_; +}; + +/// @ingroup jsonschema +/// An error that represents an invalid regular expression during compilation +class SOURCEMETA_BLAZE_COMPILER_EXPORT CompilerInvalidRegexError + : public std::exception { +public: + CompilerInvalidRegexError(sourcemeta::core::URI base, + sourcemeta::core::Pointer schema_location, + std::string regex) + : base_{std::move(base)}, schema_location_{std::move(schema_location)}, + regex_{std::move(regex)} {} + [[nodiscard]] auto what() const noexcept -> const char * override { + return "Invalid regular expression"; + } + + [[nodiscard]] auto base() const noexcept -> const sourcemeta::core::URI & { + return this->base_; + } + + [[nodiscard]] auto location() const noexcept + -> const sourcemeta::core::Pointer & { + return this->schema_location_; + } + + [[nodiscard]] auto regex() const noexcept -> const std::string & { + return this->regex_; + } + +private: + sourcemeta::core::URI base_; + sourcemeta::core::Pointer schema_location_; + std::string regex_; +}; + +/// @ingroup jsonschema +/// An error that represents a reference target that is not a valid schema +class SOURCEMETA_BLAZE_COMPILER_EXPORT CompilerReferenceTargetNotSchemaError + : public std::exception { +public: + CompilerReferenceTargetNotSchemaError( + const std::string_view identifier, + sourcemeta::core::Pointer schema_location) + : identifier_{identifier}, schema_location_{std::move(schema_location)} {} + + [[nodiscard]] auto what() const noexcept -> const char * override { + return "The referenced schema is not considered to be a valid subschema " + "given the dialect and vocabularies in use"; + } + + [[nodiscard]] auto identifier() const noexcept -> const std::string & { + return this->identifier_; + } + + [[nodiscard]] auto location() const noexcept + -> const sourcemeta::core::Pointer & { + return this->schema_location_; + } + +private: + std::string identifier_; + sourcemeta::core::Pointer schema_location_; +}; + +/// @ingroup jsonschema +/// An error that represents an invalid compilation entrypoint +class SOURCEMETA_BLAZE_COMPILER_EXPORT CompilerInvalidEntryPoint + : public std::exception { +public: + CompilerInvalidEntryPoint(const std::string_view entrypoint, + const char *message) + : identifier_{entrypoint}, message_{message} {} + CompilerInvalidEntryPoint(const std::string_view entrypoint, + std::string message) = delete; + CompilerInvalidEntryPoint(const std::string_view entrypoint, + std::string &&message) = delete; + CompilerInvalidEntryPoint(const std::string_view entrypoint, + std::string_view message) = delete; + + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_; + } + + [[nodiscard]] auto identifier() const noexcept -> const std::string & { + return this->identifier_; + } + +private: + std::string identifier_; + const char *message_; +}; + +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_unevaluated.h b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_unevaluated.h new file mode 100644 index 0000000..aca465c --- /dev/null +++ b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler_unevaluated.h @@ -0,0 +1,50 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_UNEVALUATED_H +#define SOURCEMETA_BLAZE_COMPILER_UNEVALUATED_H + +#ifndef SOURCEMETA_BLAZE_COMPILER_EXPORT +#include +#endif + +#include +#include +#include + +#include // std::map +#include // std::set + +// TODO: Eventually this file should dissapear and move this analysis as part of +// framing + +namespace sourcemeta::blaze { + +/// @ingroup compiler +struct SchemaUnevaluatedEntry { + /// The absolute pointers of the static keyword dependencies + std::set static_dependencies; + /// The absolute pointers of the static keyword dependencies + std::set dynamic_dependencies; + /// Whether the entry cannot be fully resolved, which means + /// there might be unknown dynamic dependencies + bool unresolved{false}; +}; + +/// @ingroup compiler +/// The flattened set of unevaluated cases in the schema by absolute URI +using SchemaUnevaluatedEntries = + std::map; + +/// @ingroup compiler +/// +/// This function performs a static analysis pass on `unevaluatedProperties` and +/// `unevaluatedItems` occurences throughout the entire schema (if any). +/// ``` +auto SOURCEMETA_BLAZE_COMPILER_EXPORT +unevaluated(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) + -> SchemaUnevaluatedEntries; + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/compiler/postprocess.h b/vendor/blaze/src/compiler/postprocess.h new file mode 100644 index 0000000..a959457 --- /dev/null +++ b/vendor/blaze/src/compiler/postprocess.h @@ -0,0 +1,472 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_POSTPROCESS_H +#define SOURCEMETA_BLAZE_COMPILER_POSTPROCESS_H + +#include +#include + +#include +#include +#include // std::string +#include +#include // std::unordered_set +#include + +// TODO: Move all `FastValidation` conditional optimisations from the default +// compilers here. Only the structural ones from Draft 4 seem to be missing + +namespace sourcemeta::blaze { + +struct TargetStatistics { + std::size_t count; + std::unordered_map jump_targets; + bool requires_empty_instance_location; +}; + +inline auto is_noop_without_children(const InstructionIndex type) noexcept + -> bool { + switch (type) { + case InstructionIndex::LogicalAnd: + case InstructionIndex::LogicalCondition: + case InstructionIndex::LogicalWhenType: + case InstructionIndex::LogicalWhenDefines: + case InstructionIndex::LogicalWhenArraySizeGreater: + case InstructionIndex::LoopPropertiesMatch: + case InstructionIndex::LoopProperties: + case InstructionIndex::LoopPropertiesEvaluate: + case InstructionIndex::LoopPropertiesRegex: + case InstructionIndex::LoopPropertiesStartsWith: + case InstructionIndex::LoopPropertiesExcept: + case InstructionIndex::LoopKeys: + case InstructionIndex::LoopItems: + case InstructionIndex::LoopItemsFrom: + case InstructionIndex::LoopItemsUnevaluated: + case InstructionIndex::LoopContains: + case InstructionIndex::ControlGroupWhenDefines: + case InstructionIndex::ControlGroupWhenDefinesDirect: + case InstructionIndex::ControlGroupWhenType: + return true; + default: + return false; + } +} + +inline auto +is_parent_to_children_instruction(const InstructionIndex type) noexcept + -> bool { + switch (type) { + case InstructionIndex::LoopPropertiesMatch: + case InstructionIndex::LoopPropertiesMatchClosed: + case InstructionIndex::ControlGroupWhenDefines: + case InstructionIndex::ControlGroupWhenDefinesDirect: + return true; + default: + return false; + } +} + +inline auto convert_to_property_type_assertions(Instructions &instructions) + -> void { + for (auto &instruction : instructions) { + if (!instruction.relative_instance_location.empty()) { + switch (instruction.type) { + case InstructionIndex::AssertionTypeStrict: + instruction.type = InstructionIndex::AssertionPropertyTypeStrict; + break; + case InstructionIndex::AssertionType: + instruction.type = InstructionIndex::AssertionPropertyType; + break; + case InstructionIndex::AssertionTypeStrictAny: + instruction.type = InstructionIndex::AssertionPropertyTypeStrictAny; + break; + default: + break; + } + } + + if (!is_parent_to_children_instruction(instruction.type)) { + convert_to_property_type_assertions(instruction.children); + } + } +} + +inline auto duplicate_metadata(Instruction &instruction, + std::vector &extra) -> void { + const auto new_index{extra.size()}; + auto source_extra{extra[instruction.extra_index]}; + extra.push_back(std::move(source_extra)); + instruction.extra_index = new_index; + for (auto &child : instruction.children) { + duplicate_metadata(child, extra); + } +} + +inline auto rebase(Instruction &instruction, + std::vector &extra, + const sourcemeta::core::Pointer &schema_prefix, + const sourcemeta::core::Pointer &instance_prefix) -> void { + extra[instruction.extra_index].relative_schema_location = + schema_prefix.concat( + extra[instruction.extra_index].relative_schema_location); + instruction.relative_instance_location = + instance_prefix.concat(instruction.relative_instance_location); + + if (instruction.type == InstructionIndex::LogicalCondition) { + const auto &value{std::get(instruction.value)}; + const auto then_cursor{value.first}; + // TODO(C++23): Use std::views::enumerate when available in libc++ + for (std::size_t index = then_cursor; index < instruction.children.size(); + ++index) { + auto &child{instruction.children[index]}; + extra[child.extra_index].relative_schema_location = schema_prefix.concat( + extra[child.extra_index].relative_schema_location); + for (auto &grandchild : child.children) { + extra[grandchild.extra_index].relative_schema_location = + schema_prefix.concat( + extra[grandchild.extra_index].relative_schema_location); + } + } + } +} + +inline auto collect_statistics(const Instructions &instructions, + TargetStatistics &statistics) -> void { + for (const auto &instruction : instructions) { + statistics.count += 1; + + if (instruction.type == InstructionIndex::ControlJump) { + statistics + .jump_targets[std::get(instruction.value)]++; + } + + if (instruction.type == InstructionIndex::ControlGroupWhenDefinesDirect || + instruction.type == InstructionIndex::ControlGroupWhenType) { + statistics.requires_empty_instance_location = true; + } + + collect_statistics(instruction.children, statistics); + } +} + +inline auto +transform_instruction(Instruction &instruction, Instructions &output, + std::vector &extra, + const std::vector &targets, + const std::vector &statistics, + TargetStatistics ¤t_stats, const Tweaks &tweaks, + const bool uses_dynamic_scopes) -> bool { + if (instruction.type == InstructionIndex::ControlJump) { + const auto jump_target_index{ + std::get(instruction.value)}; + const auto &jump_target_stats{statistics[jump_target_index]}; + + if (jump_target_stats.count == 0) { + current_stats.jump_targets[jump_target_index]--; + return true; + } + + const auto jump_target_self_loop{ + jump_target_stats.jump_targets.find(jump_target_index)}; + if (jump_target_stats.count < tweaks.target_inline_threshold && + (jump_target_self_loop == jump_target_stats.jump_targets.end() || + jump_target_self_loop->second == 0) && + !uses_dynamic_scopes && + (!jump_target_stats.requires_empty_instance_location || + instruction.relative_instance_location.empty())) { + current_stats.jump_targets[jump_target_index]--; + for (const auto &[inlined_jump, inlined_count] : + jump_target_stats.jump_targets) { + current_stats.jump_targets[inlined_jump] += inlined_count; + } + current_stats.requires_empty_instance_location |= + jump_target_stats.requires_empty_instance_location; + + const auto schema_prefix{ + extra[instruction.extra_index].relative_schema_location}; + for (auto target_instruction : targets[jump_target_index]) { + duplicate_metadata(target_instruction, extra); + rebase(target_instruction, extra, schema_prefix, + instruction.relative_instance_location); + output.push_back(std::move(target_instruction)); + } + return true; + } + } + + if (is_noop_without_children(instruction.type) && + instruction.children.empty()) { + return true; + } + + // TODO: De-duplicate this logic from default_compiler_draft4.h. Just do it + // all here + + if (instruction.type == InstructionIndex::LoopProperties && + instruction.children.size() == 1) { + auto &child{instruction.children.front()}; + if (child.type == InstructionIndex::AssertionTypeStrict) { + const auto new_extra_index{extra.size()}; + auto &instruction_meta{extra[instruction.extra_index]}; + auto &child_meta{extra[child.extra_index]}; + extra.push_back( + {.relative_schema_location = + instruction_meta.relative_schema_location.concat( + child_meta.relative_schema_location), + .keyword_location = std::move(child_meta.keyword_location), + .schema_resource = child_meta.schema_resource}); + output.push_back( + Instruction{.type = InstructionIndex::LoopPropertiesTypeStrict, + .relative_instance_location = + std::move(instruction.relative_instance_location), + .value = std::move(child.value), + .children = {}, + .extra_index = new_extra_index}); + return true; + } + + if (child.type == InstructionIndex::AssertionType) { + const auto new_extra_index{extra.size()}; + auto &instruction_meta{extra[instruction.extra_index]}; + auto &child_meta{extra[child.extra_index]}; + extra.push_back( + {.relative_schema_location = + instruction_meta.relative_schema_location.concat( + child_meta.relative_schema_location), + .keyword_location = std::move(child_meta.keyword_location), + .schema_resource = child_meta.schema_resource}); + output.push_back(Instruction{.type = InstructionIndex::LoopPropertiesType, + .relative_instance_location = std::move( + instruction.relative_instance_location), + .value = std::move(child.value), + .children = {}, + .extra_index = new_extra_index}); + return true; + } + + if (child.type == InstructionIndex::AssertionTypeStrictAny) { + const auto new_extra_index{extra.size()}; + auto &instruction_meta{extra[instruction.extra_index]}; + auto &child_meta{extra[child.extra_index]}; + extra.push_back( + {.relative_schema_location = + instruction_meta.relative_schema_location.concat( + child_meta.relative_schema_location), + .keyword_location = std::move(child_meta.keyword_location), + .schema_resource = child_meta.schema_resource}); + output.push_back( + Instruction{.type = InstructionIndex::LoopPropertiesTypeStrictAny, + .relative_instance_location = + std::move(instruction.relative_instance_location), + .value = std::move(child.value), + .children = {}, + .extra_index = new_extra_index}); + return true; + } + } + + if (instruction.type == InstructionIndex::LoopPropertiesEvaluate && + instruction.children.size() == 1) { + auto &child{instruction.children.front()}; + if (child.type == InstructionIndex::AssertionTypeStrict) { + const auto new_extra_index{extra.size()}; + auto &instruction_meta{extra[instruction.extra_index]}; + auto &child_meta{extra[child.extra_index]}; + extra.push_back( + {.relative_schema_location = + instruction_meta.relative_schema_location.concat( + child_meta.relative_schema_location), + .keyword_location = std::move(child_meta.keyword_location), + .schema_resource = child_meta.schema_resource}); + output.push_back(Instruction{ + .type = InstructionIndex::LoopPropertiesTypeStrictEvaluate, + .relative_instance_location = + std::move(instruction.relative_instance_location), + .value = std::move(child.value), + .children = {}, + .extra_index = new_extra_index}); + return true; + } + + if (child.type == InstructionIndex::AssertionType) { + const auto new_extra_index{extra.size()}; + auto &instruction_meta{extra[instruction.extra_index]}; + auto &child_meta{extra[child.extra_index]}; + extra.push_back( + {.relative_schema_location = + instruction_meta.relative_schema_location.concat( + child_meta.relative_schema_location), + .keyword_location = std::move(child_meta.keyword_location), + .schema_resource = child_meta.schema_resource}); + output.push_back( + Instruction{.type = InstructionIndex::LoopPropertiesTypeEvaluate, + .relative_instance_location = + std::move(instruction.relative_instance_location), + .value = std::move(child.value), + .children = {}, + .extra_index = new_extra_index}); + return true; + } + + if (child.type == InstructionIndex::AssertionTypeStrictAny) { + const auto new_extra_index{extra.size()}; + auto &instruction_meta{extra[instruction.extra_index]}; + auto &child_meta{extra[child.extra_index]}; + extra.push_back( + {.relative_schema_location = + instruction_meta.relative_schema_location.concat( + child_meta.relative_schema_location), + .keyword_location = std::move(child_meta.keyword_location), + .schema_resource = child_meta.schema_resource}); + output.push_back(Instruction{ + .type = InstructionIndex::LoopPropertiesTypeStrictAnyEvaluate, + .relative_instance_location = + std::move(instruction.relative_instance_location), + .value = std::move(child.value), + .children = {}, + .extra_index = new_extra_index}); + return true; + } + } + + if (is_parent_to_children_instruction(instruction.type)) { + convert_to_property_type_assertions(instruction.children); + } + + if (!instruction.relative_instance_location.empty() && + std::ranges::all_of( + instruction.relative_instance_location, + [](const auto &token) { return token.is_property(); })) { + switch (instruction.type) { + case InstructionIndex::AssertionTypeStrict: + instruction.type = InstructionIndex::AssertionPropertyTypeStrict; + break; + case InstructionIndex::AssertionType: + instruction.type = InstructionIndex::AssertionPropertyType; + break; + case InstructionIndex::AssertionTypeStrictAny: + instruction.type = InstructionIndex::AssertionPropertyTypeStrictAny; + break; + default: + break; + } + } + + output.push_back(std::move(instruction)); + return false; +} + +inline auto postprocess(std::vector &targets, + std::vector &extra, + const Tweaks &tweaks, const bool uses_dynamic_scopes) + -> void { + std::vector statistics; + statistics.reserve(targets.size()); + for (const auto &target : targets) { + TargetStatistics entry{.count = 0, + .jump_targets = {}, + .requires_empty_instance_location = false}; + collect_statistics(target, entry); + statistics.push_back(std::move(entry)); + } + + bool changed{true}; + while (changed) { + changed = false; + + for (std::size_t current_target_index = 0; + current_target_index < targets.size(); ++current_target_index) { + auto &target{targets[current_target_index]}; + auto ¤t_stats{statistics[current_target_index]}; + + std::vector worklist; + std::vector> stack; + stack.emplace_back(&target, 0); + + while (!stack.empty()) { + auto current{stack.back().first}; + auto index{stack.back().second}; + stack.pop_back(); + + while (index < current->size() && (*current)[index].children.empty()) { + ++index; + } + + if (index < current->size()) { + stack.emplace_back(current, index + 1); + stack.emplace_back(&(*current)[index].children, 0); + } else { + worklist.push_back(current); + } + } + + for (auto *current : worklist) { + Instructions result; + result.reserve(current->size()); + + std::unordered_set fusion_covered_properties; + for (const auto &instruction : *current) { + if (instruction.type == + InstructionIndex::AssertionObjectPropertiesSimple) { + const auto &entries{ + std::get(instruction.value)}; + for (const auto &entry : entries) { + fusion_covered_properties.insert(std::get<0>(entry)); + } + } + } + + for (auto &instruction : *current) { + if (!fusion_covered_properties.empty()) { + switch (instruction.type) { + case InstructionIndex::AssertionDefinesAllStrict: + case InstructionIndex::AssertionDefinesAll: { + const auto &value{std::get(instruction.value)}; + bool all_covered{true}; + for (const auto &property : value) { + if (!fusion_covered_properties.contains(property.first)) { + all_covered = false; + break; + } + } + if (all_covered) { + changed = true; + continue; + } + break; + } + case InstructionIndex::AssertionDefinesStrict: + case InstructionIndex::AssertionDefines: { + const auto &value{std::get(instruction.value)}; + if (fusion_covered_properties.contains(value.first)) { + changed = true; + continue; + } + break; + } + case InstructionIndex::AssertionTypeStrict: + case InstructionIndex::AssertionType: + if (std::get(instruction.value) == + sourcemeta::core::JSON::Type::Object) { + changed = true; + continue; + } + break; + default: + break; + } + } + + if (transform_instruction(instruction, result, extra, targets, + statistics, current_stats, tweaks, + uses_dynamic_scopes)) + changed = true; + } + + *current = std::move(result); + } + } + } +} + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/compiler/unevaluated.cc b/vendor/blaze/src/compiler/unevaluated.cc new file mode 100644 index 0000000..67a0a97 --- /dev/null +++ b/vendor/blaze/src/compiler/unevaluated.cc @@ -0,0 +1,223 @@ +#include + +#include "compile_helpers.h" + +namespace { +using namespace sourcemeta::core; +using namespace sourcemeta::blaze; +using Known = Vocabularies::Known; + +static const std::string UNEVALUATED_PROPERTIES{"unevaluatedProperties"}; +static const std::string UNEVALUATED_ITEMS{"unevaluatedItems"}; + +auto find_adjacent_dependencies( + const JSON::String ¤t, const JSON &schema, const SchemaFrame &frame, + const SchemaWalker &walker, const SchemaResolver &resolver, + const std::set &keywords, const SchemaFrame::Location &root, + const SchemaFrame::Location &entry, const bool is_static, + sourcemeta::blaze::SchemaUnevaluatedEntry &result) -> void { + const auto &subschema{get(schema, entry.pointer)}; + if (!subschema.is_object()) { + return; + } + + const auto subschema_vocabularies{ + vocabularies(subschema, resolver, entry.dialect)}; + + for (const auto &property : subschema.as_object()) { + if (property.first == current && entry.pointer == root.pointer) { + continue; + } else if (keywords.contains(property.first)) { + // In 2019-09, `additionalItems` takes no effect without `items` + if (subschema_vocabularies.contains( + Known::JSON_Schema_2019_09_Applicator) && + property.first == "additionalItems" && !subschema.defines("items")) { + continue; + } + + auto pointer{entry.pointer.concat(make_weak_pointer(property.first))}; + if (is_static) { + result.static_dependencies.emplace(std::move(pointer)); + } else { + result.dynamic_dependencies.emplace(std::move(pointer)); + } + + continue; + } + + switch (walker(property.first, subschema_vocabularies).type) { + // References + case SchemaKeywordType::Reference: { + const auto reference{ + frame.dereference(entry, make_weak_pointer(property.first))}; + if (reference.first == SchemaReferenceType::Static && + reference.second.has_value()) { + find_adjacent_dependencies( + current, schema, frame, walker, resolver, keywords, root, + reference.second.value().get(), is_static, result); + } else if (reference.first == SchemaReferenceType::Dynamic) { + result.unresolved = true; + } + + break; + } + + // Static + case SchemaKeywordType::ApplicatorElementsInPlace: + // TODO(C++23): Use std::views::enumerate when available in libc++ + for (std::size_t index = 0; index < property.second.size(); index++) { + find_adjacent_dependencies( + current, schema, frame, walker, resolver, keywords, root, + frame.traverse(entry, make_weak_pointer(property.first, index)), + is_static, result); + } + + break; + + // Dynamic + case SchemaKeywordType::ApplicatorElementsInPlaceSome: + if (property.second.is_array()) { + for (std::size_t index = 0; index < property.second.size(); index++) { + find_adjacent_dependencies( + current, schema, frame, walker, resolver, keywords, root, + frame.traverse(entry, make_weak_pointer(property.first, index)), + false, result); + } + } + + break; + case SchemaKeywordType::ApplicatorValueTraverseAnyItem: + [[fallthrough]]; + case SchemaKeywordType::ApplicatorValueTraverseParent: + [[fallthrough]]; + case SchemaKeywordType::ApplicatorValueInPlaceMaybe: + if (is_schema(property.second)) { + find_adjacent_dependencies( + current, schema, frame, walker, resolver, keywords, root, + frame.traverse(entry, make_weak_pointer(property.first)), false, + result); + } + + break; + case SchemaKeywordType::ApplicatorValueOrElementsInPlace: + if (property.second.is_array()) { + for (std::size_t index = 0; index < property.second.size(); index++) { + find_adjacent_dependencies( + current, schema, frame, walker, resolver, keywords, root, + frame.traverse(entry, make_weak_pointer(property.first, index)), + false, result); + } + } else if (is_schema(property.second)) { + find_adjacent_dependencies( + current, schema, frame, walker, resolver, keywords, root, + frame.traverse(entry, make_weak_pointer(property.first)), false, + result); + } + + break; + case SchemaKeywordType::ApplicatorMembersInPlaceSome: + if (property.second.is_object()) { + for (const auto &pair : property.second.as_object()) { + find_adjacent_dependencies( + current, schema, frame, walker, resolver, keywords, root, + frame.traverse(entry, + make_weak_pointer(property.first, pair.first)), + false, result); + } + } + + break; + + // Anything else does not contribute to the dependency list + default: + break; + } + } +} + +} // namespace + +namespace sourcemeta::blaze { + +// TODO: Refactor this entire function using `SchemaFrame`'s new `Instances` +// mode. We can loop over every subschema that defines `unevaluatedProperties` +// or `unevaluatedItems`, find all other subschemas with the same unresolved +// instance location (static dependency) or conditional equivalent unresolved +// instance location (dynamic dependency) and see if those ones define any of +// the dependent keywords. +auto unevaluated(const JSON &schema, const SchemaFrame &frame, + const SchemaWalker &walker, const SchemaResolver &resolver) + -> SchemaUnevaluatedEntries { + SchemaUnevaluatedEntries result; + + for (const auto &entry : frame.locations()) { + if (entry.second.type != SchemaFrame::LocationType::Subschema && + entry.second.type != SchemaFrame::LocationType::Resource) { + continue; + } + + const auto &subschema{get(schema, entry.second.pointer)}; + assert(is_schema(subschema)); + if (!subschema.is_object()) { + continue; + } + + const bool has_unevaluated_properties{ + subschema.defines("unevaluatedProperties")}; + const bool has_unevaluated_items{subschema.defines("unevaluatedItems")}; + if (!has_unevaluated_properties && !has_unevaluated_items) { + continue; + } + + const auto subschema_vocabularies{ + frame.vocabularies(entry.second, resolver)}; + + if (has_unevaluated_properties) { + if ((subschema_vocabularies.contains( + Known::JSON_Schema_2020_12_Unevaluated) && + subschema_vocabularies.contains( + Known::JSON_Schema_2020_12_Applicator)) || + subschema_vocabularies.contains( + Known::JSON_Schema_2019_09_Applicator)) { + SchemaUnevaluatedEntry unevaluated; + find_adjacent_dependencies( + "unevaluatedProperties", schema, frame, walker, resolver, + {"properties", "patternProperties", "additionalProperties", + "unevaluatedProperties"}, + entry.second, entry.second, true, unevaluated); + result.emplace( + frame.uri(entry.second, make_weak_pointer(UNEVALUATED_PROPERTIES)), + std::move(unevaluated)); + } + } + + if (has_unevaluated_items) { + SchemaUnevaluatedEntry unevaluated; + if (subschema_vocabularies.contains( + Known::JSON_Schema_2020_12_Unevaluated) && + subschema_vocabularies.contains( + Known::JSON_Schema_2020_12_Applicator)) { + find_adjacent_dependencies( + "unevaluatedItems", schema, frame, walker, resolver, + {"prefixItems", "items", "contains", "unevaluatedItems"}, + entry.second, entry.second, true, unevaluated); + result.emplace( + frame.uri(entry.second, make_weak_pointer(UNEVALUATED_ITEMS)), + std::move(unevaluated)); + } else if (subschema_vocabularies.contains( + Known::JSON_Schema_2019_09_Applicator)) { + find_adjacent_dependencies( + "unevaluatedItems", schema, frame, walker, resolver, + {"items", "additionalItems", "unevaluatedItems"}, entry.second, + entry.second, true, unevaluated); + result.emplace( + frame.uri(entry.second, make_weak_pointer(UNEVALUATED_ITEMS)), + std::move(unevaluated)); + } + } + } + + return result; +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/configuration/CMakeLists.txt b/vendor/blaze/src/configuration/CMakeLists.txt new file mode 100644 index 0000000..9a9d08c --- /dev/null +++ b/vendor/blaze/src/configuration/CMakeLists.txt @@ -0,0 +1,13 @@ +sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME configuration + PRIVATE_HEADERS error.h SOURCES parse.cc json.cc configuration.cc lock.cc fetch.cc) + +if(BLAZE_INSTALL) + sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME configuration) +endif() + +target_link_libraries(sourcemeta_blaze_configuration PUBLIC sourcemeta::core::json) +target_link_libraries(sourcemeta_blaze_configuration PUBLIC sourcemeta::core::jsonpointer) +target_link_libraries(sourcemeta_blaze_configuration PRIVATE sourcemeta::core::uri) +target_link_libraries(sourcemeta_blaze_configuration PRIVATE sourcemeta::core::io) +target_link_libraries(sourcemeta_blaze_configuration PRIVATE sourcemeta::core::crypto) +target_link_libraries(sourcemeta_blaze_configuration PRIVATE sourcemeta::core::jsonschema) diff --git a/vendor/blaze/src/configuration/configuration.cc b/vendor/blaze/src/configuration/configuration.cc new file mode 100644 index 0000000..1344d55 --- /dev/null +++ b/vendor/blaze/src/configuration/configuration.cc @@ -0,0 +1,86 @@ +#include +#include +#include + +#include // std::ranges::any_of +#include // assert +#include // std::string +#include // std::error_code + +namespace sourcemeta::blaze { + +auto Configuration::add_dependency(const sourcemeta::core::URI &uri, + const std::filesystem::path &path) -> void { + assert(path.is_absolute()); + const auto canonical_uri{ + sourcemeta::core::URI::canonicalize(uri.recompose())}; + + if (this->dependencies.contains(canonical_uri)) [[unlikely]] { + throw ConfigurationParseError( + "The dependency already exists", + sourcemeta::core::Pointer({"dependencies", canonical_uri})); + } + + for (const auto &existing : this->dependencies) { + if (existing.second == path) [[unlikely]] { + throw ConfigurationParseError( + "Multiple dependencies cannot point to the same path", + sourcemeta::core::Pointer({"dependencies", existing.first})); + } + } + + this->dependencies.emplace(canonical_uri, path); +} + +auto Configuration::find(const std::filesystem::path &path) + -> std::optional { + // Note we use non-throwing overloads of filesystem functions to gracefully + // handle I/O errors on FUSE and other unusual filesystems + std::filesystem::path canonical; + try { + canonical = sourcemeta::core::weakly_canonical(path); + } catch (const std::filesystem::filesystem_error &) { + return std::nullopt; + } + + assert(canonical.is_absolute()); + std::error_code error; + const auto is_directory = std::filesystem::is_directory(canonical, error); + auto current = !error && !is_directory ? canonical.parent_path() : canonical; + + while (!current.empty()) { + auto candidate = current / "jsonschema.json"; + if (std::filesystem::exists(candidate, error) && + std::filesystem::is_regular_file(candidate, error)) { + return candidate; + } + + auto parent = current.parent_path(); + if (parent == current) { + break; + } else { + current = parent; + } + } + + return std::nullopt; +} + +auto Configuration::applies_to(const std::filesystem::path &path) const + -> bool { + if (this->extension.empty()) { + return true; + } + + const std::string filename{path.filename().string()}; + return std::ranges::any_of(this->extension, + [&path, &filename](const auto &suffix) { + if (suffix.empty()) { + return path.extension().empty(); + } + + return filename.ends_with(suffix); + }); +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/configuration/fetch.cc b/vendor/blaze/src/configuration/fetch.cc new file mode 100644 index 0000000..2c2cdd9 --- /dev/null +++ b/vendor/blaze/src/configuration/fetch.cc @@ -0,0 +1,364 @@ +#include + +#include +#include +#include + +#include // assert +#include // std::uint8_t +#include // std::ostringstream +#include // std::move +#include // std::vector + +namespace { + +auto compute_sha256(const std::string &content) + -> sourcemeta::core::JSON::String { + std::ostringstream result; + sourcemeta::core::sha256(content, result); + return result.str(); +} + +auto emit_event( + const sourcemeta::blaze::Configuration::FetchEvent::Callback &callback, + sourcemeta::blaze::Configuration::FetchEvent::Type type, + const std::string &event_uri, const std::filesystem::path &event_path, + std::size_t index, std::size_t total, std::string details = {}, + const std::exception_ptr &exception = nullptr, + bool emit_error_if_aborted = false) -> bool { + const auto result{callback({.type = type, + .uri = event_uri, + .path = event_path, + .index = index, + .total = total, + .details = std::move(details), + .exception = exception})}; + if (!result && emit_error_if_aborted) { + emit_event( + callback, sourcemeta::blaze::Configuration::FetchEvent::Type::Error, + event_uri, event_path, index, total, "Operation aborted by callback"); + } + + return result; +} + +enum class FetchResult : std::uint8_t { Success, Error, Aborted }; + +auto verify_written_schema( + const std::string &dependency_uri, + const std::filesystem::path &dependency_path, + const sourcemeta::blaze::Configuration::ReadCallback &reader, + const sourcemeta::blaze::Configuration::FetchEvent::Callback &callback, + std::size_t index, std::size_t total, + sourcemeta::core::JSON::String &out_hash) -> FetchResult { + using FetchEvent = sourcemeta::blaze::Configuration::FetchEvent; + + if (!emit_event(callback, FetchEvent::Type::VerifyStart, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + std::string written_content; + try { + written_content = reader(dependency_path); + } catch (...) { + emit_event(callback, FetchEvent::Type::Error, dependency_uri, + dependency_path, index, total, "Failed to verify written schema", + std::current_exception()); + return FetchResult::Error; + } + + out_hash = compute_sha256(written_content); + + if (!emit_event(callback, FetchEvent::Type::VerifyEnd, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + return FetchResult::Success; +} + +auto fetch_and_write( + const std::string &dependency_uri, + const std::filesystem::path &dependency_path, + const sourcemeta::blaze::Configuration::FetchCallback &fetcher, + const sourcemeta::core::SchemaResolver &resolver, + const sourcemeta::blaze::Configuration::WriteCallback &writer, + const sourcemeta::blaze::Configuration::FetchEvent::Callback &callback, + const std::optional &default_dialect, + std::size_t index, std::size_t total, sourcemeta::core::JSON &out_schema) + -> FetchResult { + using FetchEvent = sourcemeta::blaze::Configuration::FetchEvent; + + if (!emit_event(callback, FetchEvent::Type::FetchStart, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + try { + out_schema = fetcher(dependency_uri); + } catch (...) { + emit_event(callback, FetchEvent::Type::Error, dependency_uri, + dependency_path, index, total, "Failed to fetch schema", + std::current_exception()); + return FetchResult::Error; + } + + if (!emit_event(callback, FetchEvent::Type::FetchEnd, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + if (!emit_event(callback, FetchEvent::Type::BundleStart, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + try { + const std::string default_dialect_value{default_dialect.value_or("")}; + sourcemeta::core::bundle(out_schema, sourcemeta::core::schema_walker, + resolver, default_dialect_value, dependency_uri); + } catch (...) { + emit_event(callback, FetchEvent::Type::Error, dependency_uri, + dependency_path, index, total, "Failed to bundle schema", + std::current_exception()); + return FetchResult::Error; + } + + if (!emit_event(callback, FetchEvent::Type::BundleEnd, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + if (!emit_event(callback, FetchEvent::Type::WriteStart, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + try { + writer(dependency_path, out_schema); + } catch (...) { + emit_event(callback, FetchEvent::Type::Error, dependency_uri, + dependency_path, index, total, "Failed to write schema", + std::current_exception()); + return FetchResult::Error; + } + + if (!emit_event(callback, FetchEvent::Type::WriteEnd, dependency_uri, + dependency_path, index, total, {}, nullptr, true)) { + return FetchResult::Aborted; + } + + return FetchResult::Success; +} + +} // namespace + +namespace sourcemeta::blaze { + +auto Configuration::fetch(Lock &lock, const FetchCallback &fetcher, + const sourcemeta::core::SchemaResolver &resolver, + const ReadCallback &reader, + const WriteCallback &writer, + const FetchEvent::Callback &callback, + const FetchMode mode, + [[maybe_unused]] std::size_t concurrency) const + -> void { + const auto dependency_count{this->dependencies.size()}; + std::size_t current_index{0}; + + for (const auto &[dependency_uri, dependency_path] : this->dependencies) { + assert(dependency_path.is_absolute()); + const auto entry_status{ + lock.check(dependency_uri, dependency_path, reader)}; + + bool should_fetch{false}; + switch (entry_status) { + case Lock::Entry::Status::Untracked: + case Lock::Entry::Status::FileMissing: + case Lock::Entry::Status::Mismatched: + case Lock::Entry::Status::PathMismatch: + should_fetch = true; + break; + case Lock::Entry::Status::UpToDate: + should_fetch = (mode == FetchMode::All); + break; + } + + if (should_fetch) { + sourcemeta::core::JSON schema{sourcemeta::core::JSON::make_object()}; + const auto result{fetch_and_write( + dependency_uri, dependency_path, fetcher, resolver, writer, callback, + this->default_dialect, current_index, dependency_count, schema)}; + + switch (result) { + case FetchResult::Aborted: + case FetchResult::Error: + return; + case FetchResult::Success: + break; + } + + sourcemeta::core::JSON::String written_hash; + const auto verify_result{verify_written_schema( + dependency_uri, dependency_path, reader, callback, current_index, + dependency_count, written_hash)}; + + switch (verify_result) { + case FetchResult::Aborted: + case FetchResult::Error: + return; + case FetchResult::Success: + break; + } + + lock.emplace(dependency_uri, dependency_path, written_hash); + } else { + if (!emit_event(callback, FetchEvent::Type::UpToDate, dependency_uri, + dependency_path, current_index, dependency_count, {}, + nullptr, true)) { + return; + } + } + + current_index += 1; + } + + std::vector orphaned_uris; + for (const auto &[lock_uri, lock_entry] : lock) { + if (!this->dependencies.contains(lock_uri)) { + orphaned_uris.push_back(lock_uri); + if (!emit_event(callback, FetchEvent::Type::Orphaned, lock_uri, + lock_entry.path, 0, 0, {}, nullptr, true)) { + return; + } + } + } + + for (const auto &uri : orphaned_uris) { + lock.erase(uri); + } +} + +auto Configuration::fetch(const Lock &lock, const FetchCallback &fetcher, + const sourcemeta::core::SchemaResolver &resolver, + const ReadCallback &reader, + const WriteCallback &writer, + const FetchEvent::Callback &callback, + const bool dry_run, + [[maybe_unused]] std::size_t concurrency) const + -> void { + const auto dependency_count{this->dependencies.size()}; + std::size_t current_index{0}; + + for (const auto &[dependency_uri, dependency_path] : this->dependencies) { + assert(dependency_path.is_absolute()); + const auto entry_status{ + lock.check(dependency_uri, dependency_path, reader)}; + switch (entry_status) { + case Lock::Entry::Status::Untracked: + if (!emit_event(callback, FetchEvent::Type::Untracked, dependency_uri, + dependency_path, current_index, dependency_count, {}, + nullptr, true)) { + return; + } + break; + + case Lock::Entry::Status::FileMissing: { + if (dry_run) { + if (!emit_event(callback, FetchEvent::Type::FileMissing, + dependency_uri, dependency_path, current_index, + dependency_count, {}, nullptr, true)) { + return; + } + } else { + sourcemeta::core::JSON schema{sourcemeta::core::JSON::make_object()}; + const auto result{ + fetch_and_write(dependency_uri, dependency_path, fetcher, + resolver, writer, callback, this->default_dialect, + current_index, dependency_count, schema)}; + + switch (result) { + case FetchResult::Aborted: + case FetchResult::Error: + return; + case FetchResult::Success: + break; + } + + sourcemeta::core::JSON::String written_hash; + const auto verify_result{verify_written_schema( + dependency_uri, dependency_path, reader, callback, current_index, + dependency_count, written_hash)}; + + switch (verify_result) { + case FetchResult::Aborted: + case FetchResult::Error: + return; + case FetchResult::Success: + break; + } + + assert(lock.at(dependency_uri).has_value()); + const auto &lock_entry{lock.at(dependency_uri)->get()}; + if (written_hash != lock_entry.hash) { + emit_event(callback, FetchEvent::Type::Error, dependency_uri, + dependency_path, current_index, dependency_count, + "Written file hash does not match lock file"); + return; + } + } + break; + } + + case Lock::Entry::Status::Mismatched: + if (!emit_event(callback, FetchEvent::Type::Mismatched, dependency_uri, + dependency_path, current_index, dependency_count, {}, + nullptr, true)) { + return; + } + if (!dry_run) { + emit_event(callback, FetchEvent::Type::Error, dependency_uri, + dependency_path, current_index, dependency_count, + "File hash does not match lock file in frozen mode"); + return; + } + break; + + case Lock::Entry::Status::PathMismatch: + if (!emit_event(callback, FetchEvent::Type::PathMismatch, + dependency_uri, dependency_path, current_index, + dependency_count, {}, nullptr, true)) { + return; + } + if (!dry_run) { + emit_event(callback, FetchEvent::Type::Error, dependency_uri, + dependency_path, current_index, dependency_count, + "Configured path does not match lock file in frozen mode"); + return; + } + break; + + case Lock::Entry::Status::UpToDate: + if (!emit_event(callback, FetchEvent::Type::UpToDate, dependency_uri, + dependency_path, current_index, dependency_count, {}, + nullptr, true)) { + return; + } + break; + } + + current_index += 1; + } + + for (const auto &[lock_uri, lock_entry] : lock) { + if (!this->dependencies.contains(lock_uri)) { + if (!emit_event(callback, FetchEvent::Type::Orphaned, lock_uri, + lock_entry.path, 0, 0, {}, nullptr, true)) { + return; + } + } + } +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/configuration/include/sourcemeta/blaze/configuration.h b/vendor/blaze/src/configuration/include/sourcemeta/blaze/configuration.h new file mode 100644 index 0000000..22430be --- /dev/null +++ b/vendor/blaze/src/configuration/include/sourcemeta/blaze/configuration.h @@ -0,0 +1,232 @@ +#ifndef SOURCEMETA_BLAZE_CONFIGURATION_H_ +#define SOURCEMETA_BLAZE_CONFIGURATION_H_ + +/// @defgroup configuration Configuration +/// @brief A configuration manifest for JSON Schema projects +/// +/// This functionality is included as follows: +/// +/// ```cpp +/// #include +/// ``` + +#ifndef SOURCEMETA_BLAZE_CONFIGURATION_EXPORT +#include +#endif + +// NOLINTBEGIN(misc-include-cleaner) +#include +// NOLINTEND(misc-include-cleaner) + +#include +#include +#include + +#include // std::size_t +#include // std::uint8_t +#include // std::exception_ptr +#include // std::filesystem +#include // std::function +#include // std::map +#include // std::optional +#include // std::string +#include // std::string_view +#include // std::unordered_map +// TODO(C++23): Consider std::flat_map/std::flat_set when available in libc++ +#include // std::unordered_set +#include // std::vector + +namespace sourcemeta::blaze { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup configuration +/// A configuration file format for JSON Schema projects +struct SOURCEMETA_BLAZE_CONFIGURATION_EXPORT Configuration { + std::optional title; + std::optional description; + std::optional email; + std::optional github; + std::optional website; + std::filesystem::path absolute_path; + bool absolute_path_explicit{false}; + std::filesystem::path base_path; + sourcemeta::core::JSON::String base; + std::optional default_dialect; + std::unordered_set extension{".json", ".yml", + ".yaml"}; + std::unordered_map + resolve; + // Ordered to guarantee deterministic iteration + std::map dependencies; + std::vector ignore; + + struct Lint { + std::vector rules; + }; + + Lint lint; + sourcemeta::core::JSON extra = sourcemeta::core::JSON::make_object(); + + /// A callback to read file contents from a path + using ReadCallback = + // TODO(C++23): Use std::move_only_function when available in libc++ + std::function; + + /// A lock file that tracks fetched dependency hashes + class SOURCEMETA_BLAZE_CONFIGURATION_EXPORT Lock { + public: + struct Entry { + std::filesystem::path path; + // TODO: Separately store the server ETag for exploiting HTTP caching? + sourcemeta::core::JSON::String hash; + enum class HashAlgorithm : std::uint8_t { SHA256 }; + HashAlgorithm hash_algorithm; + enum class Status : std::uint8_t { + Untracked, + FileMissing, + Mismatched, + PathMismatch, + UpToDate + }; + }; + + using const_iterator = + std::map::const_iterator; + + auto + emplace(const sourcemeta::core::JSON::String &uri, + const std::filesystem::path &path, + const sourcemeta::core::JSON::String &hash, + Entry::HashAlgorithm hash_algorithm = Entry::HashAlgorithm::SHA256) + -> void; + auto erase(const sourcemeta::core::JSON::String &uri) -> void; + + [[nodiscard]] auto size() const noexcept -> std::size_t; + [[nodiscard]] auto at(const sourcemeta::core::JSON::String &uri) const + -> std::optional>; + [[nodiscard]] auto begin() const noexcept -> const_iterator; + [[nodiscard]] auto end() const noexcept -> const_iterator; + + [[nodiscard]] + auto check(const sourcemeta::core::JSON::String &uri, + const std::filesystem::path &expected_path, + const ReadCallback &reader) const -> Entry::Status; + [[nodiscard]] + static auto from_json(const sourcemeta::core::JSON &value, + const std::filesystem::path &lock_base_path) -> Lock; + [[nodiscard]] + auto to_json(const std::filesystem::path &lock_base_path) const + -> sourcemeta::core::JSON; + + private: + // Ordered to guarantee deterministic iteration + std::map entries_; + }; + + /// An event emitted during dependency fetching + struct FetchEvent { + enum class Type : std::uint8_t { + FetchStart, + FetchEnd, + BundleStart, + BundleEnd, + WriteStart, + WriteEnd, + VerifyStart, + VerifyEnd, + UpToDate, + FileMissing, + Orphaned, + Mismatched, + PathMismatch, + Untracked, + Error + }; + + Type type; + std::string uri; + std::filesystem::path path; + std::size_t index; + std::size_t total; + std::string details; + std::exception_ptr exception; + + using Callback = std::function; + }; + + /// A callback to fetch a JSON Schema from a URI + using FetchCallback = + std::function; + + /// A callback to write a JSON Schema to a path + using WriteCallback = std::function; + + /// The mode for fetching dependencies with a mutable lock + enum class FetchMode : std::uint8_t { + /// Fetch only what's missing or untracked + Missing, + /// Re-fetch everything regardless of current state + All + }; + + /// Fetch dependencies, modifying the lock in-place + auto fetch(Lock &lock, const FetchCallback &fetcher, + const sourcemeta::core::SchemaResolver &resolver, + const ReadCallback &reader, const WriteCallback &writer, + const FetchEvent::Callback &on_event, FetchMode mode, + // TODO: Make this work for real + std::size_t concurrency = 1) const -> void; + + /// Fetch dependencies without modifying the lock file (frozen mode) + auto fetch(const Lock &lock, const FetchCallback &fetcher, + const sourcemeta::core::SchemaResolver &resolver, + const ReadCallback &reader, const WriteCallback &writer, + const FetchEvent::Callback &on_event, bool dry_run = false, + // TODO: Make this work for real + std::size_t concurrency = 1) const -> void; + + /// Add a dependency to the configuration + auto add_dependency(const sourcemeta::core::URI &uri, + const std::filesystem::path &path) -> void; + + /// Check if the given path represents a schema described by this + /// configuration + [[nodiscard]] + auto applies_to(const std::filesystem::path &path) const -> bool; + + /// Parse a configuration file from its contents + [[nodiscard]] + static auto from_json(const sourcemeta::core::JSON &value, + const std::filesystem::path &base_path) + -> Configuration; + + /// Serialize a configuration to JSON + [[nodiscard]] + auto to_json() const -> sourcemeta::core::JSON; + + /// Read and parse a configuration file + [[nodiscard]] + static auto read_json(const std::filesystem::path &path, + const ReadCallback &reader) -> Configuration; + + /// A nearest ancestor configuration lookup + [[nodiscard]] + static auto find(const std::filesystem::path &path) + -> std::optional; +}; + +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/configuration/include/sourcemeta/blaze/configuration_error.h b/vendor/blaze/src/configuration/include/sourcemeta/blaze/configuration_error.h new file mode 100644 index 0000000..6fefdf2 --- /dev/null +++ b/vendor/blaze/src/configuration/include/sourcemeta/blaze/configuration_error.h @@ -0,0 +1,57 @@ +#ifndef SOURCEMETA_BLAZE_CONFIGURATION_ERROR_H_ +#define SOURCEMETA_BLAZE_CONFIGURATION_ERROR_H_ + +#ifndef SOURCEMETA_BLAZE_CONFIGURATION_EXPORT +#include +#endif + +#include + +#include // std::exception +#include // std::string +#include // std::string_view +#include // std::move + +namespace sourcemeta::blaze { + +// Exporting symbols that depends on the standard C++ library is considered +// safe. +// https://learn.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-2-c4275?view=msvc-170&redirectedfrom=MSDN +#if defined(_MSC_VER) +#pragma warning(disable : 4251 4275) +#endif + +/// @ingroup configuration +class SOURCEMETA_BLAZE_CONFIGURATION_EXPORT ConfigurationParseError + : public std::exception { +public: + ConfigurationParseError(const char *message, + sourcemeta::core::Pointer location) + : message_{message}, location_{std::move(location)} {} + ConfigurationParseError(std::string message, + sourcemeta::core::Pointer location) = delete; + ConfigurationParseError(std::string &&message, + sourcemeta::core::Pointer location) = delete; + ConfigurationParseError(std::string_view message, + sourcemeta::core::Pointer location) = delete; + + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_; + } + + [[nodiscard]] auto location() const noexcept -> const auto & { + return this->location_; + } + +private: + const char *message_; + sourcemeta::core::Pointer location_; +}; + +#if defined(_MSC_VER) +#pragma warning(default : 4251 4275) +#endif + +} // namespace sourcemeta::blaze + +#endif diff --git a/vendor/blaze/src/configuration/json.cc b/vendor/blaze/src/configuration/json.cc new file mode 100644 index 0000000..532af7e --- /dev/null +++ b/vendor/blaze/src/configuration/json.cc @@ -0,0 +1,123 @@ +#include + +#include + +#include // std::ranges::sort +#include // std::string +#include // std::vector + +namespace sourcemeta::blaze { + +static auto relative_display_path(const std::filesystem::path &path, + const std::filesystem::path &base) + -> std::string { + auto relative_path{std::filesystem::relative(path, base).generic_string()}; + if (relative_path.starts_with("..")) { + return relative_path; + } + + return "./" + relative_path; +} + +auto Configuration::to_json() const -> sourcemeta::core::JSON { + auto result{sourcemeta::core::JSON::make_object()}; + + if (this->title.has_value()) { + result.assign("title", sourcemeta::core::JSON{this->title.value()}); + } + + if (this->description.has_value()) { + result.assign("description", + sourcemeta::core::JSON{this->description.value()}); + } + + if (this->email.has_value()) { + result.assign("email", sourcemeta::core::JSON{this->email.value()}); + } + + if (this->github.has_value()) { + result.assign("github", sourcemeta::core::JSON{this->github.value()}); + } + + if (this->website.has_value()) { + result.assign("website", sourcemeta::core::JSON{this->website.value()}); + } + + if (this->absolute_path_explicit) { + result.assign("path", + sourcemeta::core::JSON{this->absolute_path.generic_string()}); + } + + if (!this->base.empty()) { + result.assign("baseUri", sourcemeta::core::JSON{this->base}); + } + + if (this->default_dialect.has_value()) { + result.assign("defaultDialect", + sourcemeta::core::JSON{this->default_dialect.value()}); + } + + static const Configuration defaults; + if (!this->extension.empty() && this->extension != defaults.extension) { + auto extension_array{sourcemeta::core::JSON::make_array()}; + // Sort for deterministic output + std::vector sorted_extensions{this->extension.cbegin(), + this->extension.cend()}; + std::ranges::sort(sorted_extensions); + for (const auto &entry : sorted_extensions) { + extension_array.push_back(sourcemeta::core::JSON{entry}); + } + + result.assign("extension", std::move(extension_array)); + } + + if (!this->resolve.empty()) { + auto resolve_object{sourcemeta::core::JSON::make_object()}; + for (const auto &pair : this->resolve) { + resolve_object.assign(pair.first, sourcemeta::core::JSON{pair.second}); + } + + result.assign("resolve", std::move(resolve_object)); + } + + if (!this->dependencies.empty()) { + auto dependencies_object{sourcemeta::core::JSON::make_object()}; + for (const auto &pair : this->dependencies) { + dependencies_object.assign( + pair.first, sourcemeta::core::JSON{ + relative_display_path(pair.second, this->base_path)}); + } + + result.assign("dependencies", std::move(dependencies_object)); + } + + if (!this->ignore.empty()) { + auto ignore_array{sourcemeta::core::JSON::make_array()}; + for (const auto &entry : this->ignore) { + ignore_array.push_back(sourcemeta::core::JSON{ + relative_display_path(entry, this->base_path)}); + } + + result.assign("ignore", std::move(ignore_array)); + } + + if (!this->lint.rules.empty()) { + auto lint_object{sourcemeta::core::JSON::make_object()}; + auto rules_array{sourcemeta::core::JSON::make_array()}; + for (const auto &rule : this->lint.rules) { + rules_array.push_back( + sourcemeta::core::JSON{relative_display_path(rule, this->base_path)}); + } + + lint_object.assign("rules", std::move(rules_array)); + result.assign("lint", std::move(lint_object)); + } + + for (const auto &pair : this->extra.as_object()) { + result.assign(pair.first, pair.second); + } + + return result; +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/configuration/lock.cc b/vendor/blaze/src/configuration/lock.cc new file mode 100644 index 0000000..89ced3f --- /dev/null +++ b/vendor/blaze/src/configuration/lock.cc @@ -0,0 +1,250 @@ +#include + +#include +#include +#include + +#include // assert +#include // std::ostringstream +#include // std::move + +namespace { + +auto compute_hash( + const std::string &content, + const sourcemeta::blaze::Configuration::Lock::Entry::HashAlgorithm + algorithm, + const sourcemeta::core::Pointer &location) + -> sourcemeta::core::JSON::String { + using HashAlgorithm = + sourcemeta::blaze::Configuration::Lock::Entry::HashAlgorithm; + switch (algorithm) { + case HashAlgorithm::SHA256: { + std::ostringstream result; + sourcemeta::core::sha256(content, result); + return result.str(); + } + default: + throw sourcemeta::blaze::ConfigurationParseError("Unknown hash algorithm", + location); + } +} + +auto hash_algorithm_to_string( + const sourcemeta::blaze::Configuration::Lock::Entry::HashAlgorithm + algorithm, + const sourcemeta::core::Pointer &location) + -> sourcemeta::core::JSON::String { + using HashAlgorithm = + sourcemeta::blaze::Configuration::Lock::Entry::HashAlgorithm; + switch (algorithm) { + case HashAlgorithm::SHA256: + return "sha256"; + default: + throw sourcemeta::blaze::ConfigurationParseError("Unknown hash algorithm", + location); + } +} + +auto string_to_hash_algorithm(const sourcemeta::core::JSON::String &value, + const sourcemeta::core::Pointer &location) + -> sourcemeta::blaze::Configuration::Lock::Entry::HashAlgorithm { + using HashAlgorithm = + sourcemeta::blaze::Configuration::Lock::Entry::HashAlgorithm; + if (value == "sha256") { + return HashAlgorithm::SHA256; + } + + throw sourcemeta::blaze::ConfigurationParseError("Unknown hash algorithm", + location); +} + +} // namespace + +namespace sourcemeta::blaze { + +auto Configuration::Lock::from_json(const sourcemeta::core::JSON &value, + const std::filesystem::path &lock_base_path) + -> Lock { + assert(lock_base_path.is_absolute()); + Lock result; + + if (!value.is_object()) [[unlikely]] { + throw ConfigurationParseError("The lock file must be an object", {}); + } + + if (!value.defines("version")) [[unlikely]] { + throw ConfigurationParseError("The lock file must have a version property", + {}); + } + + if (!value.at("version").is_integer() || + value.at("version").to_integer() != 1) [[unlikely]] { + throw ConfigurationParseError("Unsupported lock file version", {"version"}); + } + + if (value.defines("dependencies")) { + if (!value.at("dependencies").is_object()) [[unlikely]] { + throw ConfigurationParseError( + "The lock file dependencies property must be an object", + {"dependencies"}); + } + + for (const auto &pair : value.at("dependencies").as_object()) { + if (!pair.second.is_object()) [[unlikely]] { + throw ConfigurationParseError( + "The lock file dependency entry must be an object", + {"dependencies", pair.first}); + } + + if (!pair.second.defines("path") || !pair.second.at("path").is_string()) + [[unlikely]] { + throw ConfigurationParseError( + "The lock file dependency entry must have a path", + {"dependencies", pair.first, "path"}); + } + + if (!pair.second.defines("hash") || !pair.second.at("hash").is_string()) + [[unlikely]] { + throw ConfigurationParseError( + "The lock file dependency entry must have a hash", + {"dependencies", pair.first, "hash"}); + } + + if (!pair.second.defines("hashAlgorithm") || + !pair.second.at("hashAlgorithm").is_string()) [[unlikely]] { + throw ConfigurationParseError( + "The lock file dependency entry must have a hash algorithm", + {"dependencies", pair.first, "hashAlgorithm"}); + } + + const std::filesystem::path entry_path{ + pair.second.at("path").to_string()}; + + Entry entry; + if (entry_path.is_absolute()) { + entry.path = entry_path; + } else { + try { + entry.path = + std::filesystem::weakly_canonical(lock_base_path / entry_path); + } catch (const std::filesystem::filesystem_error &) { + throw ConfigurationParseError( + "The lock file dependency entry path could not be resolved", + {"dependencies", pair.first, "path"}); + } + } + + entry.hash = pair.second.at("hash").to_string(); + entry.hash_algorithm = string_to_hash_algorithm( + pair.second.at("hashAlgorithm").to_string(), + {"dependencies", pair.first, "hashAlgorithm"}); + + result.entries_.emplace(pair.first, std::move(entry)); + } + } + + return result; +} + +auto Configuration::Lock::to_json(const std::filesystem::path &lock_base_path) + const -> sourcemeta::core::JSON { + assert(lock_base_path.is_absolute()); + auto result{sourcemeta::core::JSON::make_object()}; + result.assign("version", sourcemeta::core::JSON{1}); + + auto dependencies_json{sourcemeta::core::JSON::make_object()}; + for (const auto &pair : this->entries_) { + assert(pair.second.path.is_absolute()); + auto entry_json{sourcemeta::core::JSON::make_object()}; + auto relative_path{ + std::filesystem::relative(pair.second.path, lock_base_path) + .generic_string()}; + if (!relative_path.starts_with("..")) { + relative_path.insert(0, "./"); + } + + entry_json.assign("path", sourcemeta::core::JSON{relative_path}); + entry_json.assign("hash", sourcemeta::core::JSON{pair.second.hash}); + entry_json.assign("hashAlgorithm", + sourcemeta::core::JSON{hash_algorithm_to_string( + pair.second.hash_algorithm, + {"dependencies", pair.first, "hashAlgorithm"})}); + dependencies_json.assign(pair.first, std::move(entry_json)); + } + + result.assign("dependencies", std::move(dependencies_json)); + return result; +} + +auto Configuration::Lock::emplace(const sourcemeta::core::JSON::String &uri, + const std::filesystem::path &path, + const sourcemeta::core::JSON::String &hash, + Entry::HashAlgorithm hash_algorithm) -> void { + assert(path.is_absolute()); + Entry entry; + entry.path = path; + entry.hash = hash; + entry.hash_algorithm = hash_algorithm; + this->entries_.insert_or_assign(uri, std::move(entry)); +} + +auto Configuration::Lock::erase(const sourcemeta::core::JSON::String &uri) + -> void { + this->entries_.erase(uri); +} + +auto Configuration::Lock::size() const noexcept -> std::size_t { + return this->entries_.size(); +} + +auto Configuration::Lock::at(const sourcemeta::core::JSON::String &uri) const + -> std::optional> { + const auto iterator{this->entries_.find(uri)}; + if (iterator == this->entries_.cend()) { + return std::nullopt; + } + + return std::cref(iterator->second); +} + +auto Configuration::Lock::begin() const noexcept -> const_iterator { + return this->entries_.cbegin(); +} + +auto Configuration::Lock::end() const noexcept -> const_iterator { + return this->entries_.cend(); +} + +auto Configuration::Lock::check(const sourcemeta::core::JSON::String &uri, + const std::filesystem::path &expected_path, + const Configuration::ReadCallback &reader) const + -> Entry::Status { + const auto iterator{this->entries_.find(uri)}; + if (iterator == this->entries_.cend()) { + return Entry::Status::Untracked; + } + + const auto &entry{iterator->second}; + + if (entry.path != expected_path) { + return Entry::Status::PathMismatch; + } + + std::string content; + try { + content = reader(entry.path); + } catch (...) { + return Entry::Status::FileMissing; + } + + const auto current_hash{ + compute_hash(content, entry.hash_algorithm, {"dependencies", uri})}; + if (current_hash != entry.hash) { + return Entry::Status::Mismatched; + } + + return Entry::Status::UpToDate; +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/configuration/parse.cc b/vendor/blaze/src/configuration/parse.cc new file mode 100644 index 0000000..9bb2913 --- /dev/null +++ b/vendor/blaze/src/configuration/parse.cc @@ -0,0 +1,254 @@ +#include + +#include +#include + +#include // assert + +namespace sourcemeta::blaze { + +auto Configuration::from_json(const sourcemeta::core::JSON &value, + const std::filesystem::path &base_path) + -> Configuration { + assert(base_path.is_absolute()); + Configuration result; + result.base_path = base_path; + +#define CONFIGURATION_ENSURE(condition, message, location) \ + if (!(condition)) [[unlikely]] { \ + throw ConfigurationParseError((message), \ + sourcemeta::core::Pointer(location)); \ + } + + // Just to keep this decoupled from actual JSON Schema validator, which we + // don't do in this repository + CONFIGURATION_ENSURE(value.is_object(), "The configuration must be an object", + {}); + CONFIGURATION_ENSURE(!value.defines("title") || value.at("title").is_string(), + "The title property must be a string", {"title"}); + CONFIGURATION_ENSURE( + !value.defines("description") || value.at("description").is_string(), + "The description property must be a string", {"description"}); + CONFIGURATION_ENSURE(!value.defines("email") || value.at("email").is_string(), + "The email property must be a string", {"email"}); + CONFIGURATION_ENSURE(!value.defines("github") || + value.at("github").is_string(), + "The github property must be a string", {"github"}); + CONFIGURATION_ENSURE(!value.defines("website") || + value.at("website").is_string(), + "The website property must be a string", {"website"}); + CONFIGURATION_ENSURE(!value.defines("path") || value.at("path").is_string(), + "The path property must be a string", {"path"}); + CONFIGURATION_ENSURE(!value.defines("baseUri") || + value.at("baseUri").is_string(), + "The baseUri property must be a string", {"baseUri"}); + CONFIGURATION_ENSURE(!value.defines("defaultDialect") || + value.at("defaultDialect").is_string(), + "The defaultDialect property must be a string", + {"defaultDialect"}); + CONFIGURATION_ENSURE( + !value.defines("extension") || value.at("extension").is_array() || + value.at("extension").is_string(), + "The extension property must be a string or an array", {"extension"}); + CONFIGURATION_ENSURE(!value.defines("resolve") || + value.at("resolve").is_object(), + "The resolve property must be an object", {"resolve"}); + CONFIGURATION_ENSURE( + !value.defines("dependencies") || value.at("dependencies").is_object(), + "The dependencies property must be an object", {"dependencies"}); + + result.title = + sourcemeta::core::from_json( + value.at_or("title", sourcemeta::core::JSON{nullptr})); + result.description = + sourcemeta::core::from_json( + value.at_or("description", sourcemeta::core::JSON{nullptr})); + result.email = + sourcemeta::core::from_json( + value.at_or("email", sourcemeta::core::JSON{nullptr})); + result.github = + sourcemeta::core::from_json( + value.at_or("github", sourcemeta::core::JSON{nullptr})); + result.website = + sourcemeta::core::from_json( + value.at_or("website", sourcemeta::core::JSON{nullptr})); + + if (value.defines("path")) { + const std::filesystem::path path{value.at("path").to_string()}; + if (path.is_absolute()) { + result.absolute_path = std::filesystem::weakly_canonical(path); + } else { + result.absolute_path = + std::filesystem::weakly_canonical(base_path / path); + } + + result.absolute_path_explicit = true; + } else { + result.absolute_path = std::filesystem::weakly_canonical(base_path); + } + + assert(result.absolute_path.is_absolute()); + + if (value.defines("baseUri")) { + try { + sourcemeta::core::URI base{value.at("baseUri").to_string()}; + base.canonicalize(); + if (!base.is_absolute()) { + CONFIGURATION_ENSURE( + false, "The baseUri property must be an absolute URI", {"baseUri"}); + } + + result.base = base.recompose(); + } catch (const sourcemeta::core::URIParseError &) { + CONFIGURATION_ENSURE(false, + "The baseUri property must represent a valid URI", + {"baseUri"}); + } + } else { + // Otherwise the base is the directory + result.base = + sourcemeta::core::URI::from_path(result.absolute_path).recompose(); + } + + result.default_dialect = + sourcemeta::core::from_json( + value.at_or("defaultDialect", sourcemeta::core::JSON{nullptr})); + + if (value.defines("extension")) { + result.extension.clear(); + const auto &extension_value{value.at("extension")}; + if (extension_value.is_string()) { + auto extension_string{extension_value.to_string()}; + if (!extension_string.empty() && extension_string.front() != '.') { + extension_string.insert(extension_string.begin(), '.'); + } + + result.extension.emplace(std::move(extension_string)); + } else { + // TODO(C++23): Use std::views::enumerate when available in libc++ + std::size_t index{0}; + for (const auto &element : extension_value.as_array()) { + CONFIGURATION_ENSURE( + element.is_string(), + "The values in the extension array must be strings", + sourcemeta::core::Pointer({"extension", index})); + + auto extension_string{element.to_string()}; + if (!extension_string.empty() && extension_string.front() != '.') { + extension_string.insert(extension_string.begin(), '.'); + } + + result.extension.emplace(std::move(extension_string)); + index += 1; + } + } + } + + if (value.defines("resolve")) { + for (const auto &pair : value.at("resolve").as_object()) { + CONFIGURATION_ENSURE(pair.second.is_string(), + "The values in the resolve object must be strings", + sourcemeta::core::Pointer({"resolve", pair.first})); + + try { + result.resolve.emplace(pair.first, sourcemeta::core::URI::canonicalize( + pair.second.to_string())); + } catch (const sourcemeta::core::URIParseError &) { + CONFIGURATION_ENSURE( + false, "The values in the resolve object must represent valid URIs", + sourcemeta::core::Pointer({"resolve", pair.first})); + } + } + } + + if (value.defines("dependencies")) { + for (const auto &pair : value.at("dependencies").as_object()) { + CONFIGURATION_ENSURE( + pair.second.is_string(), + "The values in the dependencies object must be strings", + sourcemeta::core::Pointer({"dependencies", pair.first})); + + const std::filesystem::path dependency_path{pair.second.to_string()}; + const auto absolute_dependency_path = + dependency_path.is_absolute() + ? std::filesystem::weakly_canonical(dependency_path) + : std::filesystem::weakly_canonical(base_path / dependency_path); + try { + result.add_dependency(sourcemeta::core::URI{pair.first}, + absolute_dependency_path); + } catch (const sourcemeta::core::URIParseError &) { + CONFIGURATION_ENSURE( + false, "The dependency URI is not valid", + sourcemeta::core::Pointer({"dependencies", pair.first})); + } + } + } + + CONFIGURATION_ENSURE(!value.defines("lint") || value.at("lint").is_object(), + "The lint property must be an object", {"lint"}); + + if (value.defines("lint")) { + const auto &lint_value{value.at("lint")}; + CONFIGURATION_ENSURE(!lint_value.defines("rules") || + lint_value.at("rules").is_array(), + "The lint rules property must be an array", + sourcemeta::core::Pointer({"lint", "rules"})); + + if (lint_value.defines("rules")) { + std::size_t index{0}; + for (const auto &element : lint_value.at("rules").as_array()) { + CONFIGURATION_ENSURE( + element.is_string(), + "The values in the lint rules array must be strings", + sourcemeta::core::Pointer({"lint", "rules", index})); + + const std::filesystem::path path{element.to_string()}; + result.lint.rules.push_back( + path.is_absolute() + ? std::filesystem::weakly_canonical(path) + : std::filesystem::weakly_canonical(base_path / path)); + index += 1; + } + } + } + + CONFIGURATION_ENSURE(!value.defines("ignore") || + value.at("ignore").is_array(), + "The ignore property must be an array", {"ignore"}); + + if (value.defines("ignore")) { + std::size_t index{0}; + for (const auto &element : value.at("ignore").as_array()) { + CONFIGURATION_ENSURE(element.is_string(), + "The values in the ignore array must be strings", + sourcemeta::core::Pointer({"ignore", index})); + + const std::filesystem::path path{element.to_string()}; + result.ignore.push_back( + path.is_absolute() + ? std::filesystem::weakly_canonical(path) + : std::filesystem::weakly_canonical(base_path / path)); + index += 1; + } + } + + assert(result.extra.is_object()); + for (const auto &subentry : value.as_object()) { + if (subentry.first.starts_with("x-")) { + result.extra.assign(subentry.first, subentry.second); + } + } + +#undef CONFIGURATION_ENSURE + return result; +} + +auto Configuration::read_json(const std::filesystem::path &path, + const Configuration::ReadCallback &reader) + -> Configuration { + assert(path.is_absolute()); + const auto value{sourcemeta::core::parse_json(reader(path))}; + return from_json(value, path.parent_path()); +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/evaluator/CMakeLists.txt b/vendor/blaze/src/evaluator/CMakeLists.txt new file mode 100644 index 0000000..e58c9a1 --- /dev/null +++ b/vendor/blaze/src/evaluator/CMakeLists.txt @@ -0,0 +1,21 @@ +sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME evaluator + FOLDER "Blaze/Evaluator" + PRIVATE_HEADERS error.h value.h instruction.h string_set.h dispatch.h + SOURCES evaluator_json.cc evaluator_describe.cc) + +if(BLAZE_INSTALL) + sourcemeta_library_install(NAMESPACE sourcemeta PROJECT blaze NAME evaluator) +endif() + +if(PROJECT_IS_TOP_LEVEL) + sourcemeta_add_vectorization_diagnostics(sourcemeta_blaze_evaluator) +endif() + +target_link_libraries(sourcemeta_blaze_evaluator PUBLIC + sourcemeta::core::json) +target_link_libraries(sourcemeta_blaze_evaluator PUBLIC + sourcemeta::core::regex) +target_link_libraries(sourcemeta_blaze_evaluator PUBLIC + sourcemeta::core::jsonpointer) +target_link_libraries(sourcemeta_blaze_evaluator PRIVATE + sourcemeta::core::uri) diff --git a/vendor/blaze/src/evaluator/evaluator_describe.cc b/vendor/blaze/src/evaluator/evaluator_describe.cc new file mode 100644 index 0000000..99a2556 --- /dev/null +++ b/vendor/blaze/src/evaluator/evaluator_describe.cc @@ -0,0 +1,2405 @@ +#include + +#include // std::ranges::any_of +#include // assert +#include // std::ostringstream +#include // std::string +#include // std::string_view +#include // std::to_underlying, std::unreachable +#include // std::visit + +namespace { +using namespace sourcemeta::blaze; + +template +auto instruction_value(const T &step) -> decltype(auto) { + if constexpr (requires { step.value; }) { + return std::get(step.value); + } else { + return step.id; + } +} + +template +auto describe_stringify(const sourcemeta::core::JSON &value, T &stream) + -> void { + if (value.is_decimal()) { + stream << value.to_decimal().to_string(); + } else { + sourcemeta::core::stringify(value, stream); + } +} + +auto type_name(const sourcemeta::core::JSON::Type type) -> std::string_view { + using Type = sourcemeta::core::JSON::Type; + switch (type) { + case Type::Null: + return "null"; + case Type::Boolean: + return "boolean"; + case Type::Integer: + return "integer"; + case Type::Real: + case Type::Decimal: + return "number"; + case Type::String: + return "string"; + case Type::Array: + return "array"; + case Type::Object: + return "object"; + default: + std::unreachable(); + } +} + +auto value_type_name(const sourcemeta::core::JSON &value) -> std::string_view { + if (value.type() == sourcemeta::core::JSON::Type::Decimal) { + return value.to_decimal().is_integer() ? "integer" : "number"; + } + return type_name(value.type()); +} + +auto escape_string(const std::string &input) -> std::string { + std::size_t size{2}; + for (const auto character : input) { + size += (character == '"') ? 2 : 1; + } + + std::string result; + result.resize_and_overwrite(size, [&](char *buffer, std::size_t) { + auto *cursor{buffer}; + *cursor++ = '"'; + for (const auto character : input) { + if (character == '"') { + *cursor++ = '\\'; + } + *cursor++ = character; + } + *cursor++ = '"'; + return static_cast(cursor - buffer); + }); + + return result; +} + +auto describe_type_check(const bool valid, + const sourcemeta::core::JSON::Type current, + const sourcemeta::core::JSON::Type expected, + std::ostringstream &message) -> void { + message << "The value was expected to be of type "; + message << type_name(expected); + if (!valid) { + message << " but it was of type "; + if (current == sourcemeta::core::JSON::Type::Decimal) { + message << "number"; + } else { + message << type_name(current); + } + } +} + +auto describe_types_check(const bool valid, + const sourcemeta::core::JSON::Type current, + const ValueTypes expected, + std::ostringstream &message) -> void { + ValueTypes types{expected}; + const auto has_real{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Real))}; + const auto has_integer{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Integer))}; + const auto has_decimal{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Decimal))}; + + if (has_real && has_integer) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + } + if (has_real && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + if (has_integer && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + + const auto popcount{types.count()}; + + if (popcount == 1) { + std::uint8_t type_index{0}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + type_index = bit; + break; + } + } + describe_type_check(valid, current, + static_cast(type_index), + message); + return; + } + + message << "The value was expected to be of type "; + bool first{true}; + std::uint8_t last_bit{255}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + last_bit = bit; + } + } + + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + if (!first) { + message << ", "; + } + if (bit == last_bit) { + message << "or "; + } + message << type_name(static_cast(bit)); + first = false; + } + } + + if (valid) { + message << " and it was of type "; + } else { + message << " but it was of type "; + } + + if (valid && current == sourcemeta::core::JSON::Type::Decimal && + has_integer && !has_real) { + message << "integer"; + } else if ((valid && current == sourcemeta::core::JSON::Type::Integer && + has_real) || + current == sourcemeta::core::JSON::Type::Decimal) { + message << "number"; + } else { + message << type_name(current); + } +} + +auto describe_reference(const sourcemeta::core::JSON &target) -> std::string { + std::ostringstream message; + message << "The " << type_name(target.type()) + << " value was expected to validate against the referenced schema"; + return message.str(); +} + +auto is_within_keyword(const sourcemeta::core::WeakPointer &evaluate_path, + const std::string &keyword) -> bool { + return std::ranges::any_of(evaluate_path, [&keyword](const auto &token) { + return token.is_property() && token.to_property() == keyword; + }); +} + +auto unknown() -> std::string { + assert(false); + return ""; +} + +} // namespace + +namespace sourcemeta::blaze { + +// TODO: What will unlock even better error messages is being able to +// get the subschema being evaluated along with the keyword +auto describe(const bool valid, const Instruction &step, + const sourcemeta::core::WeakPointer &evaluate_path, + const sourcemeta::core::WeakPointer &instance_location, + const sourcemeta::core::JSON &instance, + const sourcemeta::core::JSON &annotation) -> std::string { + const std::string keyword{evaluate_path.empty() || + // The last token can be an index for + // boolean schemas inside array applicators + !evaluate_path.back().is_property() + ? "" + : evaluate_path.back().to_property()}; + const sourcemeta::core::JSON &target{get(instance, instance_location)}; + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionFail) { + if (keyword == "contains") { + return "The constraints declared for this keyword were not satisfiable"; + } + + if (keyword == "additionalProperties" || + keyword == "unevaluatedProperties") { + std::ostringstream message; + assert(!instance_location.empty()); + assert(instance_location.back().is_property()); + message << "The object value was not expected to define the property " + << escape_string(instance_location.back().to_property()); + return message.str(); + } + + if (keyword == "unevaluatedItems") { + std::ostringstream message; + assert(!instance_location.empty()); + assert(instance_location.back().is_index()); + message << "The array value was not expected to define the item at index " + << instance_location.back().to_index(); + return message.str(); + } + + return "No instance is expected to succeed against the false schema"; + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalOr) { + assert(!step.children.empty()); + std::ostringstream message; + message << "The " << type_name(target.type()) + << " value was expected to validate against "; + if (step.children.size() > 1) { + message << "at least one of the " << step.children.size() + << " given subschemas"; + } else { + message << "the given subschema"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalAnd) { + if (keyword == "allOf") { + assert(!step.children.empty()); + std::ostringstream message; + message << "The " << type_name(target.type()) + << " value was expected to validate against the "; + if (step.children.size() > 1) { + message << step.children.size() << " given subschemas"; + } else { + message << "given subschema"; + } + + return message.str(); + } + + if (keyword == "$ref") { + return describe_reference(target); + } + + return unknown(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalXor) { + assert(!step.children.empty()); + std::ostringstream message; + + if (std::ranges::any_of(evaluate_path, + [](const auto &token) { + return token.is_property() && + token.to_property() == "propertyNames"; + }) && + !instance_location.empty() && instance_location.back().is_property()) { + message << "The property name " + << escape_string(instance_location.back().to_property()); + } else { + message << "The " << type_name(target.type()) << " value"; + } + + message << " was expected to validate against "; + if (step.children.size() > 1) { + message << "one and only one of the " << step.children.size() + << " given subschemas"; + } else { + message << "the given subschema"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalCondition) { + std::ostringstream message; + message << "The " << type_name(target.type()) + << " value was expected to validate against the given conditional"; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalNot) { + std::ostringstream message; + message + << "The " << type_name(target.type()) + << " value was expected to not validate against the given subschema"; + if (!valid) { + message << ", but it did"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalNotEvaluate) { + std::ostringstream message; + message + << "The " << type_name(target.type()) + << " value was expected to not validate against the given subschema"; + if (!valid) { + message << ", but it did"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::Evaluate) { + return "The instance location was marked as evaluated"; + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::ControlDynamicAnchorJump) { + if (keyword == "$dynamicRef") { + const auto &value{instruction_value(step)}; + std::ostringstream message; + message << "The " << type_name(target.type()) + << " value was expected to validate against the first subschema " + "in scope that declared the dynamic anchor " + << escape_string(value); + return message.str(); + } + + assert(keyword == "$recursiveRef"); + std::ostringstream message; + message << "The " << type_name(target.type()) + << " value was expected to validate against the first subschema " + "in scope that declared a recursive anchor"; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AnnotationEmit) { + if (keyword == "properties") { + assert(annotation.is_string()); + std::ostringstream message; + message << "The object property " << escape_string(annotation.to_string()) + << " successfully validated against its property " + "subschema"; + return message.str(); + } + + if ((keyword == "items" || keyword == "additionalItems") && + annotation.is_boolean() && annotation.to_boolean()) { + assert(target.is_array()); + return "Every item in the array value was successfully validated"; + } + + if ((keyword == "prefixItems" || keyword == "items") && + annotation.is_integer()) { + assert(target.is_array()); + assert(annotation.is_positive()); + std::ostringstream message; + if (annotation.to_integer() == 0) { + message << "The first item of the array value successfully validated " + "against the first " + "positional subschema"; + } else { + message << "The first " << annotation.to_integer() + 1 + << " items of the array value successfully validated against " + "the given " + "positional subschemas"; + } + + return message.str(); + } + + if (keyword == "prefixItems" && annotation.is_boolean() && + annotation.to_boolean()) { + assert(target.is_array()); + std::ostringstream message; + message << "Every item of the array value validated against the given " + "positional subschemas"; + return message.str(); + } + + if (keyword == "title" || keyword == "description") { + assert(annotation.is_string()); + std::ostringstream message; + message << "The " << keyword << " of the"; + if (instance_location.empty()) { + message << " instance"; + } else { + message << " instance location \""; + stringify(instance_location, message); + message << "\""; + } + + message << " was " << escape_string(annotation.to_string()); + return message.str(); + } + + if (keyword == "default") { + std::ostringstream message; + message << "The default value of the"; + if (instance_location.empty()) { + message << " instance"; + } else { + message << " instance location \""; + stringify(instance_location, message); + message << "\""; + } + + message << " was "; + stringify(annotation, message); + return message.str(); + } + + if (keyword == "deprecated" && annotation.is_boolean()) { + std::ostringstream message; + if (instance_location.empty()) { + message << "The instance"; + } else { + message << "The instance location \""; + stringify(instance_location, message); + message << "\""; + } + + if (annotation.to_boolean()) { + message << " was considered deprecated"; + } else { + message << " was not considered deprecated"; + } + + return message.str(); + } + + if (keyword == "readOnly" && annotation.is_boolean()) { + std::ostringstream message; + if (instance_location.empty()) { + message << "The instance"; + } else { + message << "The instance location \""; + stringify(instance_location, message); + message << "\""; + } + + if (annotation.to_boolean()) { + message << " was considered read-only"; + } else { + message << " was not considered read-only"; + } + + return message.str(); + } + + if (keyword == "writeOnly" && annotation.is_boolean()) { + std::ostringstream message; + if (instance_location.empty()) { + message << "The instance"; + } else { + message << "The instance location \""; + stringify(instance_location, message); + message << "\""; + } + + if (annotation.to_boolean()) { + message << " was considered write-only"; + } else { + message << " was not considered write-only"; + } + + return message.str(); + } + + if (keyword == "examples") { + assert(annotation.is_array()); + std::ostringstream message; + if (instance_location.empty()) { + message << "Examples of the instance"; + } else { + message << "Examples of the instance location \""; + stringify(instance_location, message); + message << "\""; + } + + message << " were "; + for (auto iterator = annotation.as_array().cbegin(); + iterator != annotation.as_array().cend(); ++iterator) { + if (std::next(iterator) == annotation.as_array().cend()) { + message << "and "; + describe_stringify(*iterator, message); + } else { + describe_stringify(*iterator, message); + message << ", "; + } + } + + return message.str(); + } + + if (keyword == "contentEncoding") { + assert(annotation.is_string()); + std::ostringstream message; + message << "The content encoding of the"; + if (instance_location.empty()) { + message << " instance"; + } else { + message << " instance location \""; + stringify(instance_location, message); + message << "\""; + } + + message << " was " << escape_string(annotation.to_string()); + return message.str(); + } + + if (keyword == "contentMediaType") { + assert(annotation.is_string()); + std::ostringstream message; + message << "The content media type of the"; + if (instance_location.empty()) { + message << " instance"; + } else { + message << " instance location \""; + stringify(instance_location, message); + message << "\""; + } + + message << " was " << escape_string(annotation.to_string()); + return message.str(); + } + + if (keyword == "contentSchema") { + std::ostringstream message; + message << "When decoded, the"; + if (instance_location.empty()) { + message << " instance"; + } else { + message << " instance location \""; + stringify(instance_location, message); + message << "\""; + } + + message << " was expected to validate against the schema "; + describe_stringify(annotation, message); + return message.str(); + } + + if (keyword == "format") { + std::ostringstream message; + message << "The logical type of the"; + if (instance_location.empty()) { + message << " instance"; + } else { + message << " instance location \""; + stringify(instance_location, message); + message << "\""; + } + + message << " was expected to be "; + describe_stringify(annotation, message); + return message.str(); + } + + std::ostringstream message; + message << "The unrecognized keyword " << escape_string(keyword) + << " was collected as the annotation "; + describe_stringify(annotation, message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AnnotationToParent) { + if (keyword == "unevaluatedItems" && annotation.is_boolean() && + annotation.to_boolean()) { + assert(target.is_array()); + std::ostringstream message; + message << "At least one item of the array value successfully validated " + "against the subschema for unevaluated items"; + return message.str(); + } + + return unknown(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AnnotationBasenameToParent) { + if (keyword == "patternProperties") { + assert(annotation.is_string()); + std::ostringstream message; + message << "The object property " << escape_string(annotation.to_string()) + << " successfully validated against its pattern property " + "subschema"; + return message.str(); + } + + if (keyword == "additionalProperties") { + assert(annotation.is_string()); + std::ostringstream message; + message << "The object property " << escape_string(annotation.to_string()) + << " successfully validated against the additional properties " + "subschema"; + return message.str(); + } + + if (keyword == "unevaluatedProperties") { + assert(annotation.is_string()); + std::ostringstream message; + message << "The object property " << escape_string(annotation.to_string()) + << " successfully validated against the subschema for " + "unevaluated properties"; + return message.str(); + } + + if (keyword == "contains" && annotation.is_integer()) { + assert(target.is_array()); + assert(annotation.is_positive()); + std::ostringstream message; + message << "The item at index " << annotation.to_integer() + << " of the array value successfully validated against the " + "containment check subschema"; + return message.str(); + } + + return unknown(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopProperties) { + assert(keyword == "additionalProperties" || + keyword == "unevaluatedProperties"); + std::ostringstream message; + if (!step.children.empty() && + step.children.front().type == InstructionIndex::AssertionFail) { + if (keyword == "unevaluatedProperties") { + message << "The object value was not expected to define unevaluated " + "properties"; + } else { + message << "The object value was not expected to define additional " + "properties"; + } + } else if (keyword == "unevaluatedProperties") { + message << "The object properties not covered by other object " + "keywords were expected to validate against this subschema"; + } else { + message << "The object properties not covered by other adjacent object " + "keywords were expected to validate against this subschema"; + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesEvaluate) { + assert(keyword == "additionalProperties"); + std::ostringstream message; + if (step.children.size() == 1 && + step.children.front().type == InstructionIndex::AssertionFail) { + message << "The object value was not expected to define additional " + "properties"; + } else { + message << "The object properties not covered by other adjacent object " + "keywords were expected to validate against this subschema"; + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesUnevaluated) { + if (keyword == "unevaluatedProperties") { + std::ostringstream message; + if (!step.children.empty() && + step.children.front().type == InstructionIndex::AssertionFail) { + message << "The object value was not expected to define unevaluated " + "properties"; + } else { + message << "The object properties not covered by other object " + "keywords were expected to validate against this subschema"; + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesUnevaluatedExcept) { + if (keyword == "unevaluatedProperties") { + std::ostringstream message; + if (!step.children.empty() && + step.children.front().type == InstructionIndex::AssertionFail) { + message << "The object value was not expected to define unevaluated " + "properties"; + } else { + message << "The object properties not covered by other object " + "keywords were expected to validate against this subschema"; + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopPropertiesExcept) { + assert(keyword == "additionalProperties" || + keyword == "unevaluatedProperties"); + std::ostringstream message; + if (!step.children.empty() && + step.children.front().type == InstructionIndex::AssertionFail) { + if (keyword == "unevaluatedProperties") { + message << "The object value was not expected to define unevaluated " + "properties"; + } else { + message << "The object value was not expected to define additional " + "properties"; + } + } else if (keyword == "unevaluatedProperties") { + message << "The object properties not covered by other object " + "keywords were expected to validate against this subschema"; + } else { + message << "The object properties not covered by other adjacent object " + "keywords were expected to validate against this subschema"; + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesExactlyTypeStrict) { + std::ostringstream message; + message << "The required object properties were expected to be of type " + << type_name(instruction_value(step).first); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex:: + LoopPropertiesExactlyTypeStrictHash) { + std::ostringstream message; + message << "The required object properties were expected to be of type " + << type_name(instruction_value(step).first); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex:: + LoopItemsPropertiesExactlyTypeStrictHash || + step.type == sourcemeta::blaze::InstructionIndex:: + LoopItemsPropertiesExactlyTypeStrictHash3) { + std::ostringstream message; + message << "Every item in the array was expected to be an object whose " + "required properties were of type " + << type_name(instruction_value(step).first); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBounded) { + return "Every item in the array was expected to be a number within the " + "given range"; + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopItemsIntegerBoundedSized) { + return "Every item in the array was expected to be a number within the " + "given range"; + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeIntegerBounded || + step.type == sourcemeta::blaze::InstructionIndex:: + AssertionTypeIntegerBoundedStrict) { + return "The value was expected to be an integer within the given range"; + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeIntegerLowerBound || + step.type == sourcemeta::blaze::InstructionIndex:: + AssertionTypeIntegerLowerBoundStrict) { + return "The value was expected to be an integer above the given minimum"; + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionObjectPropertiesSimple) { + return "The object value was expected to validate against the defined " + "property subschemas"; + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopPropertiesType) { + std::ostringstream message; + message << "The object properties were expected to be of type " + << type_name(instruction_value(step)); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesTypeEvaluate) { + std::ostringstream message; + message << "The object properties were expected to be of type " + << type_name(instruction_value(step)); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesTypeStrict) { + std::ostringstream message; + message << "The object properties were expected to be of type " + << type_name(instruction_value(step)); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesTypeStrictEvaluate) { + std::ostringstream message; + message << "The object properties were expected to be of type " + << type_name(instruction_value(step)); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesTypeStrictAny) { + std::ostringstream message; + message << "The object properties were expected to be of type "; + ValueTypes types{instruction_value(step)}; + + const auto has_real{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Real))}; + const auto has_integer{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Integer))}; + const auto has_decimal{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Decimal))}; + + if (has_real && has_integer) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + } + if (has_real && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + if (has_integer && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + + const auto popcount{types.count()}; + + if (popcount == 1) { + std::uint8_t type_index{0}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + type_index = bit; + break; + } + } + message << type_name( + static_cast(type_index)); + } else { + bool first{true}; + std::uint8_t last_bit{255}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + last_bit = bit; + } + } + + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + if (!first) { + message << ", "; + } + if (bit == last_bit) { + message << "or "; + } + message << type_name(static_cast(bit)); + first = false; + } + } + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex:: + LoopPropertiesTypeStrictAnyEvaluate) { + std::ostringstream message; + message << "The object properties were expected to be of type "; + ValueTypes types{instruction_value(step)}; + + const auto has_real{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Real))}; + const auto has_integer{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Integer))}; + const auto has_decimal{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Decimal))}; + + if (has_real && has_integer) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + } + if (has_real && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + if (has_integer && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + + const auto popcount{types.count()}; + + if (popcount == 1) { + std::uint8_t type_index{0}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + type_index = bit; + break; + } + } + message << type_name( + static_cast(type_index)); + } else { + bool first{true}; + std::uint8_t last_bit{255}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + last_bit = bit; + } + } + + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + if (!first) { + message << ", "; + } + if (bit == last_bit) { + message << "or "; + } + message << type_name(static_cast(bit)); + first = false; + } + } + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopKeys) { + assert(keyword == "propertyNames"); + assert(target.is_object()); + std::ostringstream message; + + if (target.size() == 0) { + assert(valid); + message << "The object is empty and no properties were expected to " + "validate against the given subschema"; + } else if (target.size() == 1) { + message << "The object property "; + message << escape_string(target.as_object().cbegin()->first); + message << " was expected to validate against the given subschema"; + } else { + message << "The object properties "; + for (auto iterator = target.as_object().cbegin(); + iterator != target.as_object().cend(); ++iterator) { + if (std::next(iterator) == target.as_object().cend()) { + message << "and " << escape_string(iterator->first); + } else { + message << escape_string(iterator->first) << ", "; + } + } + + message << " were expected to validate against the given subschema"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopItems) { + assert(target.is_array()); + return "Every item in the array value was expected to validate against the " + "given subschema"; + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopItemsFrom) { + assert(target.is_array()); + const auto &value{instruction_value(step)}; + std::ostringstream message; + message << "Every item in the array value"; + if (value == 1) { + message << " except for the first one"; + } else if (value > 0) { + message << " except for the first " << value; + } + + message << " was expected to validate against the given subschema"; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopItemsUnevaluated) { + assert(keyword == "unevaluatedItems"); + std::ostringstream message; + message << "The array items not covered by other array keywords, if any, " + "were expected to validate against this subschema"; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopItemsType) { + std::ostringstream message; + message << "The array items were expected to be of type " + << type_name(instruction_value(step)); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopItemsTypeStrict) { + std::ostringstream message; + message << "The array items were expected to be of type " + << type_name(instruction_value(step)); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopItemsTypeStrictAny) { + std::ostringstream message; + message << "The array items were expected to be of type "; + ValueTypes types{instruction_value(step)}; + + const auto has_real{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Real))}; + const auto has_integer{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Integer))}; + const auto has_decimal{ + types.test(std::to_underlying(sourcemeta::core::JSON::Type::Decimal))}; + + if (has_real && has_integer) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Integer)); + } + if (has_real && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + if (has_integer && has_decimal) { + types.reset(std::to_underlying(sourcemeta::core::JSON::Type::Decimal)); + } + + const auto popcount{types.count()}; + + if (popcount == 1) { + std::uint8_t type_index{0}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + type_index = bit; + break; + } + } + message << type_name( + static_cast(type_index)); + } else { + bool first{true}; + std::uint8_t last_bit{255}; + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + last_bit = bit; + } + } + + for (std::uint8_t bit{0}; bit < 8; bit++) { + if (types.test(bit)) { + if (!first) { + message << ", "; + } + if (bit == last_bit) { + message << "or "; + } + message << type_name(static_cast(bit)); + first = false; + } + } + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopContains) { + assert(target.is_array()); + std::ostringstream message; + const auto &value{instruction_value(step)}; + const auto &[minimum, maximum, exhaustive] = value; + bool plural{true}; + + message << "The array value was expected to contain "; + if (maximum.has_value()) { + if (minimum == maximum.value() && minimum == 0) { + message << "any number of"; + } else if (minimum == maximum.value()) { + message << "exactly " << minimum; + if (minimum == 1) { + plural = false; + } + } else if (minimum == 0) { + message << "up to " << maximum.value(); + if (maximum.value() == 1) { + plural = false; + } + } else { + message << minimum << " to " << maximum.value(); + if (maximum.value() == 1) { + plural = false; + } + } + } else { + message << "at least " << minimum; + if (minimum == 1) { + plural = false; + } + } + + if (plural) { + message << " items that validate against the given subschema"; + } else { + message << " item that validates against the given subschema"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionDefines) { + std::ostringstream message; + message << "The object value was expected to define the property " + << escape_string(instruction_value(step).first); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionDefinesStrict) { + std::ostringstream message; + message + << "The value was expected to be an object that defines the property " + << escape_string(instruction_value(step).first); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionDefinesAll) { + const auto &value{instruction_value(step)}; + assert(value.size() > 1); + + std::vector value_vector; + for (const auto &entry : value) { + value_vector.push_back(entry.first); + } + + std::ostringstream message; + message << "The object value was expected to define properties "; + for (auto iterator = value_vector.cbegin(); iterator != value_vector.cend(); + ++iterator) { + if (std::next(iterator) == value_vector.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + + if (valid) { + return message.str(); + } + + assert(target.is_object()); + std::set missing; + for (const auto &property : value) { + if (!target.defines(property.first, property.second)) { + missing.insert(property.first); + } + } + + assert(!missing.empty()); + if (missing.size() == 1) { + message << " but did not define the property " + << escape_string(*(missing.cbegin())); + } else { + message << " but did not define properties "; + for (auto iterator = missing.cbegin(); iterator != missing.cend(); + ++iterator) { + if (std::next(iterator) == missing.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionDefinesAllStrict) { + const auto &value{instruction_value(step)}; + assert(value.size() > 1); + + std::vector value_vector; + for (const auto &entry : value) { + value_vector.push_back(entry.first); + } + + std::ostringstream message; + message + << "The value was expected to be an object that defines properties "; + for (auto iterator = value_vector.cbegin(); iterator != value_vector.cend(); + ++iterator) { + if (std::next(iterator) == value_vector.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionDefinesExactly) { + const auto &value{instruction_value(step)}; + assert(value.size() > 1); + std::vector value_vector; + for (const auto &entry : value) { + value_vector.push_back(entry.first); + } + + std::ranges::sort(value_vector); + std::ostringstream message; + message << "The object value was expected to only define properties "; + for (auto iterator = value_vector.cbegin(); iterator != value_vector.cend(); + ++iterator) { + if (std::next(iterator) == value_vector.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionDefinesExactlyStrict) { + const auto &value{instruction_value(step)}; + assert(value.size() > 1); + std::vector value_vector; + for (const auto &entry : value) { + value_vector.push_back(entry.first); + } + + std::ranges::sort(value_vector); + std::ostringstream message; + message << "The value was expected to be an object that only defines " + "properties "; + for (auto iterator = value_vector.cbegin(); iterator != value_vector.cend(); + ++iterator) { + if (std::next(iterator) == value_vector.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionDefinesExactlyStrictHash3) { + const auto &value{instruction_value(step).first}; + std::ostringstream message; + message << "The value was expected to be an object that only defines " + "the " + << value.size() << " given properties"; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionType || + step.type == sourcemeta::blaze::InstructionIndex::AssertionTypeStrict) { + if (std::ranges::any_of(evaluate_path, + [](const auto &token) { + return token.is_property() && + token.to_property() == "propertyNames"; + }) && + !instance_location.empty() && instance_location.back().is_property()) { + std::ostringstream message; + message << "The property name " + << escape_string(instance_location.back().to_property()) + << " was expected to be of type " + << type_name(instruction_value(step)); + return message.str(); + } + + std::ostringstream message; + describe_type_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionTypeAny) { + std::ostringstream message; + describe_types_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny) { + std::ostringstream message; + describe_types_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeStringBounded) { + std::ostringstream message; + + const auto &[minimum, maximum, exhaustive] = + instruction_value(step); + if (minimum == 0 && maximum.has_value()) { + message << "The value was expected to consist of a string of at most " + << maximum.value() + << (maximum.value() == 1 ? " character" : " characters"); + } else if (maximum.has_value()) { + message << "The value was expected to consist of a string of " << minimum + << " to " << maximum.value() + << (maximum.value() == 1 ? " character" : " characters"); + } else { + message << "The value was expected to consist of a string of at least " + << minimum << (minimum == 1 ? " character" : " characters"); + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeStringUpper) { + std::ostringstream message; + message << "The value was expected to consist of a string of at most " + << instruction_value(step) + << (instruction_value(step) == 1 + ? " character" + : " characters"); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeArrayBounded) { + std::ostringstream message; + + const auto &[minimum, maximum, exhaustive] = + instruction_value(step); + if (minimum == 0 && maximum.has_value()) { + message << "The value was expected to consist of an array of at most " + << maximum.value() << (maximum.value() == 1 ? " item" : " items"); + } else if (maximum.has_value()) { + message << "The value was expected to consist of an array of " << minimum + << " to " << maximum.value() + << (maximum.value() == 1 ? " item" : " items"); + } else { + message << "The value was expected to consist of an array of at least " + << minimum << (minimum == 1 ? " item" : " items"); + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeArrayUpper) { + std::ostringstream message; + message << "The value was expected to consist of an array of at most " + << instruction_value(step) + << (instruction_value(step) == 1 ? " item" + : " items"); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeObjectBounded) { + std::ostringstream message; + + const auto &[minimum, maximum, exhaustive] = + instruction_value(step); + if (minimum == 0 && maximum.has_value()) { + message << "The value was expected to consist of an object of at most " + << maximum.value() + << (maximum.value() == 1 ? " property" : " properties"); + } else if (maximum.has_value()) { + message << "The value was expected to consist of an object of " << minimum + << " to " << maximum.value() + << (maximum.value() == 1 ? " property" : " properties"); + } else { + message << "The value was expected to consist of an object of at least " + << minimum << (minimum == 1 ? " property" : " properties"); + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionTypeObjectUpper) { + std::ostringstream message; + message << "The value was expected to consist of an object of at most " + << instruction_value(step) + << (instruction_value(step) == 1 + ? " property" + : " properties"); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionRegex) { + if (std::ranges::any_of(evaluate_path, + [](const auto &token) { + return token.is_property() && + token.to_property() == "propertyNames"; + }) && + !instance_location.empty() && instance_location.back().is_property()) { + std::ostringstream message; + message << "The property name " + << escape_string(instance_location.back().to_property()) + << " was expected to match the regular expression " + << escape_string(instruction_value(step).second); + return message.str(); + } + + assert(target.is_string()); + std::ostringstream message; + message << "The string value " << escape_string(target.to_string()) + << " was expected to match the regular expression " + << escape_string(instruction_value(step).second); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionStringSizeLess) { + if (keyword == "maxLength") { + std::ostringstream message; + const auto maximum{instruction_value(step) - 1}; + + if (is_within_keyword(evaluate_path, "propertyNames")) { + assert(instance_location.back().is_property()); + message << "The object property name " + << escape_string(instance_location.back().to_property()); + } else { + message << "The string value "; + describe_stringify(target, message); + } + + message << " was expected to consist of at most " << maximum + << (maximum == 1 ? " character" : " characters"); + + if (valid) { + message << " and"; + } else { + message << " but"; + } + + message << " it consisted of "; + + if (is_within_keyword(evaluate_path, "propertyNames")) { + message << instance_location.back().to_property().size(); + message << (instance_location.back().to_property().size() == 1 + ? " character" + : " characters"); + } else { + message << target.size(); + message << (target.size() == 1 ? " character" : " characters"); + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionStringSizeGreater) { + if (keyword == "minLength") { + std::ostringstream message; + const auto minimum{instruction_value(step) + 1}; + + if (is_within_keyword(evaluate_path, "propertyNames")) { + assert(instance_location.back().is_property()); + message << "The object property name " + << escape_string(instance_location.back().to_property()); + } else { + message << "The string value "; + describe_stringify(target, message); + } + + message << " was expected to consist of at least " << minimum + << (minimum == 1 ? " character" : " characters"); + + if (valid) { + message << " and"; + } else { + message << " but"; + } + + message << " it consisted of "; + + if (is_within_keyword(evaluate_path, "propertyNames")) { + message << instance_location.back().to_property().size(); + message << (instance_location.back().to_property().size() == 1 + ? " character" + : " characters"); + } else { + message << target.size(); + message << (target.size() == 1 ? " character" : " characters"); + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionArraySizeLess) { + if (keyword == "maxItems") { + assert(target.is_array()); + std::ostringstream message; + const auto maximum{instruction_value(step) - 1}; + message << "The array value was expected to contain at most " << maximum; + if (maximum == 1) { + message << " item"; + } else { + message << " items"; + } + + if (valid) { + message << " and"; + } else { + message << " but"; + } + + message << " it contained " << target.size(); + if (target.size() == 1) { + message << " item"; + } else { + message << " items"; + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionArraySizeGreater) { + assert(target.is_array()); + std::ostringstream message; + const auto minimum{instruction_value(step) + 1}; + message << "The array value was expected to contain at least " << minimum; + if (minimum == 1) { + message << " item"; + } else { + message << " items"; + } + + if (valid) { + message << " and"; + } else { + message << " but"; + } + + message << " it contained " << target.size(); + if (target.size() == 1) { + message << " item"; + } else { + message << " items"; + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionObjectSizeLess) { + if (keyword == "additionalProperties") { + return "The object value was not expected to define additional " + "properties"; + } + + if (keyword == "maxProperties") { + assert(target.is_object()); + std::ostringstream message; + const auto maximum{instruction_value(step) - 1}; + message << "The object value was expected to contain at most " << maximum; + if (maximum == 1) { + message << " property"; + } else { + message << " properties"; + } + + if (valid) { + message << " and"; + } else { + message << " but"; + } + + message << " it contained " << target.size(); + if (target.size() == 0) { + message << " properties"; + } else if (target.size() == 1) { + message << " property: "; + message << escape_string(target.as_object().cbegin()->first); + } else { + message << " properties: "; + + std::vector properties; + for (const auto &entry : target.as_object()) { + properties.push_back(entry.first); + } + std::ranges::sort(properties); + + for (auto iterator = properties.cbegin(); iterator != properties.cend(); + ++iterator) { + if (std::next(iterator) == properties.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionObjectSizeGreater) { + if (keyword == "minProperties") { + assert(target.is_object()); + std::ostringstream message; + const auto minimum{instruction_value(step) + 1}; + message << "The object value was expected to contain at least " + << minimum; + if (minimum == 1) { + message << " property"; + } else { + message << " properties"; + } + + if (valid) { + message << " and"; + } else { + message << " but"; + } + + message << " it contained " << target.size(); + if (target.size() == 1) { + message << " property: "; + message << escape_string(target.as_object().cbegin()->first); + } else { + message << " properties: "; + std::vector properties; + for (const auto &entry : target.as_object()) { + properties.push_back(entry.first); + } + std::ranges::sort(properties); + + for (auto iterator = properties.cbegin(); iterator != properties.cend(); + ++iterator) { + if (std::next(iterator) == properties.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionEqual) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + + if (std::ranges::any_of(evaluate_path, + [](const auto &token) { + return token.is_property() && + token.to_property() == "propertyNames"; + }) && + !instance_location.empty() && instance_location.back().is_property()) { + message << "The property name " + << escape_string(instance_location.back().to_property()); + } else { + message << "The " << type_name(target.type()) << " value "; + describe_stringify(target, message); + } + + message << " was expected to equal the " << type_name(value.type()) + << " constant "; + describe_stringify(value, message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionGreaterEqual) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + message << "The " << value_type_name(target) << " value "; + describe_stringify(target, message); + message << " was expected to be greater than or equal to the " + << value_type_name(value) << " "; + describe_stringify(value, message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionLessEqual) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + message << "The " << value_type_name(target) << " value "; + describe_stringify(target, message); + message << " was expected to be less than or equal to the " + << value_type_name(value) << " "; + describe_stringify(value, message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionGreater) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + message << "The " << value_type_name(target) << " value "; + describe_stringify(target, message); + message << " was expected to be greater than the " << value_type_name(value) + << " "; + describe_stringify(value, message); + if (!valid && value == target) { + message << ", but they were equal"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionLess) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + message << "The " << value_type_name(target) << " value "; + describe_stringify(target, message); + message << " was expected to be less than the " << value_type_name(value) + << " "; + describe_stringify(value, message); + if (!valid && value == target) { + message << ", but they were equal"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionUnique) { + assert(target.is_array()); + auto array{target.as_array()}; + std::ostringstream message; + if (valid) { + message << "The array value was expected to not contain duplicate items"; + } else { + std::set duplicates; + for (auto iterator = array.cbegin(); iterator != array.cend(); + ++iterator) { + for (auto subiterator = std::next(iterator); + subiterator != array.cend(); ++subiterator) { + if (*iterator == *subiterator) { + duplicates.insert(*iterator); + } + } + } + + assert(!duplicates.empty()); + message << "The array value contained the following duplicate"; + if (duplicates.size() == 1) { + message << " item: "; + describe_stringify(*(duplicates.cbegin()), message); + } else { + message << " items: "; + for (auto subiterator = duplicates.cbegin(); + subiterator != duplicates.cend(); ++subiterator) { + if (std::next(subiterator) == duplicates.cend()) { + message << "and "; + describe_stringify(*subiterator, message); + } else { + describe_stringify(*subiterator, message); + message << ", "; + } + } + } + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionDivisible) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + message << "The " << value_type_name(target) << " value "; + describe_stringify(target, message); + message << " was expected to be divisible by the " << value_type_name(value) + << " "; + describe_stringify(value, message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionEqualsAny) { + std::ostringstream message; + const auto &value{instruction_value(step)}; + assert(!value.empty()); + + if (std::ranges::any_of(evaluate_path, + [](const auto &token) { + return token.is_property() && + token.to_property() == "propertyNames"; + }) && + !instance_location.empty() && instance_location.back().is_property()) { + message << "The property name " + << escape_string(instance_location.back().to_property()); + } else { + message << "The " << type_name(target.type()) << " value "; + describe_stringify(target, message); + } + + if (value.size() == 1) { + message << " was expected to equal the " + << type_name(value.cbegin()->type()) << " constant "; + describe_stringify(*(value.cbegin()), message); + } else { + if (valid) { + message << " was expected to equal one of the " << value.size() + << " declared values"; + } else { + message << " was expected to equal one of the following values: "; + std::vector copy{value.cbegin(), value.cend()}; + std::ranges::sort(copy); + for (auto iterator = copy.cbegin(); iterator != copy.cend(); + ++iterator) { + if (std::next(iterator) == copy.cend()) { + message << "and "; + describe_stringify(*iterator, message); + } else { + describe_stringify(*iterator, message); + message << ", "; + } + } + } + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionEqualsAnyStringHash) { + std::ostringstream message; + const auto &value{instruction_value(step).first}; + assert(!value.empty()); + + if (std::ranges::any_of(evaluate_path, + [](const auto &token) { + return token.is_property() && + token.to_property() == "propertyNames"; + }) && + !instance_location.empty() && instance_location.back().is_property()) { + message << "The property name " + << escape_string(instance_location.back().to_property()); + } else { + message << "The " << type_name(target.type()) << " value "; + describe_stringify(target, message); + } + + if (value.size() == 1) { + message << " was expected to equal the given constant"; + } else { + message << " was expected to equal one of the given declared values"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionStringType) { + assert(target.is_string()); + std::ostringstream message; + message << "The string value " << escape_string(target.to_string()) + << " was expected to represent a valid"; + switch (instruction_value(step)) { + case ValueStringType::URI: + message << " URI"; + break; + default: + return unknown(); + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionPropertyType) { + std::ostringstream message; + describe_type_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionPropertyTypeEvaluate) { + std::ostringstream message; + describe_type_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionPropertyTypeStrict) { + std::ostringstream message; + describe_type_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex:: + AssertionPropertyTypeStrictEvaluate) { + std::ostringstream message; + describe_type_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionPropertyTypeStrictAny) { + std::ostringstream message; + describe_types_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex:: + AssertionPropertyTypeStrictAnyEvaluate) { + std::ostringstream message; + describe_types_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionArrayPrefix) { + assert(keyword == "items" || keyword == "prefixItems"); + assert(!step.children.empty()); + assert(target.is_array()); + + std::ostringstream message; + message << "The first "; + if (step.children.size() <= 2) { + message << "item of the array value was"; + } else { + message << (step.children.size() - 1) << " items of the array value were"; + } + + message << " expected to validate against the corresponding subschemas"; + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionArrayPrefixEvaluate) { + assert(keyword == "items" || keyword == "prefixItems"); + assert(!step.children.empty()); + assert(target.is_array()); + + std::ostringstream message; + message << "The first "; + if (step.children.size() <= 2) { + message << "item of the array value was"; + } else { + message << (step.children.size() - 1) << " items of the array value were"; + } + + message << " expected to validate against the corresponding subschemas"; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopPropertiesMatch) { + assert(!step.children.empty()); + assert(target.is_object()); + std::ostringstream message; + message << "The object value was expected to validate against the "; + if (step.children.size() == 1) { + message << "single defined property subschema"; + } else { + message << step.children.size() << " defined properties subschemas"; + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesMatchClosed) { + assert(!step.children.empty()); + assert(target.is_object()); + std::ostringstream message; + if (step.children.size() == 1) { + message << "The object value was expected to validate against the "; + message << "single defined property subschema"; + } else { + message + << "Every object value was expected to validate against one of the "; + message << step.children.size() << " defined properties subschemas"; + } + + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalWhenDefines) { + std::ostringstream message; + message << "The object value defined the property \"" + << instruction_value(step).first << "\""; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LoopPropertiesRegex) { + assert(target.is_object()); + std::ostringstream message; + message << "The object properties that match the regular expression \"" + << instruction_value(step).second + << "\" were expected to validate against the defined pattern " + "property subschema"; + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesRegexClosed) { + assert(target.is_object()); + std::ostringstream message; + message << "The object properties were expected to match the regular " + "expression \"" + << instruction_value(step).second + << "\" and validate against the defined pattern " + "property subschema"; + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LoopPropertiesStartsWith) { + assert(target.is_object()); + std::ostringstream message; + message << "The object properties that start with the string \"" + << instruction_value(step) + << "\" were expected to validate against the defined pattern " + "property subschema"; + return message.str(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::LogicalWhenType) { + if (keyword == "items") { + std::ostringstream message; + describe_type_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + + if (keyword == "properties") { + assert(!step.children.empty()); + if (!target.is_object()) { + std::ostringstream message; + describe_type_check(valid, target.type(), + sourcemeta::core::JSON::Type::Object, message); + return message.str(); + } + + std::ostringstream message; + message << "The object value was expected to validate against the "; + if (step.children.size() == 1) { + message << "single defined property subschema"; + } else { + // We cannot provide the specific number of properties, + // as the number of children might be flatten out + // for performance reasons + message << "defined properties subschemas"; + } + + return message.str(); + } + + if (keyword == "dependencies") { + assert(target.is_object()); + assert(!step.children.empty()); + + std::set present; + std::set present_with_schemas; + std::set present_with_properties; + std::set all_dependencies; + std::set required_properties; + + for (const auto &child : step.children) { + // Schema + if (child.type == InstructionIndex::LogicalWhenDefines) { + const auto &substep{child}; + const auto &property{instruction_value(substep).first}; + all_dependencies.insert(property); + if (!target.defines(property)) { + continue; + } + + present.insert(property); + present_with_schemas.insert(property); + + // Properties + } else { + assert(child.type == InstructionIndex::AssertionPropertyDependencies); + const auto &substep{child}; + + for (const auto &entry : std::get(substep.value)) { + all_dependencies.insert(entry.first); + if (target.defines(entry.first)) { + present.insert(entry.first); + present_with_properties.insert(entry.first); + for (const auto &dependency : entry.second) { + if (valid || !target.defines(dependency)) { + required_properties.insert(dependency); + } + } + } + } + } + } + + std::ostringstream message; + + if (present_with_schemas.empty() && present_with_properties.empty()) { + message << "The object value did not define the"; + assert(!all_dependencies.empty()); + if (all_dependencies.size() == 1) { + message << " property " + << escape_string(*(all_dependencies.cbegin())); + } else { + message << " properties "; + for (auto iterator = all_dependencies.cbegin(); + iterator != all_dependencies.cend(); ++iterator) { + if (std::next(iterator) == all_dependencies.cend()) { + message << "or " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + return message.str(); + } + + if (present.size() == 1) { + message << "Because the object value defined the"; + message << " property " << escape_string(*(present.cbegin())); + } else { + message << "Because the object value defined the"; + message << " properties "; + for (auto iterator = present.cbegin(); iterator != present.cend(); + ++iterator) { + if (std::next(iterator) == present.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + if (!required_properties.empty()) { + message << ", it was also expected to define the"; + if (required_properties.size() == 1) { + message << " property " + << escape_string(*(required_properties.cbegin())); + } else { + message << " properties "; + for (auto iterator = required_properties.cbegin(); + iterator != required_properties.cend(); ++iterator) { + if (std::next(iterator) == required_properties.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + } + + if (!present_with_schemas.empty()) { + message << ", "; + if (!required_properties.empty()) { + message << "and "; + } + + message << "it was also expected to successfully validate against the " + "corresponding "; + if (present_with_schemas.size() == 1) { + message << escape_string(*(present_with_schemas.cbegin())); + message << " subschema"; + } else { + for (auto iterator = present_with_schemas.cbegin(); + iterator != present_with_schemas.cend(); ++iterator) { + if (std::next(iterator) == present_with_schemas.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + + message << " subschemas"; + } + } + + return message.str(); + } + + if (keyword == "dependentSchemas") { + assert(target.is_object()); + assert(!step.children.empty()); + std::set present; + std::set all_dependencies; + for (const auto &child : step.children) { + assert(child.type == InstructionIndex::LogicalWhenDefines); + const auto &substep{child}; + const auto &property{instruction_value(substep).first}; + all_dependencies.insert(property); + if (!target.defines(property)) { + continue; + } + + present.insert(property); + } + + std::ostringstream message; + + if (present.empty()) { + message << "The object value did not define the"; + assert(!all_dependencies.empty()); + if (all_dependencies.size() == 1) { + message << " property " + << escape_string(*(all_dependencies.cbegin())); + } else { + message << " properties "; + for (auto iterator = all_dependencies.cbegin(); + iterator != all_dependencies.cend(); ++iterator) { + if (std::next(iterator) == all_dependencies.cend()) { + message << "or " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + } else if (present.size() == 1) { + message << "Because the object value defined the"; + message << " property " << escape_string(*(present.cbegin())); + message + << ", it was also expected to validate against the corresponding " + "subschema"; + } else { + message << "Because the object value defined the"; + message << " properties "; + for (auto iterator = present.cbegin(); iterator != present.cend(); + ++iterator) { + if (std::next(iterator) == present.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + + message + << ", it was also expected to validate against the corresponding " + "subschemas"; + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionPropertyDependencies) { + assert(target.is_object()); + std::set present; + std::set all_dependencies; + std::set required; + + for (const auto &entry : instruction_value(step)) { + all_dependencies.insert(entry.first); + if (target.defines(entry.first)) { + present.insert(entry.first); + for (const auto &dependency : entry.second) { + if (valid || !target.defines(dependency)) { + required.insert(dependency); + } + } + } + } + + std::ostringstream message; + + if (present.empty()) { + message << "The object value did not define the"; + assert(!all_dependencies.empty()); + if (all_dependencies.size() == 1) { + message << " property " << escape_string(*(all_dependencies.cbegin())); + } else { + message << " properties "; + for (auto iterator = all_dependencies.cbegin(); + iterator != all_dependencies.cend(); ++iterator) { + if (std::next(iterator) == all_dependencies.cend()) { + message << "or " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + return message.str(); + } else if (present.size() == 1) { + message << "Because the object value defined the"; + message << " property " << escape_string(*(present.cbegin())); + } else { + message << "Because the object value defined the"; + message << " properties "; + for (auto iterator = present.cbegin(); iterator != present.cend(); + ++iterator) { + if (std::next(iterator) == present.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + assert(!required.empty()); + message << ", it was also expected to define the"; + if (required.size() == 1) { + message << " property " << escape_string(*(required.cbegin())); + } else { + message << " properties "; + for (auto iterator = required.cbegin(); iterator != required.cend(); + ++iterator) { + if (std::next(iterator) == required.cend()) { + message << "and " << escape_string(*iterator); + } else { + message << escape_string(*iterator) << ", "; + } + } + } + + return message.str(); + } + + if (step.type == + sourcemeta::blaze::InstructionIndex::LogicalWhenArraySizeGreater) { + if (keyword == "additionalItems" || keyword == "items") { + assert(target.is_array()); + std::ostringstream message; + + if (target.size() > instruction_value(step)) { + const auto rest{target.size() - + instruction_value(step)}; + message << "The array value contains " << rest << " additional" + << (rest == 1 ? " item" : " items") + << " not described by related keywords"; + } else { + message << "The array value does not contain additional items not " + "described by related keywords"; + } + + return message.str(); + } + + return unknown(); + } + + if (step.type == sourcemeta::blaze::InstructionIndex::ControlJump) { + if (std::ranges::any_of(evaluate_path, + [](const auto &token) { + return token.is_property() && + token.to_property() == "propertyNames"; + }) && + !instance_location.empty() && instance_location.back().is_property()) { + std::ostringstream message; + message << "The string value was expected to validate against the " + "referenced schema"; + return message.str(); + } + + return describe_reference(target); + } + + return unknown(); +} + +} // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/evaluator/evaluator_json.cc b/vendor/blaze/src/evaluator/evaluator_json.cc new file mode 100644 index 0000000..d0b4d2e --- /dev/null +++ b/vendor/blaze/src/evaluator/evaluator_json.cc @@ -0,0 +1,182 @@ +#include + +#include // assert +#include // std::unreachable + +namespace { +auto value_from_json(const sourcemeta::core::JSON &wrapper) + -> std::optional { + if (!wrapper.is_array() || wrapper.array_size() == 0 || + !wrapper.at(0).is_integer()) { + return std::nullopt; + } else if (wrapper.array_size() == 1) { + return sourcemeta::blaze::ValueNone{}; + } + + const auto &value{wrapper.at(1)}; + + using namespace sourcemeta::blaze; + switch (wrapper.at(0).to_integer()) { + // clang-format off + case 0: return ValueNone{}; + case 1: return sourcemeta::core::from_json(value); + case 2: return sourcemeta::core::from_json(value); + case 3: return sourcemeta::core::from_json(value); + case 4: return sourcemeta::core::from_json(value); + case 5: return sourcemeta::core::from_json(value); + case 6: return sourcemeta::core::from_json(value); + case 7: return sourcemeta::core::from_json(value); + case 8: return sourcemeta::core::from_json(value); + case 9: return sourcemeta::core::from_json(value); + case 10: return sourcemeta::core::from_json(value); + case 11: return sourcemeta::core::from_json(value); + case 12: return sourcemeta::core::from_json(value); + case 13: return sourcemeta::core::from_json(value); + case 14: return sourcemeta::core::from_json(value); + case 15: return sourcemeta::core::from_json(value); + case 16: return sourcemeta::core::from_json(value); + case 17: return sourcemeta::core::from_json(value); + case 18: return sourcemeta::core::from_json(value); + case 19: return sourcemeta::core::from_json(value); + case 20: return sourcemeta::core::from_json(value); + case 21: return sourcemeta::core::from_json(value); + case 22: return sourcemeta::core::from_json(value); + case 23: return sourcemeta::core::from_json(value); + case 24: return sourcemeta::core::from_json(value); + // clang-format on + default: + std::unreachable(); + } +} + +auto instructions_from_json( + const sourcemeta::core::JSON &instructions, + std::vector &extra) + -> std::optional { + if (!instructions.is_array()) { + return std::nullopt; + } + + sourcemeta::blaze::Instructions result; + result.reserve(instructions.size()); + for (const auto &instruction : instructions.as_array()) { + if (!instruction.is_array() || instruction.array_size() < 6) { + return std::nullopt; + } + + const auto &type{instruction.at(0)}; + const auto &relative_schema_location{instruction.at(1)}; + const auto &relative_instance_location{instruction.at(2)}; + const auto &keyword_location{instruction.at(3)}; + const auto &schema_resource{instruction.at(4)}; + const auto &value{instruction.at(5)}; + + auto type_result{ + sourcemeta::core::from_json(type)}; + auto relative_schema_location_result{ + sourcemeta::core::from_json( + relative_schema_location)}; + auto relative_instance_location_result{ + sourcemeta::core::from_json( + relative_instance_location)}; + auto keyword_location_result{ + sourcemeta::core::from_json(keyword_location)}; + auto schema_resource_result{ + sourcemeta::core::from_json(schema_resource)}; + auto value_result{value_from_json(value)}; + + // Parse children if there + std::optional children_result{ + instruction.array_size() == 7 + ? instructions_from_json(instruction.at(6), extra) + : sourcemeta::blaze::Instructions{}}; + + if (!type_result.has_value() || + !relative_schema_location_result.has_value() || + !relative_instance_location_result.has_value() || + !keyword_location_result.has_value() || + !schema_resource_result.has_value() || !value_result.has_value() || + !children_result.has_value()) { + return std::nullopt; + } + + const auto extra_index{extra.size()}; + extra.push_back( + {.relative_schema_location = + std::move(relative_schema_location_result).value(), + .keyword_location = std::move(keyword_location_result).value(), + .schema_resource = std::move(schema_resource_result).value()}); + + // TODO: Maybe we should emplace here? + result.push_back({std::move(type_result).value(), + std::move(relative_instance_location_result).value(), + std::move(value_result).value(), + std::move(children_result).value(), extra_index}); + } + + return result; +} + +} // namespace + +namespace sourcemeta::blaze { + +auto from_json(const sourcemeta::core::JSON &json) -> std::optional