From 8da6486acc346e3e21cdb71b0b38915f6900bd68 Mon Sep 17 00:00:00 2001 From: Juan Cruz Viotti Date: Thu, 7 May 2026 18:18:56 -0400 Subject: [PATCH] Upgrade Core and Blaze to the latest versions Signed-off-by: Juan Cruz Viotti --- DEPENDENCIES | 4 +- package-lock.json | 8 +- package.json | 2 +- vendor/blaze/CMakeLists.txt | 1 + vendor/blaze/DEPENDENCIES | 2 +- .../0001-draft3-ref-sibling.patch | 26 + vendor/blaze/ports/javascript/describe.mjs | 77 +- vendor/blaze/ports/javascript/index.mjs | 481 ++-- vendor/blaze/ports/javascript/opcodes.mjs | 174 +- vendor/blaze/src/alterschema/CMakeLists.txt | 3 + vendor/blaze/src/alterschema/alterschema.cc | 69 + .../canonicalizer/additional_items_implicit.h | 11 +- .../allof_merge_compatible_branches.h | 9 +- .../canonicalizer/dependencies_to_any_of.h | 10 +- .../dependencies_to_extends_disallow.h | 10 +- .../dependent_required_to_any_of.h | 10 +- .../dependent_schemas_to_any_of.h | 8 +- .../canonicalizer/deprecated_false_drop.h | 8 +- .../canonicalizer/divisible_by_implicit.h | 9 +- .../draft3_drop_extends_empty_schemas.h | 56 + .../canonicalizer/draft3_type_any.h | 53 + .../canonicalizer/empty_definitions_drop.h | 8 +- .../canonicalizer/empty_defs_drop.h | 7 +- .../canonicalizer/empty_dependencies_drop.h | 8 +- .../empty_dependent_required_drop.h | 8 +- .../empty_dependent_schemas_drop.h | 8 +- .../enum_drop_redundant_validation.h | 6 +- .../canonicalizer/enum_filter_by_type.h | 15 +- .../exclusive_bounds_false_drop.h | 22 +- .../exclusive_maximum_boolean_integer_fold.h | 17 +- .../exclusive_maximum_integer_to_maximum.h | 13 +- .../exclusive_minimum_boolean_integer_fold.h | 17 +- .../exclusive_minimum_integer_to_minimum.h | 13 +- .../canonicalizer/extends_to_array.h | 6 +- .../implicit_contains_keywords.h | 7 +- .../canonicalizer/implicit_object_keywords.h | 8 +- .../canonicalizer/inline_single_use_ref.h | 19 +- .../canonicalizer/items_implicit.h | 7 +- .../max_contains_covered_by_max_items.h | 12 +- .../canonicalizer/max_decimal_implicit.h | 9 +- .../maximum_can_equal_integer_fold.h | 17 +- .../maximum_can_equal_true_drop.h | 16 +- .../min_items_given_min_contains.h | 8 +- .../canonicalizer/min_length_implicit.h | 9 +- .../min_properties_covered_by_required.h | 26 +- .../minimum_can_equal_integer_fold.h | 17 +- .../minimum_can_equal_true_drop.h | 16 +- .../canonicalizer/multiple_of_implicit.h | 11 +- .../optional_property_implicit.h | 13 +- .../recursive_anchor_false_drop.h | 8 +- .../required_property_implicit.h | 13 +- .../canonicalizer/single_branch_allof.h | 9 +- .../canonicalizer/single_branch_anyof.h | 7 +- .../canonicalizer/single_branch_oneof.h | 7 +- .../canonicalizer/type_array_to_any_of.h | 6 +- .../canonicalizer/type_boolean_as_enum.h | 10 +- .../canonicalizer/type_inherit_in_place.h | 76 +- .../canonicalizer/type_null_as_enum.h | 9 +- .../canonicalizer/type_union_implicit.h | 16 +- .../canonicalizer/type_union_to_schemas.h | 8 +- .../type_with_applicator_to_allof.h | 4 +- .../type_with_applicator_to_extends.h | 15 +- .../unsatisfiable_can_equal_bounds.h | 31 +- .../unsatisfiable_exclusive_equal_bounds.h | 31 +- .../unsatisfiable_type_and_enum.h | 15 +- .../alterschema/common/allof_false_simplify.h | 11 +- .../alterschema/common/anyof_false_simplify.h | 9 +- .../src/alterschema/common/const_in_enum.h | 10 +- .../src/alterschema/common/const_with_type.h | 13 +- .../common/dependencies_property_tautology.h | 25 +- .../common/dependent_required_tautology.h | 23 +- .../common/double_negation_elimination.h | 14 +- .../draft_official_dialect_with_https.h | 7 +- ..._official_dialect_without_empty_fragment.h | 7 +- .../common/drop_allof_empty_schemas.h | 9 +- .../common/duplicate_allof_branches.h | 7 +- .../common/duplicate_anyof_branches.h | 7 +- .../common/duplicate_enum_values.h | 8 +- .../common/duplicate_required_values.h | 7 +- .../common/dynamic_ref_to_static_ref.h | 76 +- .../src/alterschema/common/enum_with_type.h | 12 +- .../common/equal_numeric_bounds_to_enum.h | 42 +- .../exclusive_maximum_number_and_maximum.h | 10 +- .../exclusive_minimum_number_and_minimum.h | 10 +- .../alterschema/common/ignored_metaschema.h | 5 +- .../common/maximum_real_for_integer.h | 15 +- .../common/minimum_real_for_integer.h | 15 +- ...ern_official_dialect_with_empty_fragment.h | 7 +- .../modern_official_dialect_with_http.h | 7 +- .../common/non_applicable_additional_items.h | 5 +- .../non_applicable_enum_validation_keywords.h | 16 +- .../non_applicable_type_specific_keywords.h | 23 +- .../alterschema/common/oneof_false_simplify.h | 9 +- .../common/oneof_to_anyof_disjoint_types.h | 37 +- .../alterschema/common/orphan_definitions.h | 28 +- .../required_properties_in_properties.h | 22 +- .../alterschema/common/single_type_array.h | 9 +- .../unnecessary_allof_ref_wrapper_draft.h | 11 +- .../common/unsatisfiable_drop_validation.h | 8 +- .../unsatisfiable_in_place_applicator_type.h | 21 +- .../include/sourcemeta/blaze/alterschema.h | 15 + .../alterschema/linter/const_not_in_enum.h | 10 +- .../linter/dependent_required_default.h | 8 +- .../alterschema/linter/duplicate_examples.h | 7 +- .../blaze/src/alterschema/linter/else_empty.h | 22 +- .../src/alterschema/linter/enum_to_const.h | 8 +- .../linter/equal_numeric_bounds_to_const.h | 28 +- .../alterschema/linter/forbid_empty_enum.h | 8 +- .../linter/incoherent_min_max_contains.h | 14 +- .../alterschema/linter/items_array_default.h | 7 +- .../alterschema/linter/multiple_of_default.h | 17 +- .../linter/pattern_properties_default.h | 9 +- .../linter/portable_anchor_names.h | 105 + .../alterschema/linter/properties_default.h | 8 +- .../linter/property_names_type_default.h | 33 +- .../linter/simple_properties_identifiers.h | 9 +- .../blaze/src/alterschema/linter/then_empty.h | 22 +- .../linter/top_level_description.h | 6 +- .../alterschema/linter/top_level_examples.h | 6 +- .../src/alterschema/linter/top_level_title.h | 6 +- .../unnecessary_allof_ref_wrapper_modern.h | 8 +- .../linter/unnecessary_allof_wrapper.h | 15 +- .../linter/unsatisfiable_max_contains.h | 12 +- .../linter/unsatisfiable_min_properties.h | 28 +- .../src/alterschema/linter/valid_examples.h | 8 +- .../blaze/src/alterschema/upgrade/helpers.h | 85 + .../prefix_promoted_2020_12_keywords.h | 65 + .../prefix_promoted_draft_2019_09_keywords.h | 74 + .../prefix_promoted_draft_4_keywords.h | 72 + .../prefix_promoted_draft_6_keywords.h | 71 + .../prefix_promoted_draft_7_keywords.h | 72 + .../upgrade/upgrade_2019_09_to_2020_12.h | 583 ++++ .../upgrade_dialect_override_cleanup.h | 47 + .../upgrade/upgrade_draft_3_to_draft_4.h | 396 +++ .../upgrade/upgrade_draft_4_to_draft_6.h | 624 +++++ .../upgrade/upgrade_draft_6_to_draft_7.h | 106 + .../upgrade_draft_7_to_draft_2019_09.h | 393 +++ vendor/blaze/src/compiler/compile_helpers.h | 48 + vendor/blaze/src/compiler/default_compiler.cc | 214 +- .../src/compiler/default_compiler_2019_09.h | 14 +- .../src/compiler/default_compiler_2020_12.h | 6 +- .../src/compiler/default_compiler_draft3.h | 2443 +++++++++++++++++ .../src/compiler/default_compiler_draft4.h | 2295 +--------------- .../blaze/src/evaluator/evaluator_describe.cc | 109 +- .../include/sourcemeta/blaze/evaluator.h | 2 +- .../sourcemeta/blaze/evaluator_dispatch.h | 17 +- .../sourcemeta/blaze/evaluator_instruction.h | 2 + .../src/test/include/sourcemeta/blaze/test.h | 16 +- vendor/blaze/src/test/test_parser.cc | 74 +- vendor/blaze/src/test/test_runner.cc | 29 +- .../core/src/lang/numeric/big_coefficient.h | 1 + vendor/core/src/lang/numeric/decimal.cc | 35 +- .../include/sourcemeta/core/numeric_decimal.h | 16 +- 153 files changed, 7214 insertions(+), 3298 deletions(-) create mode 100644 vendor/blaze/patches/jsonschema-test-suite/0001-draft3-ref-sibling.patch create mode 100644 vendor/blaze/src/alterschema/canonicalizer/draft3_drop_extends_empty_schemas.h create mode 100644 vendor/blaze/src/alterschema/canonicalizer/draft3_type_any.h create mode 100644 vendor/blaze/src/alterschema/linter/portable_anchor_names.h create mode 100644 vendor/blaze/src/alterschema/upgrade/helpers.h create mode 100644 vendor/blaze/src/alterschema/upgrade/prefix_promoted_2020_12_keywords.h create mode 100644 vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_2019_09_keywords.h create mode 100644 vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_4_keywords.h create mode 100644 vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_6_keywords.h create mode 100644 vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_7_keywords.h create mode 100644 vendor/blaze/src/alterschema/upgrade/upgrade_2019_09_to_2020_12.h create mode 100644 vendor/blaze/src/alterschema/upgrade/upgrade_dialect_override_cleanup.h create mode 100644 vendor/blaze/src/alterschema/upgrade/upgrade_draft_3_to_draft_4.h create mode 100644 vendor/blaze/src/alterschema/upgrade/upgrade_draft_4_to_draft_6.h create mode 100644 vendor/blaze/src/alterschema/upgrade/upgrade_draft_6_to_draft_7.h create mode 100644 vendor/blaze/src/alterschema/upgrade/upgrade_draft_7_to_draft_2019_09.h create mode 100644 vendor/blaze/src/compiler/default_compiler_draft3.h diff --git a/DEPENDENCIES b/DEPENDENCIES index 4e2b996b9..ba873061c 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,7 +1,7 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 uwebsockets https://github.com/uNetworking/uWebSockets v20.77.0 -core https://github.com/sourcemeta/core 8b2e47830b198136cefe3ba82b6a937b98c6371d -blaze https://github.com/sourcemeta/blaze 3494e9c82e0e8fbd974d99888accb4d7b8df5fff +core https://github.com/sourcemeta/core 19439f7c88386e820f36e67ac22840183bcd7b08 +blaze https://github.com/sourcemeta/blaze 5eeef87e52cf45ae76d5b8c7a38f3189da65adb7 jsonbinpack https://github.com/sourcemeta/jsonbinpack 8b93f69a1270d8d05eed6bb9e106c4d6613bb257 bootstrap https://github.com/twbs/bootstrap v5.3.3 bootstrap-icons https://github.com/twbs/icons v1.11.3 diff --git a/package-lock.json b/package-lock.json index 6be83299c..44a1f30f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ }, "devDependencies": { "@playwright/test": "^1.55.1", - "@sourcemeta/jsonschema": "^15.0.0", + "@sourcemeta/jsonschema": "^15.5.0", "jsdom": "^26.0.0" } }, @@ -356,9 +356,9 @@ } }, "node_modules/@sourcemeta/jsonschema": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@sourcemeta/jsonschema/-/jsonschema-15.0.0.tgz", - "integrity": "sha512-7XGYHQhVxOvu/m9nRRLbdl6z6AYPxvv8rGwHT+t4KA8MwzUaofpAdNigsz0LCeZrG2quKiMjwj4y+T9cb6HTPg==", + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/@sourcemeta/jsonschema/-/jsonschema-15.5.0.tgz", + "integrity": "sha512-4AgdIxbzGTxa98mfxEOfADgLhJOAzfgS7WfSGsy65rciLmDFC9i5iAWCOtdIRomeJ7LWvI4G/vlFbmVdm8hrVg==", "cpu": [ "x64", "arm64" diff --git a/package.json b/package.json index 8a4b7f79c..d3785a193 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@playwright/test": "^1.55.1", - "@sourcemeta/jsonschema": "^15.0.0", + "@sourcemeta/jsonschema": "^15.5.0", "jsdom": "^26.0.0" } } diff --git a/vendor/blaze/CMakeLists.txt b/vendor/blaze/CMakeLists.txt index ea51f7111..64f5f6c17 100644 --- a/vendor/blaze/CMakeLists.txt +++ b/vendor/blaze/CMakeLists.txt @@ -104,6 +104,7 @@ if(PROJECT_IS_TOP_LEVEL) add_custom_target(blaze_format_trace_json COMMAND "${Python3_EXECUTABLE}" "${PROJECT_SOURCE_DIR}/contrib/format_trace_json.py" + "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_draft3.json" "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_draft4.json" "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_draft6.json" "${PROJECT_SOURCE_DIR}/test/evaluator/evaluator_draft7.json" diff --git a/vendor/blaze/DEPENDENCIES b/vendor/blaze/DEPENDENCIES index 200b3e906..ff9cc8a82 100644 --- a/vendor/blaze/DEPENDENCIES +++ b/vendor/blaze/DEPENDENCIES @@ -1,3 +1,3 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 -core https://github.com/sourcemeta/core c43332629d71475f44d212f140effbf0a46c1492 +core https://github.com/sourcemeta/core 19439f7c88386e820f36e67ac22840183bcd7b08 jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite c7257e92580678a086f0b9243a1903ed88bd27f7 diff --git a/vendor/blaze/patches/jsonschema-test-suite/0001-draft3-ref-sibling.patch b/vendor/blaze/patches/jsonschema-test-suite/0001-draft3-ref-sibling.patch new file mode 100644 index 000000000..8c41d471b --- /dev/null +++ b/vendor/blaze/patches/jsonschema-test-suite/0001-draft3-ref-sibling.patch @@ -0,0 +1,26 @@ +From 95fec2442e66b3de6651972e5f7d4635ca6f7ff9 Mon Sep 17 00:00:00 2001 +From: Juan Cruz Viotti +Date: Tue, 5 May 2026 09:15:10 -0400 +Subject: [PATCH] Do not use `definitions` sibling to `$ref` in `ref.json` for + Draft 3 + +I'm extending Blaze to support Draft 3. As per the spec (applies to all drafts up to 7), a `$ref` overrides any sibling keyword. This seems to be the only case in the test suite where we do this. +--- + tests/draft3/ref.json | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/tests/draft3/ref.json b/tests/draft3/ref.json +index 609eaa46..6686c78e 100644 +--- a/tests/draft3/ref.json ++++ b/tests/draft3/ref.json +@@ -127,7 +127,9 @@ + "b": {"$ref": "#/definitions/a"}, + "c": {"$ref": "#/definitions/b"} + }, +- "$ref": "#/definitions/c" ++ "extends": { ++ "$ref": "#/definitions/c" ++ } + }, + "tests": [ + { diff --git a/vendor/blaze/ports/javascript/describe.mjs b/vendor/blaze/ports/javascript/describe.mjs index d51b19853..4f3b1050a 100644 --- a/vendor/blaze/ports/javascript/describe.mjs +++ b/vendor/blaze/ports/javascript/describe.mjs @@ -4,7 +4,8 @@ import { ASSERTION_DEFINES_EXACTLY, ASSERTION_DEFINES_EXACTLY_STRICT, ASSERTION_DEFINES_EXACTLY_STRICT_HASH3, ASSERTION_PROPERTY_DEPENDENCIES, ASSERTION_TYPE, ASSERTION_TYPE_ANY, ASSERTION_TYPE_STRICT, - ASSERTION_TYPE_STRICT_ANY, ASSERTION_TYPE_STRING_BOUNDED, + ASSERTION_TYPE_STRICT_ANY, ASSERTION_NOT_TYPE_STRICT_ANY, + ASSERTION_TYPE_STRING_BOUNDED, ASSERTION_TYPE_STRING_UPPER, ASSERTION_TYPE_ARRAY_BOUNDED, ASSERTION_TYPE_ARRAY_UPPER, ASSERTION_TYPE_OBJECT_BOUNDED, ASSERTION_TYPE_OBJECT_UPPER, ASSERTION_REGEX, @@ -49,6 +50,7 @@ import { const TYPE_INTEGER = 2; const TYPE_REAL = 3; const TYPE_OBJECT = 6; +const TYPE_DECIMAL = 7; const TYPE_NAMES = [ 'null', 'boolean', 'integer', 'number', 'string', 'array', 'object', 'number' ]; @@ -214,6 +216,22 @@ function describeTypeCheck(valid, currentType, expectedType) { return message; } +function describeNotTypeCheck(valid, currentType, expectedType) { + let message = 'The value was expected to NOT be of type ' + typeName(expectedType); + if (!valid) { + message += ' but it was of type '; + if (currentType === TYPE_DECIMAL && expectedType === TYPE_INTEGER) { + message += 'integer'; + } else if ((currentType === TYPE_INTEGER && expectedType === TYPE_REAL) || + currentType === TYPE_DECIMAL) { + message += 'number'; + } else { + message += typeName(currentType); + } + } + return message; +} + function describeTypesCheck(valid, currentType, bitmask) { let types = normalizeTypes(bitmask); const hasReal = (bitmask & (1 << TYPE_REAL)) !== 0; @@ -265,6 +283,57 @@ function describeTypesCheck(valid, currentType, bitmask) { return message; } +function describeNotTypesCheck(valid, currentType, bitmask) { + let types = normalizeTypes(bitmask); + const hasReal = (bitmask & (1 << TYPE_REAL)) !== 0; + const hasInteger = (bitmask & (1 << TYPE_INTEGER)) !== 0; + + let popcount = 0; + for (let bit = 0; bit < 8; bit++) { + if ((types & (1 << bit)) !== 0) popcount++; + } + + if (popcount === 1) { + let typeIndex = 0; + for (let bit = 0; bit < 8; bit++) { + if ((types & (1 << bit)) !== 0) { typeIndex = bit; break; } + } + return describeNotTypeCheck(valid, currentType, typeIndex); + } + + let message = 'The value was expected to NOT be of type '; + let first = true; + let lastBit = 0; + for (let bit = 0; bit < 8; bit++) { + if ((types & (1 << bit)) !== 0) lastBit = bit; + } + for (let bit = 0; bit < 8; bit++) { + if ((types & (1 << bit)) !== 0) { + if (!first) message += ', '; + if (bit === lastBit) message += 'or '; + message += typeName(bit); + first = false; + } + } + + if (valid) { + message += ' and it was of type '; + } else { + message += ' but it was of type '; + } + + if (!valid && currentType === TYPE_INTEGER && hasReal) { + message += 'number'; + } else if ((!valid && currentType === TYPE_INTEGER && hasReal) || + currentType === TYPE_REAL) { + message += 'number'; + } else { + message += typeName(currentType); + } + + return message; +} + function describeReference(target) { return 'The ' + typeName(jsonTypeOf(target)) + ' value was expected to validate against the referenced schema'; @@ -357,7 +426,7 @@ export function describe(valid, instruction, evaluatePath, } if (opcode === LOGICAL_AND) { - if (keyword === 'allOf') { + if (keyword === 'allOf' || keyword === 'extends') { const childCount = children ? children.length : 0; let message = 'The ' + typeName(targetType) + ' value was expected to validate against the '; @@ -916,6 +985,10 @@ export function describe(valid, instruction, evaluatePath, return describeTypesCheck(valid, targetType, value); } + if (opcode === ASSERTION_NOT_TYPE_STRICT_ANY) { + return describeNotTypesCheck(valid, targetType, value); + } + if (opcode === ASSERTION_TYPE_STRING_BOUNDED) { const minimum = value[0]; const maximum = value[1]; diff --git a/vendor/blaze/ports/javascript/index.mjs b/vendor/blaze/ports/javascript/index.mjs index 5642ca4a6..f58f86bf5 100644 --- a/vendor/blaze/ports/javascript/index.mjs +++ b/vendor/blaze/ports/javascript/index.mjs @@ -1,10 +1,12 @@ import { ANNOTATION_EMIT, ANNOTATION_TO_PARENT, ANNOTATION_BASENAME_TO_PARENT, + ASSERTION_EQUALS_ANY, CONTROL_GROUP as CONTROL_GROUP_START, - CONTROL_EVALUATE as CONTROL_EVALUATE_END + CONTROL_EVALUATE as CONTROL_EVALUATE_END, + CONTROL_JUMP, CONTROL_DYNAMIC_ANCHOR_JUMP } from './opcodes.mjs'; -const JSON_VERSION = 4; +const JSON_VERSION = 5; const DEPTH_LIMIT = 300; const URI_REGEX = /^[a-zA-Z][a-zA-Z0-9+\-.]*:[^\s]*$/; @@ -121,7 +123,7 @@ function prepareInstruction(instruction) { } const opcode = instruction[0]; - if (opcode === 27 && Array.isArray(instruction[5])) { + if (opcode === ASSERTION_EQUALS_ANY && Array.isArray(instruction[5])) { const values = instruction[5]; let allPrimitive = true; for (let index = 0; index < values.length; index++) { @@ -153,7 +155,7 @@ function prepareInstruction(instruction) { function resolveJumpTargets(instructions, targets) { for (let index = 0; index < instructions.length; index++) { const instruction = instructions[index]; - if (instruction[0] === 98) { + if (instruction[0] === CONTROL_JUMP) { const targetIndex = instruction[5]; if (targetIndex < targets.length) { instruction[5] = targets[targetIndex]; @@ -188,7 +190,7 @@ function collectAnchorNames(targets, 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') { + if (instruction[0] === CONTROL_DYNAMIC_ANCHOR_JUMP && typeof instruction[5] === 'string') { result.add(instruction[5]); } if (instruction[6]) { @@ -253,7 +255,7 @@ function compileInstructionToCode(instruction, captures, visited, budget) { 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;}'; + case 43: 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; } } @@ -276,65 +278,66 @@ function compileInstructionToCode(instruction, captures, visited, budget) { case 10: { var r=R('t'); return r?r+'var a=_jt(t);if(('+value+'&(1<'+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'+value[1]+')return false;':'')+'return true;':null; } + case 15: { var r=R('t'); return r?r+"return typeof t==='string'&&_ul(t)<="+value+';':null; } + case 16: { 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 17: { var r=R('t'); return r?r+'return Array.isArray(t)&&t.length<='+value+';':null; } + case 18: { 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 19: { var r=R('t'); return r?r+TO+'return false;var s=0;for(var k in t)s++;return s<='+value+';':null; } + case 20: return fb(20); case 21: return fb(21); case 22: return fb(22); + 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+'if(!Array.isArray(t))return true;return t.length>'+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: { var r=R('t'); return r?r+TO+'return true;var s=0;for(var k in t)s++;return s>'+value+';':null; } + case 27: { 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(27); } + case 28: return fb(28); case 29: return fb(29); + 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: { 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 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: return fb(40); + case 41: { 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 42: return fb(42); + case 43: { var r=R('t'); return r?IO+r+'if(t===void 0)return true;return _es(t)==='+value+';':null; } + case 44: return fb(44); + case 45: { var r=R('t'); return r?IO+r+'if(t===void 0)return true;return('+value+'&(1<<_es(t)))!==0;':null; } + case 46: return fb(46); case 47: return fb(47); case 48: return fb(48); + case 49: return fb(49); + case 50: return 'return true;'; case 51: return 'return true;'; case 52: return 'return true;'; case 53: return 'return true;'; + case 54: { 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 61: { 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 62: { 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 63: return fb(63); case 64: return fb(64); + 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){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;'; 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 67: { 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 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: return fb(76); + case 77: { 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 78: return fb(78); case 79: return fb(79); case 80: return fb(80); case 81: return fb(81); + case 82: { 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 95: { 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 96: { var c='if(_jt(i)!=='+value+')return true;'; if(children&&children.length>0)c+=seq(children,'i'); return c+'return true;'; } + case 97: return 'return true;'; + case 98: return fb(98); + case 99: { if(!value)return 'return true;'; if(visited&&visited.has(instruction))return fb(99); if(!visited)visited=new Set(); visited.add(instruction); var r=R('t'); if(!r)return fb(99); var c=r; for(var j=0;j 96) { + if (type < CONTROL_GROUP_START || type > CONTROL_EVALUATE_END) { if (evaluator.trackMode) { evaluator.pushPath(instruction[1]); } @@ -1054,6 +1057,14 @@ function AssertionTypeStrictAny(instruction, instance, depth, template, evaluato return __result; }; +function AssertionNotTypeStrictAny(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]); @@ -2617,92 +2628,93 @@ const handlers = [ 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 + AssertionNotTypeStrictAny, // 13 + AssertionTypeStringBounded, // 14 + AssertionTypeStringUpper, // 15 + AssertionTypeArrayBounded, // 16 + AssertionTypeArrayUpper, // 17 + AssertionTypeObjectBounded, // 18 + AssertionTypeObjectUpper, // 19 + AssertionRegex, // 20 + AssertionStringSizeLess, // 21 + AssertionStringSizeGreater, // 22 + AssertionArraySizeLess, // 23 + AssertionArraySizeGreater, // 24 + AssertionObjectSizeLess, // 25 + AssertionObjectSizeGreater, // 26 + AssertionEqual, // 27 + AssertionEqualsAny, // 28 + AssertionEqualsAnyStringHash, // 29 + AssertionGreaterEqual, // 30 + AssertionLessEqual, // 31 + AssertionGreater, // 32 + AssertionLess, // 33 + AssertionUnique, // 34 + AssertionDivisible, // 35 + AssertionTypeIntegerBounded, // 36 + AssertionTypeIntegerBoundedStrict, // 37 + AssertionTypeIntegerLowerBound, // 38 + AssertionTypeIntegerLowerBoundStrict, // 39 + AssertionStringType, // 40 + AssertionPropertyType, // 41 + AssertionPropertyTypeEvaluate, // 42 + AssertionPropertyTypeStrict, // 43 + AssertionPropertyTypeStrictEvaluate, // 44 + AssertionPropertyTypeStrictAny, // 45 + AssertionPropertyTypeStrictAnyEvaluate, // 46 + AssertionArrayPrefix, // 47 + AssertionArrayPrefixEvaluate, // 48 + AssertionObjectPropertiesSimple, // 49 + AnnotationEmit, // 50 + AnnotationToParent, // 51 + AnnotationBasenameToParent, // 52 + Evaluate, // 53 + LogicalNot, // 54 + LogicalNotEvaluate, // 55 + LogicalOr, // 56 + LogicalAnd, // 57 + LogicalXor, // 58 + LogicalCondition, // 59 + LogicalWhenType, // 60 + LogicalWhenDefines, // 61 + LogicalWhenArraySizeGreater, // 62 + LoopPropertiesUnevaluated, // 63 + LoopPropertiesUnevaluatedExcept, // 64 + LoopPropertiesMatch, // 65 + LoopPropertiesMatchClosed, // 66 + LoopProperties, // 67 + LoopPropertiesEvaluate, // 68 + LoopPropertiesRegex, // 69 + LoopPropertiesRegexClosed, // 70 + LoopPropertiesStartsWith, // 71 + LoopPropertiesExcept, // 72 + LoopPropertiesType, // 73 + LoopPropertiesTypeEvaluate, // 74 + LoopPropertiesExactlyTypeStrict, // 75 + LoopPropertiesExactlyTypeStrictHash, // 76 + LoopPropertiesTypeStrict, // 77 + LoopPropertiesTypeStrictEvaluate, // 78 + LoopPropertiesTypeStrictAny, // 79 + LoopPropertiesTypeStrictAnyEvaluate, // 80 + LoopKeys, // 81 + LoopItems, // 82 + LoopItemsFrom, // 83 + LoopItemsUnevaluated, // 84 + LoopItemsType, // 85 + LoopItemsTypeStrict, // 86 + LoopItemsTypeStrictAny, // 87 LoopItemsPropertiesExactlyTypeStrictHash, // 88 - LoopItemsIntegerBounded, // 89 - LoopItemsIntegerBoundedSized, // 90 - LoopContains, // 91 - ControlGroup, // 92 - ControlGroupWhenDefines, // 93 - ControlGroupWhenDefinesDirect, // 94 - ControlGroupWhenType, // 95 - ControlEvaluate, // 96 - ControlDynamicAnchorJump, // 97 - ControlJump // 98 + LoopItemsPropertiesExactlyTypeStrictHash, // 89 + LoopItemsIntegerBounded, // 90 + LoopItemsIntegerBoundedSized, // 91 + LoopContains, // 92 + ControlGroup, // 93 + ControlGroupWhenDefines, // 94 + ControlGroupWhenDefinesDirect, // 95 + ControlGroupWhenType, // 96 + ControlEvaluate, // 97 + ControlDynamicAnchorJump, // 98 + ControlJump // 99 ]; function AssertionTypeArrayBounded_fast(instruction, instance, depth, template, evaluator) { @@ -3211,6 +3223,12 @@ function AssertionTypeStrictAny_fast(instruction, instance, depth, template, eva return typeSetTest(instruction[5], effectiveTypeStrictReal(target)); } +function AssertionNotTypeStrictAny_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); @@ -3879,100 +3897,101 @@ function AssertionTypeIntegerLowerBoundStrict_fast(instruction, instance, depth, } const fastHandlers = handlers.slice(); -fastHandlers[15] = AssertionTypeArrayBounded_fast; -fastHandlers[86] = LoopItemsTypeStrictAny_fast; -fastHandlers[42] = AssertionPropertyTypeStrict_fast; +fastHandlers[16] = AssertionTypeArrayBounded_fast; +fastHandlers[87] = LoopItemsTypeStrictAny_fast; +fastHandlers[43] = 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[27] = AssertionEqual_fast; +fastHandlers[65] = LoopPropertiesMatch_fast; +fastHandlers[56] = LogicalOr_fast; +fastHandlers[99] = ControlJump_fast; +fastHandlers[29] = AssertionEqualsAnyStringHash_fast; +fastHandlers[58] = LogicalXor_fast; fastHandlers[2] = AssertionDefinesStrict_fast; -fastHandlers[81] = LoopItems_fast; -fastHandlers[65] = LoopPropertiesMatchClosed_fast; -fastHandlers[13] = AssertionTypeStringBounded_fast; -fastHandlers[56] = LogicalAnd_fast; +fastHandlers[82] = LoopItems_fast; +fastHandlers[66] = LoopPropertiesMatchClosed_fast; +fastHandlers[14] = AssertionTypeStringBounded_fast; +fastHandlers[57] = 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[59] = LogicalCondition_fast; +fastHandlers[72] = LoopPropertiesExcept_fast; +fastHandlers[20] = AssertionRegex_fast; +fastHandlers[67] = LoopProperties_fast; fastHandlers[1] = AssertionDefines_fast; -fastHandlers[59] = LogicalWhenType_fast; -fastHandlers[60] = LogicalWhenDefines_fast; +fastHandlers[60] = LogicalWhenType_fast; +fastHandlers[61] = 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[92] = LoopContains_fast; +fastHandlers[54] = LogicalNot_fast; +fastHandlers[85] = LoopItemsType_fast; +fastHandlers[86] = LoopItemsTypeStrict_fast; +fastHandlers[28] = 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[13] = AssertionNotTypeStrictAny_fast; +fastHandlers[15] = AssertionTypeStringUpper_fast; +fastHandlers[17] = AssertionTypeArrayUpper_fast; +fastHandlers[18] = AssertionTypeObjectBounded_fast; +fastHandlers[19] = AssertionTypeObjectUpper_fast; +fastHandlers[21] = AssertionStringSizeLess_fast; +fastHandlers[22] = AssertionStringSizeGreater_fast; +fastHandlers[23] = AssertionArraySizeLess_fast; +fastHandlers[24] = AssertionArraySizeGreater_fast; +fastHandlers[25] = AssertionObjectSizeLess_fast; +fastHandlers[26] = AssertionObjectSizeGreater_fast; +fastHandlers[30] = AssertionGreaterEqual_fast; +fastHandlers[31] = AssertionLessEqual_fast; +fastHandlers[32] = AssertionGreater_fast; +fastHandlers[33] = AssertionLess_fast; +fastHandlers[34] = AssertionUnique_fast; +fastHandlers[35] = AssertionDivisible_fast; +fastHandlers[36] = AssertionTypeIntegerBounded_fast; +fastHandlers[37] = AssertionTypeIntegerBoundedStrict_fast; +fastHandlers[38] = AssertionTypeIntegerLowerBound_fast; +fastHandlers[39] = AssertionTypeIntegerLowerBoundStrict_fast; +fastHandlers[40] = AssertionStringType_fast; +fastHandlers[41] = AssertionPropertyType_fast; +fastHandlers[42] = AssertionPropertyTypeEvaluate_fast; +fastHandlers[44] = AssertionPropertyTypeStrictEvaluate_fast; +fastHandlers[45] = AssertionPropertyTypeStrictAny_fast; +fastHandlers[46] = AssertionPropertyTypeStrictAnyEvaluate_fast; +fastHandlers[47] = AssertionArrayPrefix_fast; +fastHandlers[48] = AssertionArrayPrefixEvaluate_fast; +fastHandlers[49] = AssertionObjectPropertiesSimple_fast; +fastHandlers[50] = AnnotationEmit_fast; +fastHandlers[51] = AnnotationToParent_fast; +fastHandlers[52] = AnnotationBasenameToParent_fast; +fastHandlers[53] = Evaluate_fast; +fastHandlers[55] = LogicalNotEvaluate_fast; +fastHandlers[62] = LogicalWhenArraySizeGreater_fast; +fastHandlers[63] = LoopPropertiesUnevaluated_fast; +fastHandlers[64] = LoopPropertiesUnevaluatedExcept_fast; +fastHandlers[68] = LoopPropertiesEvaluate_fast; +fastHandlers[69] = LoopPropertiesRegex_fast; +fastHandlers[70] = LoopPropertiesRegexClosed_fast; +fastHandlers[71] = LoopPropertiesStartsWith_fast; +fastHandlers[73] = LoopPropertiesType_fast; +fastHandlers[74] = LoopPropertiesTypeEvaluate_fast; +fastHandlers[75] = LoopPropertiesExactlyTypeStrict_fast; +fastHandlers[76] = LoopPropertiesExactlyTypeStrictHash_fast; +fastHandlers[77] = LoopPropertiesTypeStrict_fast; +fastHandlers[78] = LoopPropertiesTypeStrictEvaluate_fast; +fastHandlers[79] = LoopPropertiesTypeStrictAny_fast; +fastHandlers[80] = LoopPropertiesTypeStrictAnyEvaluate_fast; +fastHandlers[81] = LoopKeys_fast; +fastHandlers[83] = LoopItemsFrom_fast; +fastHandlers[84] = LoopItemsUnevaluated_fast; fastHandlers[88] = LoopItemsPropertiesExactlyTypeStrictHash_fast; -fastHandlers[89] = LoopItemsIntegerBounded_fast; -fastHandlers[90] = LoopItemsIntegerBoundedSized_fast; -fastHandlers[97] = ControlDynamicAnchorJump_fast; +fastHandlers[89] = LoopItemsPropertiesExactlyTypeStrictHash_fast; +fastHandlers[90] = LoopItemsIntegerBounded_fast; +fastHandlers[91] = LoopItemsIntegerBoundedSized_fast; +fastHandlers[98] = ControlDynamicAnchorJump_fast; import { describe } from './describe.mjs'; diff --git a/vendor/blaze/ports/javascript/opcodes.mjs b/vendor/blaze/ports/javascript/opcodes.mjs index 22f9a034e..958382973 100644 --- a/vendor/blaze/ports/javascript/opcodes.mjs +++ b/vendor/blaze/ports/javascript/opcodes.mjs @@ -11,92 +11,93 @@ export const ASSERTION_TYPE = 9; export const ASSERTION_TYPE_ANY = 10; export const ASSERTION_TYPE_STRICT = 11; export const ASSERTION_TYPE_STRICT_ANY = 12; -export const ASSERTION_TYPE_STRING_BOUNDED = 13; -export const ASSERTION_TYPE_STRING_UPPER = 14; -export const ASSERTION_TYPE_ARRAY_BOUNDED = 15; -export const ASSERTION_TYPE_ARRAY_UPPER = 16; -export const ASSERTION_TYPE_OBJECT_BOUNDED = 17; -export const ASSERTION_TYPE_OBJECT_UPPER = 18; -export const ASSERTION_REGEX = 19; -export const ASSERTION_STRING_SIZE_LESS = 20; -export const ASSERTION_STRING_SIZE_GREATER = 21; -export const ASSERTION_ARRAY_SIZE_LESS = 22; -export const ASSERTION_ARRAY_SIZE_GREATER = 23; -export const ASSERTION_OBJECT_SIZE_LESS = 24; -export const ASSERTION_OBJECT_SIZE_GREATER = 25; -export const ASSERTION_EQUAL = 26; -export const ASSERTION_EQUALS_ANY = 27; -export const ASSERTION_EQUALS_ANY_STRING_HASH = 28; -export const ASSERTION_GREATER_EQUAL = 29; -export const ASSERTION_LESS_EQUAL = 30; -export const ASSERTION_GREATER = 31; -export const ASSERTION_LESS = 32; -export const ASSERTION_UNIQUE = 33; -export const ASSERTION_DIVISIBLE = 34; -export const ASSERTION_TYPE_INTEGER_BOUNDED = 35; -export const ASSERTION_TYPE_INTEGER_BOUNDED_STRICT = 36; -export const ASSERTION_TYPE_INTEGER_LOWER_BOUND = 37; -export const ASSERTION_TYPE_INTEGER_LOWER_BOUND_STRICT = 38; -export const ASSERTION_STRING_TYPE = 39; -export const ASSERTION_PROPERTY_TYPE = 40; -export const ASSERTION_PROPERTY_TYPE_EVALUATE = 41; -export const ASSERTION_PROPERTY_TYPE_STRICT = 42; -export const ASSERTION_PROPERTY_TYPE_STRICT_EVALUATE = 43; -export const ASSERTION_PROPERTY_TYPE_STRICT_ANY = 44; -export const ASSERTION_PROPERTY_TYPE_STRICT_ANY_EVALUATE = 45; -export const ASSERTION_ARRAY_PREFIX = 46; -export const ASSERTION_ARRAY_PREFIX_EVALUATE = 47; -export const ASSERTION_OBJECT_PROPERTIES_SIMPLE = 48; -export const ANNOTATION_EMIT = 49; -export const ANNOTATION_TO_PARENT = 50; -export const ANNOTATION_BASENAME_TO_PARENT = 51; -export const EVALUATE = 52; -export const LOGICAL_NOT = 53; -export const LOGICAL_NOT_EVALUATE = 54; -export const LOGICAL_OR = 55; -export const LOGICAL_AND = 56; -export const LOGICAL_XOR = 57; -export const LOGICAL_CONDITION = 58; -export const LOGICAL_WHEN_TYPE = 59; -export const LOGICAL_WHEN_DEFINES = 60; -export const LOGICAL_WHEN_ARRAY_SIZE_GREATER = 61; -export const LOOP_PROPERTIES_UNEVALUATED = 62; -export const LOOP_PROPERTIES_UNEVALUATED_EXCEPT = 63; -export const LOOP_PROPERTIES_MATCH = 64; -export const LOOP_PROPERTIES_MATCH_CLOSED = 65; -export const LOOP_PROPERTIES = 66; -export const LOOP_PROPERTIES_EVALUATE = 67; -export const LOOP_PROPERTIES_REGEX = 68; -export const LOOP_PROPERTIES_REGEX_CLOSED = 69; -export const LOOP_PROPERTIES_STARTS_WITH = 70; -export const LOOP_PROPERTIES_EXCEPT = 71; -export const LOOP_PROPERTIES_TYPE = 72; -export const LOOP_PROPERTIES_TYPE_EVALUATE = 73; -export const LOOP_PROPERTIES_EXACTLY_TYPE_STRICT = 74; -export const LOOP_PROPERTIES_EXACTLY_TYPE_STRICT_HASH = 75; -export const LOOP_PROPERTIES_TYPE_STRICT = 76; -export const LOOP_PROPERTIES_TYPE_STRICT_EVALUATE = 77; -export const LOOP_PROPERTIES_TYPE_STRICT_ANY = 78; -export const LOOP_PROPERTIES_TYPE_STRICT_ANY_EVALUATE = 79; -export const LOOP_KEYS = 80; -export const LOOP_ITEMS = 81; -export const LOOP_ITEMS_FROM = 82; -export const LOOP_ITEMS_UNEVALUATED = 83; -export const LOOP_ITEMS_TYPE = 84; -export const LOOP_ITEMS_TYPE_STRICT = 85; -export const LOOP_ITEMS_TYPE_STRICT_ANY = 86; -export const LOOP_ITEMS_PROPERTIES_EXACTLY_TYPE_STRICT_HASH = 87; -export const LOOP_ITEMS_PROPERTIES_EXACTLY_TYPE_STRICT_HASH3 = 88; -export const LOOP_ITEMS_INTEGER_BOUNDED = 89; -export const LOOP_ITEMS_INTEGER_BOUNDED_SIZED = 90; -export const LOOP_CONTAINS = 91; -export const CONTROL_GROUP = 92; -export const CONTROL_GROUP_WHEN_DEFINES = 93; -export const CONTROL_GROUP_WHEN_DEFINES_DIRECT = 94; -export const CONTROL_GROUP_WHEN_TYPE = 95; -export const CONTROL_EVALUATE = 96; -export const CONTROL_DYNAMIC_ANCHOR_JUMP = 97; -export const CONTROL_JUMP = 98; +export const ASSERTION_NOT_TYPE_STRICT_ANY = 13; +export const ASSERTION_TYPE_STRING_BOUNDED = 14; +export const ASSERTION_TYPE_STRING_UPPER = 15; +export const ASSERTION_TYPE_ARRAY_BOUNDED = 16; +export const ASSERTION_TYPE_ARRAY_UPPER = 17; +export const ASSERTION_TYPE_OBJECT_BOUNDED = 18; +export const ASSERTION_TYPE_OBJECT_UPPER = 19; +export const ASSERTION_REGEX = 20; +export const ASSERTION_STRING_SIZE_LESS = 21; +export const ASSERTION_STRING_SIZE_GREATER = 22; +export const ASSERTION_ARRAY_SIZE_LESS = 23; +export const ASSERTION_ARRAY_SIZE_GREATER = 24; +export const ASSERTION_OBJECT_SIZE_LESS = 25; +export const ASSERTION_OBJECT_SIZE_GREATER = 26; +export const ASSERTION_EQUAL = 27; +export const ASSERTION_EQUALS_ANY = 28; +export const ASSERTION_EQUALS_ANY_STRING_HASH = 29; +export const ASSERTION_GREATER_EQUAL = 30; +export const ASSERTION_LESS_EQUAL = 31; +export const ASSERTION_GREATER = 32; +export const ASSERTION_LESS = 33; +export const ASSERTION_UNIQUE = 34; +export const ASSERTION_DIVISIBLE = 35; +export const ASSERTION_TYPE_INTEGER_BOUNDED = 36; +export const ASSERTION_TYPE_INTEGER_BOUNDED_STRICT = 37; +export const ASSERTION_TYPE_INTEGER_LOWER_BOUND = 38; +export const ASSERTION_TYPE_INTEGER_LOWER_BOUND_STRICT = 39; +export const ASSERTION_STRING_TYPE = 40; +export const ASSERTION_PROPERTY_TYPE = 41; +export const ASSERTION_PROPERTY_TYPE_EVALUATE = 42; +export const ASSERTION_PROPERTY_TYPE_STRICT = 43; +export const ASSERTION_PROPERTY_TYPE_STRICT_EVALUATE = 44; +export const ASSERTION_PROPERTY_TYPE_STRICT_ANY = 45; +export const ASSERTION_PROPERTY_TYPE_STRICT_ANY_EVALUATE = 46; +export const ASSERTION_ARRAY_PREFIX = 47; +export const ASSERTION_ARRAY_PREFIX_EVALUATE = 48; +export const ASSERTION_OBJECT_PROPERTIES_SIMPLE = 49; +export const ANNOTATION_EMIT = 50; +export const ANNOTATION_TO_PARENT = 51; +export const ANNOTATION_BASENAME_TO_PARENT = 52; +export const EVALUATE = 53; +export const LOGICAL_NOT = 54; +export const LOGICAL_NOT_EVALUATE = 55; +export const LOGICAL_OR = 56; +export const LOGICAL_AND = 57; +export const LOGICAL_XOR = 58; +export const LOGICAL_CONDITION = 59; +export const LOGICAL_WHEN_TYPE = 60; +export const LOGICAL_WHEN_DEFINES = 61; +export const LOGICAL_WHEN_ARRAY_SIZE_GREATER = 62; +export const LOOP_PROPERTIES_UNEVALUATED = 63; +export const LOOP_PROPERTIES_UNEVALUATED_EXCEPT = 64; +export const LOOP_PROPERTIES_MATCH = 65; +export const LOOP_PROPERTIES_MATCH_CLOSED = 66; +export const LOOP_PROPERTIES = 67; +export const LOOP_PROPERTIES_EVALUATE = 68; +export const LOOP_PROPERTIES_REGEX = 69; +export const LOOP_PROPERTIES_REGEX_CLOSED = 70; +export const LOOP_PROPERTIES_STARTS_WITH = 71; +export const LOOP_PROPERTIES_EXCEPT = 72; +export const LOOP_PROPERTIES_TYPE = 73; +export const LOOP_PROPERTIES_TYPE_EVALUATE = 74; +export const LOOP_PROPERTIES_EXACTLY_TYPE_STRICT = 75; +export const LOOP_PROPERTIES_EXACTLY_TYPE_STRICT_HASH = 76; +export const LOOP_PROPERTIES_TYPE_STRICT = 77; +export const LOOP_PROPERTIES_TYPE_STRICT_EVALUATE = 78; +export const LOOP_PROPERTIES_TYPE_STRICT_ANY = 79; +export const LOOP_PROPERTIES_TYPE_STRICT_ANY_EVALUATE = 80; +export const LOOP_KEYS = 81; +export const LOOP_ITEMS = 82; +export const LOOP_ITEMS_FROM = 83; +export const LOOP_ITEMS_UNEVALUATED = 84; +export const LOOP_ITEMS_TYPE = 85; +export const LOOP_ITEMS_TYPE_STRICT = 86; +export const LOOP_ITEMS_TYPE_STRICT_ANY = 87; +export const LOOP_ITEMS_PROPERTIES_EXACTLY_TYPE_STRICT_HASH = 88; +export const LOOP_ITEMS_PROPERTIES_EXACTLY_TYPE_STRICT_HASH3 = 89; +export const LOOP_ITEMS_INTEGER_BOUNDED = 90; +export const LOOP_ITEMS_INTEGER_BOUNDED_SIZED = 91; +export const LOOP_CONTAINS = 92; +export const CONTROL_GROUP = 93; +export const CONTROL_GROUP_WHEN_DEFINES = 94; +export const CONTROL_GROUP_WHEN_DEFINES_DIRECT = 95; +export const CONTROL_GROUP_WHEN_TYPE = 96; +export const CONTROL_EVALUATE = 97; +export const CONTROL_DYNAMIC_ANCHOR_JUMP = 98; +export const CONTROL_JUMP = 99; export const INSTRUCTION_NAMES = { "AssertionFail": ASSERTION_FAIL, @@ -112,6 +113,7 @@ export const INSTRUCTION_NAMES = { "AssertionTypeAny": ASSERTION_TYPE_ANY, "AssertionTypeStrict": ASSERTION_TYPE_STRICT, "AssertionTypeStrictAny": ASSERTION_TYPE_STRICT_ANY, + "AssertionNotTypeStrictAny": ASSERTION_NOT_TYPE_STRICT_ANY, "AssertionTypeStringBounded": ASSERTION_TYPE_STRING_BOUNDED, "AssertionTypeStringUpper": ASSERTION_TYPE_STRING_UPPER, "AssertionTypeArrayBounded": ASSERTION_TYPE_ARRAY_BOUNDED, diff --git a/vendor/blaze/src/alterschema/CMakeLists.txt b/vendor/blaze/src/alterschema/CMakeLists.txt index db807d2b4..1f34d2719 100644 --- a/vendor/blaze/src/alterschema/CMakeLists.txt +++ b/vendor/blaze/src/alterschema/CMakeLists.txt @@ -11,6 +11,8 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME alterschema canonicalizer/dependent_required_to_any_of.h canonicalizer/dependent_schemas_to_any_of.h canonicalizer/deprecated_false_drop.h + canonicalizer/draft3_drop_extends_empty_schemas.h + canonicalizer/draft3_type_any.h canonicalizer/disallow_to_array_of_schemas.h canonicalizer/divisible_by_implicit.h canonicalizer/empty_definitions_drop.h @@ -133,6 +135,7 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT blaze NAME alterschema linter/items_schema_default.h linter/multiple_of_default.h linter/pattern_properties_default.h + linter/portable_anchor_names.h linter/properties_default.h linter/property_names_default.h linter/property_names_type_default.h diff --git a/vendor/blaze/src/alterschema/alterschema.cc b/vendor/blaze/src/alterschema/alterschema.cc index 97f9e25c7..ca09782ec 100644 --- a/vendor/blaze/src/alterschema/alterschema.cc +++ b/vendor/blaze/src/alterschema/alterschema.cc @@ -3,6 +3,7 @@ #include #include #include +#include // For built-in rules #include // std::sort, std::unique, std::ranges::none_of @@ -118,6 +119,8 @@ auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame, #include "canonicalizer/deprecated_false_drop.h" #include "canonicalizer/disallow_to_array_of_schemas.h" #include "canonicalizer/divisible_by_implicit.h" +#include "canonicalizer/draft3_drop_extends_empty_schemas.h" +#include "canonicalizer/draft3_type_any.h" #include "canonicalizer/empty_definitions_drop.h" #include "canonicalizer/empty_defs_drop.h" #include "canonicalizer/empty_dependencies_drop.h" @@ -242,6 +245,7 @@ auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame, #include "linter/items_schema_default.h" #include "linter/multiple_of_default.h" #include "linter/pattern_properties_default.h" +#include "linter/portable_anchor_names.h" #include "linter/properties_default.h" #include "linter/property_names_default.h" #include "linter/property_names_type_default.h" @@ -259,12 +263,70 @@ auto WALK_UP_IN_PLACE_APPLICATORS(const JSON &root, const SchemaFrame &frame, #include "linter/valid_default.h" #include "linter/valid_examples.h" +// Upgrade +#include "upgrade/helpers.h" +#include "upgrade/prefix_promoted_2020_12_keywords.h" +#include "upgrade/prefix_promoted_draft_2019_09_keywords.h" +#include "upgrade/prefix_promoted_draft_4_keywords.h" +#include "upgrade/prefix_promoted_draft_6_keywords.h" +#include "upgrade/prefix_promoted_draft_7_keywords.h" +#include "upgrade/upgrade_2019_09_to_2020_12.h" +#include "upgrade/upgrade_dialect_override_cleanup.h" +#include "upgrade/upgrade_draft_3_to_draft_4.h" +#include "upgrade/upgrade_draft_4_to_draft_6.h" +#include "upgrade/upgrade_draft_6_to_draft_7.h" +#include "upgrade/upgrade_draft_7_to_draft_2019_09.h" + #undef ONLY_CONTINUE_IF } // namespace sourcemeta::blaze namespace sourcemeta::blaze { auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { + if (mode == AlterSchemaMode::UpgradeDraft4 || + mode == AlterSchemaMode::UpgradeDraft6 || + mode == AlterSchemaMode::UpgradeDraft7 || + mode == AlterSchemaMode::Upgrade201909 || + mode == AlterSchemaMode::Upgrade202012) { + bundle.add(); + bundle.add(); + bundle.add(); + bundle.add(); + + if (mode == AlterSchemaMode::UpgradeDraft6 || + mode == AlterSchemaMode::UpgradeDraft7 || + mode == AlterSchemaMode::Upgrade201909 || + mode == AlterSchemaMode::Upgrade202012) { + bundle.add(); + bundle.add(); + bundle.add(); + } + + if (mode == AlterSchemaMode::UpgradeDraft7 || + mode == AlterSchemaMode::Upgrade201909 || + mode == AlterSchemaMode::Upgrade202012) { + bundle.add(); + bundle.add(); + bundle.add(); + } + + if (mode == AlterSchemaMode::Upgrade201909 || + mode == AlterSchemaMode::Upgrade202012) { + bundle.add(); + bundle.add(); + bundle.add(); + } + + if (mode == AlterSchemaMode::Upgrade202012) { + bundle.add(); + bundle.add(); + } + + bundle.add(); + + return; + } + if (mode == AlterSchemaMode::Canonicalizer) { bundle.add(); bundle.add(); @@ -310,6 +372,9 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + if (mode == AlterSchemaMode::Canonicalizer) { + bundle.add(); + } bundle.add(); bundle.add(); bundle.add(); @@ -400,6 +465,7 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { bundle.add(); bundle.add(); bundle.add(); + bundle.add(); bundle.add(); bundle.add(); bundle.add(); @@ -415,6 +481,9 @@ auto add(SchemaTransformer &bundle, const AlterSchemaMode mode) -> void { } bundle.add(); + if (mode == AlterSchemaMode::Canonicalizer) { + bundle.add(); + } bundle.add(); if (mode == AlterSchemaMode::Canonicalizer) { diff --git a/vendor/blaze/src/alterschema/canonicalizer/additional_items_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/additional_items_implicit.h index 154a58743..320c99081 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/additional_items_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/additional_items_implicit.h @@ -19,10 +19,13 @@ class AdditionalItemsImplicit final : public SchemaTransformRule { 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() == "array" && schema.defines("items") && - schema.at("items").is_array() && !schema.defines("additionalItems")); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && type->to_string() == "array"); + const auto *items{schema.try_at("items")}; + ONLY_CONTINUE_IF(items && items->is_array() && + !schema.defines("additionalItems")); this->is_draft3_ = vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_3); return true; diff --git a/vendor/blaze/src/alterschema/canonicalizer/allof_merge_compatible_branches.h b/vendor/blaze/src/alterschema/canonicalizer/allof_merge_compatible_branches.h index 5755a6383..ec26664c6 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/allof_merge_compatible_branches.h +++ b/vendor/blaze/src/alterschema/canonicalizer/allof_merge_compatible_branches.h @@ -21,14 +21,15 @@ class AllOfMergeCompatibleBranches final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines(KEYWORD) && - schema.at(KEYWORD).is_array() && - schema.at(KEYWORD).size() >= 2); + schema.is_object()); + + const auto *all_of{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(all_of && all_of->is_array() && all_of->size() >= 2); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); - const auto &branches{schema.at(KEYWORD)}; + const auto &branches{*all_of}; for (std::size_t index_a = 0; index_a < branches.size(); ++index_a) { const auto &branch_a{branches.at(index_a)}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_any_of.h b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_any_of.h index 655c738ce..ebd0a3154 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_any_of.h +++ b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_any_of.h @@ -17,11 +17,13 @@ class DependenciesToAnyOf final : public SchemaTransformRule { 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()); + schema.is_object()); - ONLY_CONTINUE_IF(std::ranges::any_of( - schema.at("dependencies").as_object(), [](const auto &entry) { + const auto *dependencies{schema.try_at("dependencies")}; + ONLY_CONTINUE_IF(dependencies && dependencies->is_object()); + + ONLY_CONTINUE_IF( + std::ranges::any_of(dependencies->as_object(), [](const auto &entry) { return is_schema(entry.second) || entry.second.is_array(); })); return true; diff --git a/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_extends_disallow.h b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_extends_disallow.h index 98e4f921b..2c36d9348 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_extends_disallow.h +++ b/vendor/blaze/src/alterschema/canonicalizer/dependencies_to_extends_disallow.h @@ -16,11 +16,13 @@ class DependenciesToExtendsDisallow final : public SchemaTransformRule { -> 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()); + schema.is_object()); - ONLY_CONTINUE_IF(std::ranges::any_of( - schema.at("dependencies").as_object(), [](const auto &entry) { + const auto *dependencies{schema.try_at("dependencies")}; + ONLY_CONTINUE_IF(dependencies && dependencies->is_object()); + + ONLY_CONTINUE_IF( + std::ranges::any_of(dependencies->as_object(), [](const auto &entry) { return is_schema(entry.second) || entry.second.is_array() || entry.second.is_string(); })); 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 index 4a71d56aa..a98ffe5af 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/dependent_required_to_any_of.h +++ b/vendor/blaze/src/alterschema/canonicalizer/dependent_required_to_any_of.h @@ -18,12 +18,14 @@ class DependentRequiredToAnyOf final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *dependent_required{schema.try_at("dependentRequired")}; + ONLY_CONTINUE_IF(dependent_required && dependent_required->is_object() && + !dependent_required->empty()); ONLY_CONTINUE_IF(std::ranges::any_of( - schema.at("dependentRequired").as_object(), + dependent_required->as_object(), [](const auto &entry) { return entry.second.is_array(); })); return true; } 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 index 9dabcda96..61f7953b6 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/dependent_schemas_to_any_of.h +++ b/vendor/blaze/src/alterschema/canonicalizer/dependent_schemas_to_any_of.h @@ -18,9 +18,11 @@ class DependentSchemasToAnyOf final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *dependent_schemas{schema.try_at("dependentSchemas")}; + ONLY_CONTINUE_IF(dependent_schemas && dependent_schemas->is_object() && + !dependent_schemas->empty()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/deprecated_false_drop.h b/vendor/blaze/src/alterschema/canonicalizer/deprecated_false_drop.h index 1bee8c02a..d692d24af 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/deprecated_false_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/deprecated_false_drop.h @@ -17,9 +17,11 @@ class DeprecatedFalseDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *deprecated{schema.try_at("deprecated")}; + ONLY_CONTINUE_IF(deprecated && deprecated->is_boolean() && + !deprecated->to_boolean()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/divisible_by_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/divisible_by_implicit.h index 3d0fa521c..507b82c76 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/divisible_by_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/divisible_by_implicit.h @@ -16,10 +16,11 @@ class DivisibleByImplicit final : public SchemaTransformRule { 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")); + schema.is_object() && !schema.defines("divisibleBy")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/draft3_drop_extends_empty_schemas.h b/vendor/blaze/src/alterschema/canonicalizer/draft3_drop_extends_empty_schemas.h new file mode 100644 index 000000000..871fa5be1 --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/draft3_drop_extends_empty_schemas.h @@ -0,0 +1,56 @@ +class Draft3DropExtendsEmptySchemas final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + Draft3DropExtendsEmptySchemas() + : SchemaTransformRule{"draft3_drop_extends_empty_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(Vocabularies::Known::JSON_Schema_Draft_3) && + schema.is_object()); + + const auto *extends{schema.try_at("extends")}; + ONLY_CONTINUE_IF(extends); + + if (sourcemeta::core::is_empty_schema(*extends)) { + return true; + } + + if (extends->is_array() && !extends->empty()) { + return std::ranges::any_of(extends->as_array(), + sourcemeta::core::is_empty_schema); + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + const auto &extends{schema.at("extends")}; + if (sourcemeta::core::is_empty_schema(extends)) { + schema.erase("extends"); + return; + } + + auto new_extends{JSON::make_array()}; + for (const auto &entry : extends.as_array()) { + if (!sourcemeta::core::is_empty_schema(entry)) { + new_extends.push_back(entry); + } + } + + if (new_extends.empty()) { + schema.erase("extends"); + } else { + schema.assign("extends", std::move(new_extends)); + } + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/draft3_type_any.h b/vendor/blaze/src/alterschema/canonicalizer/draft3_type_any.h new file mode 100644 index 000000000..dea04abec --- /dev/null +++ b/vendor/blaze/src/alterschema/canonicalizer/draft3_type_any.h @@ -0,0 +1,53 @@ +class Draft3TypeAny final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + Draft3TypeAny() : SchemaTransformRule{"draft3_type_any", ""} {}; + + [[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()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type); + + if (type->is_string()) { + return type->to_string() == "any"; + } + + if (type->is_array()) { + for (const auto &element : type->as_array()) { + if (element.is_string() && element.to_string() == "any") { + return true; + } + if (element.is_object()) { + if (element.empty()) { + return true; + } + if (element.size() == 1) { + const auto *element_type{element.try_at("type")}; + if (element_type && element_type->is_string() && + element_type->to_string() == "any") { + return true; + } + } + } + } + } + + return false; + } + + auto transform(JSON &schema, const Result &) const -> void override { + schema.erase("type"); + } +}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_definitions_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_definitions_drop.h index a06b42e58..cf79d81d5 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/empty_definitions_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_definitions_drop.h @@ -17,9 +17,11 @@ class EmptyDefinitionsDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *definitions{schema.try_at("definitions")}; + ONLY_CONTINUE_IF(definitions && definitions->is_object() && + definitions->empty()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_defs_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_defs_drop.h index 1a3cbcd65..e82bf0970 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/empty_defs_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_defs_drop.h @@ -16,9 +16,10 @@ class EmptyDefsDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *defs{schema.try_at("$defs")}; + ONLY_CONTINUE_IF(defs && defs->is_object() && defs->empty()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_dependencies_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_dependencies_drop.h index e9b838e1d..cb3b7c4e3 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/empty_dependencies_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_dependencies_drop.h @@ -19,9 +19,11 @@ class EmptyDependenciesDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *dependencies{schema.try_at("dependencies")}; + ONLY_CONTINUE_IF(dependencies && dependencies->is_object() && + dependencies->empty()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_required_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_required_drop.h index 3aa03ecd2..1753be269 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_required_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_required_drop.h @@ -18,9 +18,11 @@ class EmptyDependentRequiredDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *dependent_required{schema.try_at("dependentRequired")}; + ONLY_CONTINUE_IF(dependent_required && dependent_required->is_object() && + dependent_required->empty()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_schemas_drop.h b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_schemas_drop.h index e6d0b4d6e..54e3c8aec 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_schemas_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/empty_dependent_schemas_drop.h @@ -18,9 +18,11 @@ class EmptyDependentSchemasDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *dependent_schemas{schema.try_at("dependentSchemas")}; + ONLY_CONTINUE_IF(dependent_schemas && dependent_schemas->is_object() && + dependent_schemas->empty()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/enum_drop_redundant_validation.h b/vendor/blaze/src/alterschema/canonicalizer/enum_drop_redundant_validation.h index 42e16efa4..27e09a00f 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/enum_drop_redundant_validation.h +++ b/vendor/blaze/src/alterschema/canonicalizer/enum_drop_redundant_validation.h @@ -25,8 +25,10 @@ class EnumDropRedundantValidation final : public SchemaTransformRule { 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")); + schema.is_object() && !schema.defines("type")); + + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array()); this->keywords_.clear(); this->wrap_keywords_.clear(); diff --git a/vendor/blaze/src/alterschema/canonicalizer/enum_filter_by_type.h b/vendor/blaze/src/alterschema/canonicalizer/enum_filter_by_type.h index 925bef83d..78f90299a 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/enum_filter_by_type.h +++ b/vendor/blaze/src/alterschema/canonicalizer/enum_filter_by_type.h @@ -24,11 +24,16 @@ class EnumFilterByType final : public SchemaTransformRule { 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()); + schema.is_object()); - const auto declared_types{parse_schema_type(schema.at("type"))}; + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string()); + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array() && + !enum_value->empty()); + + const auto declared_types{parse_schema_type(*type)}; + ONLY_CONTINUE_IF(declared_types.any()); const bool integer_matches_integral{ vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_Draft_6, @@ -40,7 +45,7 @@ class EnumFilterByType final : public SchemaTransformRule { this->matching_indices_.clear(); bool has_mismatch{false}; std::size_t index{0}; - for (const auto &value : schema.at("enum").as_array()) { + for (const auto &value : enum_value->as_array()) { const bool matches{ declared_types.test(std::to_underlying(value.type())) || (integer_matches_integral && value.is_integral())}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/exclusive_bounds_false_drop.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_bounds_false_drop.h index 1d6c08056..b9162da66 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/exclusive_bounds_false_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_bounds_false_drop.h @@ -17,17 +17,19 @@ class ExclusiveBoundsFalseDrop final : public SchemaTransformRule { 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")); + schema.is_object()); - 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(); + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF( + type && type->is_string() && + (type->to_string() == "integer" || type->to_string() == "number")); + + const auto *exclusive_min{schema.try_at("exclusiveMinimum")}; + this->has_exclusive_min_ = exclusive_min && exclusive_min->is_boolean() && + !exclusive_min->to_boolean(); + const auto *exclusive_max{schema.try_at("exclusiveMaximum")}; + this->has_exclusive_max_ = exclusive_max && exclusive_max->is_boolean() && + !exclusive_max->to_boolean(); ONLY_CONTINUE_IF(this->has_exclusive_min_ || this->has_exclusive_max_); return true; 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 index af14629bc..6ceaeb596 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_boolean_integer_fold.h +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_boolean_integer_fold.h @@ -17,13 +17,16 @@ class ExclusiveMaximumBooleanIntegerFold final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *exclusive_maximum{schema.try_at("exclusiveMaximum")}; + ONLY_CONTINUE_IF(exclusive_maximum && exclusive_maximum->is_boolean() && + exclusive_maximum->to_boolean()); + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h index 95d2c8ed1..9bb3d23b2 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_maximum_integer_to_maximum.h @@ -22,12 +22,13 @@ class ExclusiveMaximumIntegerToMaximum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "integer" && - schema.defines("exclusiveMaximum") && - schema.at("exclusiveMaximum").is_number() && - !schema.defines("maximum")); + schema.is_object() && !schema.defines("maximum")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *exclusive_maximum{schema.try_at("exclusiveMaximum")}; + ONLY_CONTINUE_IF(exclusive_maximum && exclusive_maximum->is_number()); return APPLIES_TO_KEYWORDS("exclusiveMaximum", "type"); } 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 index ceb0d6f74..ee6b7abb7 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_boolean_integer_fold.h +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_boolean_integer_fold.h @@ -17,13 +17,16 @@ class ExclusiveMinimumBooleanIntegerFold final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *exclusive_minimum{schema.try_at("exclusiveMinimum")}; + ONLY_CONTINUE_IF(exclusive_minimum && exclusive_minimum->is_boolean() && + exclusive_minimum->to_boolean()); + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h b/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h index a86232c05..6406122df 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/exclusive_minimum_integer_to_minimum.h @@ -22,12 +22,13 @@ class ExclusiveMinimumIntegerToMinimum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "integer" && - schema.defines("exclusiveMinimum") && - schema.at("exclusiveMinimum").is_number() && - !schema.defines("minimum")); + schema.is_object() && !schema.defines("minimum")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *exclusive_minimum{schema.try_at("exclusiveMinimum")}; + ONLY_CONTINUE_IF(exclusive_minimum && exclusive_minimum->is_number()); return APPLIES_TO_KEYWORDS("exclusiveMinimum", "type"); } diff --git a/vendor/blaze/src/alterschema/canonicalizer/extends_to_array.h b/vendor/blaze/src/alterschema/canonicalizer/extends_to_array.h index 976d2216d..1269debbf 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/extends_to_array.h +++ b/vendor/blaze/src/alterschema/canonicalizer/extends_to_array.h @@ -18,8 +18,10 @@ class ExtendsToArray final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *extends{schema.try_at("extends")}; + ONLY_CONTINUE_IF(extends && !extends->is_array()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/implicit_contains_keywords.h b/vendor/blaze/src/alterschema/canonicalizer/implicit_contains_keywords.h index 39a9cb942..114676786 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/implicit_contains_keywords.h +++ b/vendor/blaze/src/alterschema/canonicalizer/implicit_contains_keywords.h @@ -18,9 +18,10 @@ class ImplicitContainsKeywords final : public SchemaTransformRule { 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"); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && type->to_string() == "array"); if (schema.defines("contains")) { ONLY_CONTINUE_IF(!schema.defines("minContains")); diff --git a/vendor/blaze/src/alterschema/canonicalizer/implicit_object_keywords.h b/vendor/blaze/src/alterschema/canonicalizer/implicit_object_keywords.h index d002902cb..ca0a24cfd 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/implicit_object_keywords.h +++ b/vendor/blaze/src/alterschema/canonicalizer/implicit_object_keywords.h @@ -14,10 +14,12 @@ class ImplicitObjectKeywords final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> SchemaTransformRule::Result override { - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("type") && - schema.at("type").is_string()); + ONLY_CONTINUE_IF(schema.is_object()); - const auto &type_value{schema.at("type").to_string()}; + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string()); + + const auto &type_value{type->to_string()}; this->reset(); if (type_value == "object") { diff --git a/vendor/blaze/src/alterschema/canonicalizer/inline_single_use_ref.h b/vendor/blaze/src/alterschema/canonicalizer/inline_single_use_ref.h index 37c256dd3..635b8160d 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/inline_single_use_ref.h +++ b/vendor/blaze/src/alterschema/canonicalizer/inline_single_use_ref.h @@ -13,8 +13,10 @@ class InlineSingleUseRef final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> SchemaTransformRule::Result override { - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$ref") && - schema.at("$ref").is_string() && schema.size() == 1); + ONLY_CONTINUE_IF(schema.is_object() && schema.size() == 1); + + const auto *ref{schema.try_at("$ref")}; + ONLY_CONTINUE_IF(ref && ref->is_string()); if (!location.parent.has_value()) { return false; @@ -26,17 +28,16 @@ class InlineSingleUseRef final : public SchemaTransformRule { relative.at(0).to_property() == "allOf" && relative.size() >= 2 && relative.at(1).is_index()); const auto &parent_schema{sourcemeta::core::get(root, parent_pointer)}; - ONLY_CONTINUE_IF(parent_schema.is_object() && - parent_schema.defines("allOf") && - parent_schema.at("allOf").is_array()); + ONLY_CONTINUE_IF(parent_schema.is_object()); + const auto *parent_all_of{parent_schema.try_at("allOf")}; + ONLY_CONTINUE_IF(parent_all_of && parent_all_of->is_array()); const auto current_index{relative.at(1).to_index()}; bool has_typed_sibling{false}; - for (std::size_t index = 0; index < parent_schema.at("allOf").size(); - ++index) { + for (std::size_t index = 0; index < parent_all_of->size(); ++index) { if (index == current_index) { continue; } - const auto &sibling{parent_schema.at("allOf").at(index)}; + const auto &sibling{parent_all_of->at(index)}; if (sibling.is_object() && (sibling.defines("type") || sibling.defines("enum"))) { has_typed_sibling = true; @@ -53,7 +54,7 @@ class InlineSingleUseRef final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4})); - const auto target{frame.traverse(schema.at("$ref").to_string())}; + const auto target{frame.traverse(ref->to_string())}; ONLY_CONTINUE_IF(target.has_value()); const auto &target_pointer{target->get().pointer}; diff --git a/vendor/blaze/src/alterschema/canonicalizer/items_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/items_implicit.h index a3068a342..acf61d787 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/items_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/items_implicit.h @@ -28,9 +28,10 @@ class ItemsImplicit final : public SchemaTransformRule { vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6})) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "array" && !schema.defines("items")); + schema.is_object() && !schema.defines("items")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && type->to_string() == "array"); ONLY_CONTINUE_IF( !(schema.defines("unevaluatedItems") && vocabularies.contains_any( diff --git a/vendor/blaze/src/alterschema/canonicalizer/max_contains_covered_by_max_items.h b/vendor/blaze/src/alterschema/canonicalizer/max_contains_covered_by_max_items.h index c7da52033..c80cf1fa9 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/max_contains_covered_by_max_items.h +++ b/vendor/blaze/src/alterschema/canonicalizer/max_contains_covered_by_max_items.h @@ -22,11 +22,13 @@ class MaxContainsCoveredByMaxItems final : public SchemaTransformRule { vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation}) && - schema.is_object() && schema.defines("maxContains") && - schema.at("maxContains").is_integer() && schema.defines("maxItems") && - schema.at("maxItems").is_integer() && - schema.at("maxContains").to_integer() > - schema.at("maxItems").to_integer()); + schema.is_object()); + + const auto *max_contains{schema.try_at("maxContains")}; + ONLY_CONTINUE_IF(max_contains && max_contains->is_integer()); + const auto *max_items{schema.try_at("maxItems")}; + ONLY_CONTINUE_IF(max_items && max_items->is_integer() && + max_contains->to_integer() > max_items->to_integer()); return APPLIES_TO_KEYWORDS("maxContains", "maxItems"); } diff --git a/vendor/blaze/src/alterschema/canonicalizer/max_decimal_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/max_decimal_implicit.h index 05375e37d..a4cf397b1 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/max_decimal_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/max_decimal_implicit.h @@ -16,10 +16,11 @@ class MaxDecimalImplicit final : public SchemaTransformRule { 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")); + schema.is_object() && !schema.defines("maxDecimal")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); return true; } 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 index 0c2826ba8..1758a1d9c 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_integer_fold.h +++ b/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_integer_fold.h @@ -18,13 +18,16 @@ class MaximumCanEqualIntegerFold final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *maximum_can_equal{schema.try_at("maximumCanEqual")}; + ONLY_CONTINUE_IF(maximum_can_equal && maximum_can_equal->is_boolean() && + !maximum_can_equal->to_boolean()); + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number()); return true; } 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 index 3104fcb36..f5b2068a0 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_true_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/maximum_can_equal_true_drop.h @@ -18,13 +18,15 @@ class MaximumCanEqualTrueDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF( + type && type->is_string() && + (type->to_string() == "integer" || type->to_string() == "number")); + const auto *maximum_can_equal{schema.try_at("maximumCanEqual")}; + ONLY_CONTINUE_IF(maximum_can_equal && maximum_can_equal->is_boolean() && + maximum_can_equal->to_boolean()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/min_items_given_min_contains.h b/vendor/blaze/src/alterschema/canonicalizer/min_items_given_min_contains.h index 73d2c3413..4c8950599 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/min_items_given_min_contains.h +++ b/vendor/blaze/src/alterschema/canonicalizer/min_items_given_min_contains.h @@ -21,10 +21,10 @@ class MinItemsGivenMinContains final : public SchemaTransformRule { vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - schema.at("type").to_string() == "array" && - !schema.defines("minItems")); + schema.is_object() && !schema.defines("minItems")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && type->to_string() == "array"); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/min_length_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/min_length_implicit.h index 8f16f8f5d..91932fb99 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/min_length_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/min_length_implicit.h @@ -26,10 +26,11 @@ class MinLengthImplicit final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2, 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" && - !schema.defines("minLength")); + schema.is_object() && !schema.defines("minLength")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "string"); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/min_properties_covered_by_required.h b/vendor/blaze/src/alterschema/canonicalizer/min_properties_covered_by_required.h index 2f1cb3063..532162b77 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/min_properties_covered_by_required.h +++ b/vendor/blaze/src/alterschema/canonicalizer/min_properties_covered_by_required.h @@ -17,19 +17,21 @@ class MinPropertiesCoveredByRequired final : public SchemaTransformRule { 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, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_4}) && + schema.is_object()); + + const auto *min_properties{schema.try_at("minProperties")}; + ONLY_CONTINUE_IF(min_properties && min_properties->is_integer()); + const auto *required{schema.try_at("required")}; 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}) && - schema.is_object() && schema.defines("minProperties") && - schema.at("minProperties").is_integer() && schema.defines("required") && - schema.at("required").is_array() && schema.at("required").unique() && - std::cmp_greater(schema.at("required").size(), - static_cast( - schema.at("minProperties").to_integer()))); + required && required->is_array() && required->unique() && + std::cmp_greater(required->size(), static_cast( + min_properties->to_integer()))); return APPLIES_TO_KEYWORDS("minProperties", "required"); } 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 index 3ea2bab3c..216d24721 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_integer_fold.h +++ b/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_integer_fold.h @@ -18,13 +18,16 @@ class MinimumCanEqualIntegerFold final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *minimum_can_equal{schema.try_at("minimumCanEqual")}; + ONLY_CONTINUE_IF(minimum_can_equal && minimum_can_equal->is_boolean() && + !minimum_can_equal->to_boolean()); + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number()); return true; } 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 index 32c775a48..0bd3c2e08 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_true_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/minimum_can_equal_true_drop.h @@ -18,13 +18,15 @@ class MinimumCanEqualTrueDrop final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF( + type && type->is_string() && + (type->to_string() == "integer" || type->to_string() == "number")); + const auto *minimum_can_equal{schema.try_at("minimumCanEqual")}; + ONLY_CONTINUE_IF(minimum_can_equal && minimum_can_equal->is_boolean() && + minimum_can_equal->to_boolean()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/multiple_of_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/multiple_of_implicit.h index 330578be0..934f21127 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/multiple_of_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/multiple_of_implicit.h @@ -21,11 +21,12 @@ class MultipleOfImplicit final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_string() && - // Applying this to numbers would be a semantic problem - schema.at("type").to_string() == "integer" && - !schema.defines("multipleOf")); + schema.is_object() && !schema.defines("multipleOf")); + + const auto *type{schema.try_at("type")}; + // Applying this to numbers would be a semantic problem + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/optional_property_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/optional_property_implicit.h index c3807414a..91b22f7ec 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/optional_property_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/optional_property_implicit.h @@ -18,12 +18,15 @@ class OptionalPropertyImplicit final : public SchemaTransformRule { 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()); + schema.is_object()); - for (const auto &entry : schema.at("properties").as_object()) { + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "object"); + const auto *properties{schema.try_at("properties")}; + ONLY_CONTINUE_IF(properties && properties->is_object()); + + for (const auto &entry : properties->as_object()) { if (entry.second.is_object() && !entry.second.empty() && !entry.second.defines("optional")) { return 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 index a973fcf0b..ba2b12c3f 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/recursive_anchor_false_drop.h +++ b/vendor/blaze/src/alterschema/canonicalizer/recursive_anchor_false_drop.h @@ -16,9 +16,11 @@ class RecursiveAnchorFalseDrop final : public SchemaTransformRule { -> 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()); + schema.is_object()); + + const auto *recursive_anchor{schema.try_at("$recursiveAnchor")}; + ONLY_CONTINUE_IF(recursive_anchor && recursive_anchor->is_boolean() && + !recursive_anchor->to_boolean()); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/required_property_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/required_property_implicit.h index 862912d3e..520c53160 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/required_property_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/required_property_implicit.h @@ -16,12 +16,15 @@ class RequiredPropertyImplicit final : public SchemaTransformRule { -> 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()); + schema.is_object()); - for (const auto &entry : schema.at("properties").as_object()) { + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "object"); + const auto *properties{schema.try_at("properties")}; + ONLY_CONTINUE_IF(properties && properties->is_object()); + + for (const auto &entry : properties->as_object()) { if (entry.second.is_object() && !entry.second.empty() && !entry.second.defines("$ref") && !entry.second.defines("required")) { return true; diff --git a/vendor/blaze/src/alterschema/canonicalizer/single_branch_allof.h b/vendor/blaze/src/alterschema/canonicalizer/single_branch_allof.h index bb431302f..4745cb7ca 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/single_branch_allof.h +++ b/vendor/blaze/src/alterschema/canonicalizer/single_branch_allof.h @@ -20,9 +20,10 @@ class SingleBranchAllOf final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines(KEYWORD) && - schema.at(KEYWORD).is_array() && - schema.at(KEYWORD).size() == 1); + schema.is_object()); + + const auto *all_of{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(all_of && all_of->is_array() && all_of->size() == 1); ONLY_CONTINUE_IF( !(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, @@ -31,7 +32,7 @@ class SingleBranchAllOf final : public SchemaTransformRule { schema.defines("unevaluatedItems")))); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); - const auto &branch{schema.at(KEYWORD).at(0)}; + const auto &branch{all_of->at(0)}; if (branch.is_object()) { ONLY_CONTINUE_IF(!branch.defines("$ref") && !branch.defines("$dynamicRef") && diff --git a/vendor/blaze/src/alterschema/canonicalizer/single_branch_anyof.h b/vendor/blaze/src/alterschema/canonicalizer/single_branch_anyof.h index a7862d08f..e3fc7dedf 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/single_branch_anyof.h +++ b/vendor/blaze/src/alterschema/canonicalizer/single_branch_anyof.h @@ -20,9 +20,10 @@ class SingleBranchAnyOf final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines(KEYWORD) && - schema.at(KEYWORD).is_array() && - schema.at(KEYWORD).size() == 1); + schema.is_object()); + + const auto *any_of{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(any_of && any_of->is_array() && any_of->size() == 1); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); this->has_unevaluated_ = diff --git a/vendor/blaze/src/alterschema/canonicalizer/single_branch_oneof.h b/vendor/blaze/src/alterschema/canonicalizer/single_branch_oneof.h index 158501f3a..93627c169 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/single_branch_oneof.h +++ b/vendor/blaze/src/alterschema/canonicalizer/single_branch_oneof.h @@ -20,9 +20,10 @@ class SingleBranchOneOf final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines(KEYWORD) && - schema.at(KEYWORD).is_array() && - schema.at(KEYWORD).size() == 1); + schema.is_object()); + + const auto *one_of{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(one_of && one_of->is_array() && one_of->size() == 1); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); this->has_unevaluated_ = diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_array_to_any_of.h b/vendor/blaze/src/alterschema/canonicalizer/type_array_to_any_of.h index 6155618d5..c17b91cfb 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_array_to_any_of.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_array_to_any_of.h @@ -26,8 +26,10 @@ class TypeArrayToAnyOf final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_array()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_array()); this->keyword_instances_.clear(); diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_boolean_as_enum.h b/vendor/blaze/src/alterschema/canonicalizer/type_boolean_as_enum.h index f1c21b005..9909d78ef 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_boolean_as_enum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_boolean_as_enum.h @@ -27,10 +27,12 @@ class TypeBooleanAsEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2, 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" && - !schema.defines("enum") && !schema.defines("const")); + schema.is_object() && !schema.defines("enum") && + !schema.defines("const")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "boolean"); return APPLIES_TO_KEYWORDS("type"); } diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_inherit_in_place.h b/vendor/blaze/src/alterschema/canonicalizer/type_inherit_in_place.h index 46238697a..1a7612eff 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_inherit_in_place.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_inherit_in_place.h @@ -60,7 +60,15 @@ class TypeInheritInPlace final : public SchemaTransformRule { })}; if (ancestor.has_value()) { - this->inherited_type_ = get(root, ancestor.value().get()).at("type"); + const auto &ancestor_type{get(root, ancestor.value().get()).at("type")}; + if (ancestor_type.is_array()) { + for (const auto &element : ancestor_type.as_array()) { + if (!element.is_string()) { + return false; + } + } + } + this->inherited_type_ = ancestor_type; return true; } @@ -90,42 +98,46 @@ class TypeInheritInPlace final : public SchemaTransformRule { const auto branch_index{walk_relative.at(1).to_index()}; const auto &allof_parent{get(root, wp)}; const auto &keyword_name{walk_relative.at(0).to_property()}; - if (allof_parent.is_object() && allof_parent.defines(keyword_name)) { - const auto &branches{allof_parent.at(keyword_name)}; - if (branches.is_array()) { - for (std::size_t index = 0; index < branches.size(); ++index) { - if (index == branch_index) { - continue; - } - const auto &sibling{branches.at(index)}; - if (sibling.is_object() && sibling.defines("type") && - sibling.at("type").is_string()) { - this->inherited_type_ = sibling.at("type"); + const auto *branches{allof_parent.is_object() + ? allof_parent.try_at(keyword_name) + : nullptr}; + if (branches && branches->is_array()) { + for (std::size_t index = 0; index < branches->size(); ++index) { + if (index == branch_index) { + continue; + } + const auto &sibling{branches->at(index)}; + if (!sibling.is_object()) { + continue; + } + const auto *sibling_type{sibling.try_at("type")}; + if (sibling_type && sibling_type->is_string()) { + this->inherited_type_ = *sibling_type; + return true; + } + const auto *sibling_enum{sibling.try_at("enum")}; + if (sibling_enum && sibling_enum->is_array() && + !sibling_enum->empty()) { + const auto inferred{infer_type_from_enum(*sibling_enum)}; + if (!inferred.empty()) { + this->inherited_type_ = JSON{inferred}; return true; } - if (sibling.is_object() && sibling.defines("enum") && - sibling.at("enum").is_array() && - !sibling.at("enum").empty()) { - const auto inferred{infer_type_from_enum(sibling.at("enum"))}; - if (!inferred.empty()) { - this->inherited_type_ = JSON{inferred}; + } + const auto *sibling_ref{sibling.try_at("$ref")}; + if (sibling_ref && sibling_ref->is_string()) { + const auto ref_target{frame.traverse(sibling_ref->to_string())}; + if (ref_target.has_value()) { + const auto &ref_schema{ + get(root, ref_target.value().get().pointer)}; + const auto *ref_type{ref_schema.is_object() + ? ref_schema.try_at("type") + : nullptr}; + if (ref_type && ref_type->is_string()) { + this->inherited_type_ = *ref_type; return true; } } - if (sibling.is_object() && sibling.defines("$ref") && - sibling.at("$ref").is_string()) { - const auto ref_target{ - frame.traverse(sibling.at("$ref").to_string())}; - if (ref_target.has_value()) { - const auto &ref_schema{ - get(root, ref_target.value().get().pointer)}; - if (ref_schema.is_object() && ref_schema.defines("type") && - ref_schema.at("type").is_string()) { - this->inherited_type_ = ref_schema.at("type"); - return true; - } - } - } } } } diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_null_as_enum.h b/vendor/blaze/src/alterschema/canonicalizer/type_null_as_enum.h index 93b6af341..47c0998b4 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_null_as_enum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_null_as_enum.h @@ -27,10 +27,11 @@ class TypeNullAsEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2, 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" && - !schema.defines("enum") && !schema.defines("const")); + schema.is_object() && !schema.defines("enum") && + !schema.defines("const")); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && type->to_string() == "null"); return APPLIES_TO_KEYWORDS("type"); } diff --git a/vendor/blaze/src/alterschema/canonicalizer/type_union_implicit.h b/vendor/blaze/src/alterschema/canonicalizer/type_union_implicit.h index 63eca4223..b2d13af3a 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_union_implicit.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_union_implicit.h @@ -109,14 +109,15 @@ class TypeUnionImplicit final : public SchemaTransformRule { const auto branch_index{walk_relative.at(1).to_index()}; const auto &allof_parent{get(root, wp)}; const auto &keyword_name{walk_relative.at(0).to_property()}; - if (allof_parent.is_object() && allof_parent.defines(keyword_name) && - allof_parent.at(keyword_name).is_array()) { - const auto &branches{allof_parent.at(keyword_name)}; - for (std::size_t index = 0; index < branches.size(); ++index) { + const auto *branches{allof_parent.is_object() + ? allof_parent.try_at(keyword_name) + : nullptr}; + if (branches && branches->is_array()) { + for (std::size_t index = 0; index < branches->size(); ++index) { if (index == branch_index) { continue; } - const auto &sibling{branches.at(index)}; + const auto &sibling{branches->at(index)}; if (!sibling.is_object()) { continue; } @@ -125,8 +126,9 @@ class TypeUnionImplicit final : public SchemaTransformRule { return true; } - if (sibling.defines("enum") && sibling.at("enum").is_array() && - !sibling.at("enum").empty()) { + const auto *sibling_enum{sibling.try_at("enum")}; + if (sibling_enum && sibling_enum->is_array() && + !sibling_enum->empty()) { 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 index 086c34999..5a488516b 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_union_to_schemas.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_union_to_schemas.h @@ -18,10 +18,12 @@ class TypeUnionToSchemas final : public SchemaTransformRule { 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()); + schema.is_object()); - for (const auto &element : schema.at("type").as_array()) { + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_array()); + + for (const auto &element : type->as_array()) { if (element.is_string()) { return true; } 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 index 53d7a4cd7..4022ae9e3 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_allof.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_allof.h @@ -34,8 +34,8 @@ class TypeWithApplicatorToAllOf final : public SchemaTransformRule { 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 auto *type_value{schema.try_at("type")}; + const bool has_type{type_value && type_value->is_string()}; const bool has_enum{schema.defines("enum")}; const bool is_modern{ vocabularies.contains(Vocabularies::Known::JSON_Schema_2019_09_Core) || 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 index c4a11bd88..5d4ee3696 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_extends.h +++ b/vendor/blaze/src/alterschema/canonicalizer/type_with_applicator_to_extends.h @@ -21,14 +21,13 @@ class TypeWithApplicatorToExtends final : public SchemaTransformRule { 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 auto *extends_value{schema.try_at("extends")}; + const bool has_extends{extends_value && extends_value->is_array()}; + const auto *disallow_value{schema.try_at("disallow")}; + const bool has_disallow{disallow_value && disallow_value->is_array()}; + const auto *type_value{schema.try_at("type")}; + const bool has_type_array{type_value && type_value->is_array()}; + const bool has_type{type_value && type_value->is_string()}; const bool has_enum{schema.defines("enum")}; const unsigned int applicator_count{(has_extends ? 1U : 0U) + (has_disallow ? 1U : 0U) + diff --git a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_can_equal_bounds.h b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_can_equal_bounds.h index b39ca0d8e..0b537c240 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_can_equal_bounds.h +++ b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_can_equal_bounds.h @@ -16,20 +16,25 @@ class UnsatisfiableCanEqualBounds final : public SchemaTransformRule { -> 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")); + schema.is_object()); - 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()}; + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF( + type && type->is_string() && + (type->to_string() == "number" || type->to_string() == "integer")); + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number()); + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number() && *minimum == *maximum); + + const auto *minimum_can_equal{schema.try_at("minimumCanEqual")}; + const bool min_exclusive{minimum_can_equal && + minimum_can_equal->is_boolean() && + !minimum_can_equal->to_boolean()}; + const auto *maximum_can_equal{schema.try_at("maximumCanEqual")}; + const bool max_exclusive{maximum_can_equal && + maximum_can_equal->is_boolean() && + !maximum_can_equal->to_boolean()}; ONLY_CONTINUE_IF(min_exclusive || max_exclusive); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_exclusive_equal_bounds.h b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_exclusive_equal_bounds.h index 6da4b2dfb..4cd4e33a9 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_exclusive_equal_bounds.h +++ b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_exclusive_equal_bounds.h @@ -17,20 +17,25 @@ class UnsatisfiableExclusiveEqualBounds final : public SchemaTransformRule { 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")); + schema.is_object()); - 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()}; + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF( + type && type->is_string() && + (type->to_string() == "number" || type->to_string() == "integer")); + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number()); + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number() && *minimum == *maximum); + + const auto *exclusive_minimum{schema.try_at("exclusiveMinimum")}; + const bool exclusive_min{exclusive_minimum && + exclusive_minimum->is_boolean() && + exclusive_minimum->to_boolean()}; + const auto *exclusive_maximum{schema.try_at("exclusiveMaximum")}; + const bool exclusive_max{exclusive_maximum && + exclusive_maximum->is_boolean() && + exclusive_maximum->to_boolean()}; ONLY_CONTINUE_IF(exclusive_min || exclusive_max); return true; } diff --git a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_type_and_enum.h b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_type_and_enum.h index 1c6168540..d7990b0e3 100644 --- a/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_type_and_enum.h +++ b/vendor/blaze/src/alterschema/canonicalizer/unsatisfiable_type_and_enum.h @@ -25,11 +25,16 @@ class UnsatisfiableTypeAndEnum final : public SchemaTransformRule { 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()); + schema.is_object()); - const auto declared_types{parse_schema_type(schema.at("type"))}; + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string()); + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array() && + !enum_value->empty()); + + const auto declared_types{parse_schema_type(*type)}; + ONLY_CONTINUE_IF(declared_types.any()); const bool integer_matches_integral{ vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_Draft_6, @@ -38,7 +43,7 @@ class UnsatisfiableTypeAndEnum final : public SchemaTransformRule { 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(), + enum_value->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()); diff --git a/vendor/blaze/src/alterschema/common/allof_false_simplify.h b/vendor/blaze/src/alterschema/common/allof_false_simplify.h index 3abef5b39..935100201 100644 --- a/vendor/blaze/src/alterschema/common/allof_false_simplify.h +++ b/vendor/blaze/src/alterschema/common/allof_false_simplify.h @@ -22,12 +22,13 @@ class AllOfFalseSimplify final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Applicator, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines(KEYWORD) && - !schema.defines("not") && schema.at(KEYWORD).is_array()); + schema.is_object() && !schema.defines("not")); - const auto &allof{schema.at(KEYWORD)}; - for (std::size_t index = 0; index < allof.size(); ++index) { - const auto &entry{allof.at(index)}; + const auto *all_of{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(all_of && all_of->is_array()); + + for (std::size_t index = 0; index < all_of->size(); ++index) { + const auto &entry{all_of->at(index)}; if (entry.is_boolean() && !entry.to_boolean()) { ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); diff --git a/vendor/blaze/src/alterschema/common/anyof_false_simplify.h b/vendor/blaze/src/alterschema/common/anyof_false_simplify.h index 387f3086a..7069a38bf 100644 --- a/vendor/blaze/src/alterschema/common/anyof_false_simplify.h +++ b/vendor/blaze/src/alterschema/common/anyof_false_simplify.h @@ -22,11 +22,12 @@ class AnyOfFalseSimplify final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Applicator, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines(KEYWORD) && - !schema.defines("not") && schema.at(KEYWORD).is_array() && - schema.at(KEYWORD).size() == 1); + schema.is_object() && !schema.defines("not")); - const auto &entry{schema.at(KEYWORD).front()}; + const auto *any_of{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(any_of && any_of->is_array() && any_of->size() == 1); + + const auto &entry{any_of->front()}; ONLY_CONTINUE_IF(entry.is_boolean() && !entry.to_boolean()); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); diff --git a/vendor/blaze/src/alterschema/common/const_in_enum.h b/vendor/blaze/src/alterschema/common/const_in_enum.h index 7bf39ea50..fed367cf5 100644 --- a/vendor/blaze/src/alterschema/common/const_in_enum.h +++ b/vendor/blaze/src/alterschema/common/const_in_enum.h @@ -22,9 +22,13 @@ class ConstInEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("const") && - schema.defines("enum") && schema.at("enum").is_array() && - schema.at("enum").contains(schema.at("const"))); + schema.is_object()); + + const auto *const_value{schema.try_at("const")}; + ONLY_CONTINUE_IF(const_value); + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array() && + enum_value->contains(*const_value)); return APPLIES_TO_KEYWORDS("const", "enum"); } diff --git a/vendor/blaze/src/alterschema/common/const_with_type.h b/vendor/blaze/src/alterschema/common/const_with_type.h index 025444119..f7031330b 100644 --- a/vendor/blaze/src/alterschema/common/const_with_type.h +++ b/vendor/blaze/src/alterschema/common/const_with_type.h @@ -22,12 +22,17 @@ class ConstWithType final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("type") && - schema.defines("const")); + schema.is_object()); - const auto current_types{parse_schema_type(schema.at("type"))}; + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type); + const auto *const_value{schema.try_at("const")}; + ONLY_CONTINUE_IF(const_value); + + const auto current_types{parse_schema_type(*type)}; + ONLY_CONTINUE_IF(current_types.any()); ONLY_CONTINUE_IF( - current_types.test(std::to_underlying(schema.at("const").type()))); + current_types.test(std::to_underlying(const_value->type()))); return APPLIES_TO_KEYWORDS("const", "type"); } diff --git a/vendor/blaze/src/alterschema/common/dependencies_property_tautology.h b/vendor/blaze/src/alterschema/common/dependencies_property_tautology.h index e8a6f06b7..44eb47755 100644 --- a/vendor/blaze/src/alterschema/common/dependencies_property_tautology.h +++ b/vendor/blaze/src/alterschema/common/dependencies_property_tautology.h @@ -23,19 +23,20 @@ class DependenciesPropertyTautology final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3}) && - schema.is_object() && schema.defines("dependencies") && - schema.at("dependencies").is_object() && schema.defines("required") && - schema.at("required").is_array()); + schema.is_object()); + + const auto *dependencies{schema.try_at("dependencies")}; + ONLY_CONTINUE_IF(dependencies && dependencies->is_object()); + const auto *required{schema.try_at("required")}; + ONLY_CONTINUE_IF(required && required->is_array()); + ONLY_CONTINUE_IF(std::ranges::any_of( - schema.at("required").as_array(), [&schema](const auto &element) { - return element.is_string() && - schema.at("dependencies").defines(element.to_string()) && - (schema.at("dependencies") - .at(element.to_string()) - .is_array() || - schema.at("dependencies") - .at(element.to_string()) - .is_string()); + required->as_array(), [dependencies](const auto &element) { + if (!element.is_string()) { + return false; + } + const auto *dependent{dependencies->try_at(element.to_string())}; + return dependent && (dependent->is_array() || dependent->is_string()); })); return APPLIES_TO_KEYWORDS("dependencies", "required"); } diff --git a/vendor/blaze/src/alterschema/common/dependent_required_tautology.h b/vendor/blaze/src/alterschema/common/dependent_required_tautology.h index 5aff7a3b9..1740b8dae 100644 --- a/vendor/blaze/src/alterschema/common/dependent_required_tautology.h +++ b/vendor/blaze/src/alterschema/common/dependent_required_tautology.h @@ -22,16 +22,19 @@ class DependentRequiredTautology final : public SchemaTransformRule { vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation}) && - schema.is_object() && schema.defines("dependentRequired") && - schema.at("dependentRequired").is_object() && - schema.defines("required") && schema.at("required").is_array()); - ONLY_CONTINUE_IF(std::any_of( - schema.at("required").as_array().cbegin(), - schema.at("required").as_array().cend(), - [&schema](const auto &element) { - return element.is_string() && - schema.at("dependentRequired").defines(element.to_string()); - })); + schema.is_object()); + + const auto *dependent_required{schema.try_at("dependentRequired")}; + ONLY_CONTINUE_IF(dependent_required && dependent_required->is_object()); + const auto *required{schema.try_at("required")}; + ONLY_CONTINUE_IF(required && required->is_array()); + + ONLY_CONTINUE_IF( + std::any_of(required->as_array().cbegin(), required->as_array().cend(), + [dependent_required](const auto &element) { + return element.is_string() && + dependent_required->defines(element.to_string()); + })); return APPLIES_TO_KEYWORDS("dependentRequired", "required"); } diff --git a/vendor/blaze/src/alterschema/common/double_negation_elimination.h b/vendor/blaze/src/alterschema/common/double_negation_elimination.h index f00f2d104..50b86fb8e 100644 --- a/vendor/blaze/src/alterschema/common/double_negation_elimination.h +++ b/vendor/blaze/src/alterschema/common/double_negation_elimination.h @@ -24,12 +24,14 @@ class DoubleNegationElimination final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines(KEYWORD) && - schema.at(KEYWORD).is_object() && - schema.at(KEYWORD).size() == 1 && - schema.at(KEYWORD).defines(KEYWORD) && - !(schema.at(KEYWORD).at(KEYWORD).is_boolean() && - !schema.at(KEYWORD).at(KEYWORD).to_boolean())); + schema.is_object()); + + const auto *outer_not{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(outer_not && outer_not->is_object() && + outer_not->size() == 1); + const auto *inner_not{outer_not->try_at(KEYWORD)}; + ONLY_CONTINUE_IF(inner_not && + !(inner_not->is_boolean() && !inner_not->to_boolean())); ONLY_CONTINUE_IF( !(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Unevaluated, diff --git a/vendor/blaze/src/alterschema/common/draft_official_dialect_with_https.h b/vendor/blaze/src/alterschema/common/draft_official_dialect_with_https.h index 0f1540c0c..f3eb13a2d 100644 --- a/vendor/blaze/src/alterschema/common/draft_official_dialect_with_https.h +++ b/vendor/blaze/src/alterschema/common/draft_official_dialect_with_https.h @@ -30,9 +30,10 @@ class DraftOfficialDialectWithHttps final : public SchemaTransformRule { location.base_dialect == SchemaBaseDialect::JSON_Schema_Draft_2_Hyper || location.base_dialect == SchemaBaseDialect::JSON_Schema_Draft_1_Hyper || location.base_dialect == SchemaBaseDialect::JSON_Schema_Draft_0_Hyper); - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && - schema.at("$schema").is_string()); - const auto &dialect{schema.at("$schema").to_string()}; + ONLY_CONTINUE_IF(schema.is_object()); + const auto *schema_keyword{schema.try_at("$schema")}; + ONLY_CONTINUE_IF(schema_keyword && schema_keyword->is_string()); + const auto &dialect{schema_keyword->to_string()}; ONLY_CONTINUE_IF(dialect.starts_with("https://json-schema.org/")); ONLY_CONTINUE_IF( dialect == "https://json-schema.org/draft-07/schema" || diff --git a/vendor/blaze/src/alterschema/common/draft_official_dialect_without_empty_fragment.h b/vendor/blaze/src/alterschema/common/draft_official_dialect_without_empty_fragment.h index 22950766a..dab56c1f2 100644 --- a/vendor/blaze/src/alterschema/common/draft_official_dialect_without_empty_fragment.h +++ b/vendor/blaze/src/alterschema/common/draft_official_dialect_without_empty_fragment.h @@ -16,9 +16,10 @@ class DraftOfficialDialectWithoutEmptyFragment final const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> 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()}; + ONLY_CONTINUE_IF(schema.is_object()); + const auto *schema_keyword{schema.try_at("$schema")}; + ONLY_CONTINUE_IF(schema_keyword && schema_keyword->is_string()); + const auto &dialect{schema_keyword->to_string()}; ONLY_CONTINUE_IF( dialect == "http://json-schema.org/draft-07/schema" || dialect == "http://json-schema.org/draft-07/hyper-schema" || diff --git a/vendor/blaze/src/alterschema/common/drop_allof_empty_schemas.h b/vendor/blaze/src/alterschema/common/drop_allof_empty_schemas.h index 346844fa8..f256f03dd 100644 --- a/vendor/blaze/src/alterschema/common/drop_allof_empty_schemas.h +++ b/vendor/blaze/src/alterschema/common/drop_allof_empty_schemas.h @@ -18,11 +18,10 @@ class DropAllOfEmptySchemas final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4})); - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("allOf") && - schema.at("allOf").is_array() && - !schema.at("allOf").empty()); - ONLY_CONTINUE_IF( - std::ranges::any_of(schema.at("allOf").as_array(), is_empty_schema)); + ONLY_CONTINUE_IF(schema.is_object()); + const auto *all_of{schema.try_at("allOf")}; + ONLY_CONTINUE_IF(all_of && all_of->is_array() && !all_of->empty()); + ONLY_CONTINUE_IF(std::ranges::any_of(all_of->as_array(), is_empty_schema)); return APPLIES_TO_KEYWORDS("allOf"); } diff --git a/vendor/blaze/src/alterschema/common/duplicate_allof_branches.h b/vendor/blaze/src/alterschema/common/duplicate_allof_branches.h index da5921e0f..3ee911be0 100644 --- a/vendor/blaze/src/alterschema/common/duplicate_allof_branches.h +++ b/vendor/blaze/src/alterschema/common/duplicate_allof_branches.h @@ -25,9 +25,10 @@ class DuplicateAllOfBranches final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("allOf") && - schema.at("allOf").is_array() && - !schema.at("allOf").unique()); + schema.is_object()); + + const auto *all_of{schema.try_at("allOf")}; + ONLY_CONTINUE_IF(all_of && all_of->is_array() && !all_of->unique()); // TODO: Highlight which specific entries in `allOf` are duplicated return APPLIES_TO_KEYWORDS("allOf"); } diff --git a/vendor/blaze/src/alterschema/common/duplicate_anyof_branches.h b/vendor/blaze/src/alterschema/common/duplicate_anyof_branches.h index 3f0e5b4a9..678d47237 100644 --- a/vendor/blaze/src/alterschema/common/duplicate_anyof_branches.h +++ b/vendor/blaze/src/alterschema/common/duplicate_anyof_branches.h @@ -25,9 +25,10 @@ class DuplicateAnyOfBranches final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("anyOf") && - schema.at("anyOf").is_array() && - !schema.at("anyOf").unique()); + schema.is_object()); + + const auto *any_of{schema.try_at("anyOf")}; + ONLY_CONTINUE_IF(any_of && any_of->is_array() && !any_of->unique()); // TODO: Highlight which specific entries in `anyOf` are duplicated return APPLIES_TO_KEYWORDS("anyOf"); } diff --git a/vendor/blaze/src/alterschema/common/duplicate_enum_values.h b/vendor/blaze/src/alterschema/common/duplicate_enum_values.h index 7246b5575..a8bec6db4 100644 --- a/vendor/blaze/src/alterschema/common/duplicate_enum_values.h +++ b/vendor/blaze/src/alterschema/common/duplicate_enum_values.h @@ -25,9 +25,11 @@ class DuplicateEnumValues final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, Vocabularies::Known::JSON_Schema_Draft_1}) && - schema.is_object() && schema.defines("enum") && - schema.at("enum").is_array() && - !schema.at("enum").unique()); + schema.is_object()); + + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array() && + !enum_value->unique()); // TODO: Highlight which specific entries in `enum` are duplicated return APPLIES_TO_KEYWORDS("enum"); } diff --git a/vendor/blaze/src/alterschema/common/duplicate_required_values.h b/vendor/blaze/src/alterschema/common/duplicate_required_values.h index 6d1d7d07d..ecf7347f0 100644 --- a/vendor/blaze/src/alterschema/common/duplicate_required_values.h +++ b/vendor/blaze/src/alterschema/common/duplicate_required_values.h @@ -23,9 +23,10 @@ class DuplicateRequiredValues final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("required") && - schema.at("required").is_array() && - !schema.at("required").unique()); + schema.is_object()); + + const auto *required{schema.try_at("required")}; + ONLY_CONTINUE_IF(required && required->is_array() && !required->unique()); // TODO: Highlight which specific entries in `required` are duplicated return APPLIES_TO_KEYWORDS("required"); } diff --git a/vendor/blaze/src/alterschema/common/dynamic_ref_to_static_ref.h b/vendor/blaze/src/alterschema/common/dynamic_ref_to_static_ref.h index 499d03763..481298286 100644 --- a/vendor/blaze/src/alterschema/common/dynamic_ref_to_static_ref.h +++ b/vendor/blaze/src/alterschema/common/dynamic_ref_to_static_ref.h @@ -14,7 +14,7 @@ class DynamicRefToStaticRef 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, @@ -27,24 +27,82 @@ class DynamicRefToStaticRef final : public SchemaTransformRule { schema.defines("$dynamicRef")) { auto reference_pointer{location.pointer}; reference_pointer.push_back(std::cref(KEYWORD_DYNAMIC_REF)); - const auto reference_entry{frame.reference( + + auto reference_entry{frame.reference( sourcemeta::core::SchemaReferenceType::Static, reference_pointer)}; - if (reference_entry.has_value()) { - this->keyword_ = &KEYWORD_DYNAMIC_REF; - return APPLIES_TO_KEYWORDS("$dynamicRef"); + if (!reference_entry.has_value()) { + reference_entry = frame.reference( + sourcemeta::core::SchemaReferenceType::Dynamic, reference_pointer); + } + if (!reference_entry.has_value()) { + return false; + } + + const auto destination{ + frame.traverse(reference_entry->get().destination)}; + if (!destination.has_value()) { + return false; } + + if (destination->get().type == + sourcemeta::core::SchemaFrame::LocationType::Anchor) { + const auto &subschema{sourcemeta::core::get( + root, sourcemeta::core::to_pointer(destination->get().pointer))}; + if (subschema.is_object()) { + const auto *dynamic_anchor{subschema.try_at("$dynamicAnchor")}; + if (dynamic_anchor != nullptr && dynamic_anchor->is_string()) { + const auto &destination_uri{reference_entry->get().destination}; + const auto fragment_position{destination_uri.find('#')}; + const std::string_view fragment{ + fragment_position == std::string::npos + ? std::string_view{destination_uri} + : std::string_view{ + destination_uri.data() + fragment_position + 1, + destination_uri.size() - fragment_position - 1}}; + if (fragment == dynamic_anchor->to_string()) { + return false; + } + } + } + } + + this->keyword_ = &KEYWORD_DYNAMIC_REF; + return APPLIES_TO_KEYWORDS("$dynamicRef"); } if (vocabularies.contains(Vocabularies::Known::JSON_Schema_2019_09_Core) && schema.defines("$recursiveRef")) { auto reference_pointer{location.pointer}; reference_pointer.push_back(std::cref(KEYWORD_RECURSIVE_REF)); - const auto reference_entry{frame.reference( + + auto reference_entry{frame.reference( sourcemeta::core::SchemaReferenceType::Static, reference_pointer)}; - if (reference_entry.has_value()) { - this->keyword_ = &KEYWORD_RECURSIVE_REF; - return APPLIES_TO_KEYWORDS("$recursiveRef"); + if (!reference_entry.has_value()) { + reference_entry = frame.reference( + sourcemeta::core::SchemaReferenceType::Dynamic, reference_pointer); + } + if (!reference_entry.has_value()) { + return false; + } + + const auto destination{ + frame.traverse(reference_entry->get().destination)}; + if (!destination.has_value()) { + return false; } + + const auto &subschema{sourcemeta::core::get( + root, sourcemeta::core::to_pointer(destination->get().pointer))}; + if (subschema.is_object()) { + const auto *recursive_anchor{subschema.try_at("$recursiveAnchor")}; + if (recursive_anchor != nullptr && recursive_anchor->is_boolean() && + recursive_anchor->to_boolean()) { + return false; + } + } + + this->keyword_ = &KEYWORD_RECURSIVE_REF; + return APPLIES_TO_KEYWORDS("$recursiveRef"); } return false; diff --git a/vendor/blaze/src/alterschema/common/enum_with_type.h b/vendor/blaze/src/alterschema/common/enum_with_type.h index 6e0031a4d..2e530a5be 100644 --- a/vendor/blaze/src/alterschema/common/enum_with_type.h +++ b/vendor/blaze/src/alterschema/common/enum_with_type.h @@ -26,16 +26,20 @@ class EnumWithType final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, Vocabularies::Known::JSON_Schema_Draft_1})); - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("type") && - schema.defines("enum") && schema.at("enum").is_array()); + ONLY_CONTINUE_IF(schema.is_object()); + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type); + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array()); - const auto current_types{parse_schema_type(schema.at("type"))}; + const auto current_types{parse_schema_type(*type)}; + ONLY_CONTINUE_IF(current_types.any()); 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(), + enum_value->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()); diff --git a/vendor/blaze/src/alterschema/common/equal_numeric_bounds_to_enum.h b/vendor/blaze/src/alterschema/common/equal_numeric_bounds_to_enum.h index 5b16a16b2..96fa72392 100644 --- a/vendor/blaze/src/alterschema/common/equal_numeric_bounds_to_enum.h +++ b/vendor/blaze/src/alterschema/common/equal_numeric_bounds_to_enum.h @@ -23,25 +23,29 @@ class EqualNumericBoundsToEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2, 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.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())); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF( + type && type->is_string() && + (type->to_string() == "integer" || type->to_string() == "number")); + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number()); + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number() && *minimum == *maximum); + + const auto *exclusive_minimum{schema.try_at("exclusiveMinimum")}; + ONLY_CONTINUE_IF(!(exclusive_minimum && exclusive_minimum->is_boolean() && + exclusive_minimum->to_boolean())); + const auto *exclusive_maximum{schema.try_at("exclusiveMaximum")}; + ONLY_CONTINUE_IF(!(exclusive_maximum && exclusive_maximum->is_boolean() && + exclusive_maximum->to_boolean())); + const auto *minimum_can_equal{schema.try_at("minimumCanEqual")}; + ONLY_CONTINUE_IF(!(minimum_can_equal && minimum_can_equal->is_boolean() && + !minimum_can_equal->to_boolean())); + const auto *maximum_can_equal{schema.try_at("maximumCanEqual")}; + ONLY_CONTINUE_IF(!(maximum_can_equal && maximum_can_equal->is_boolean() && + !maximum_can_equal->to_boolean())); return APPLIES_TO_KEYWORDS("minimum", "maximum"); } diff --git a/vendor/blaze/src/alterschema/common/exclusive_maximum_number_and_maximum.h b/vendor/blaze/src/alterschema/common/exclusive_maximum_number_and_maximum.h index cd44c6ace..1e226301b 100644 --- a/vendor/blaze/src/alterschema/common/exclusive_maximum_number_and_maximum.h +++ b/vendor/blaze/src/alterschema/common/exclusive_maximum_number_and_maximum.h @@ -22,10 +22,12 @@ class ExclusiveMaximumNumberAndMaximum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("maximum") && - schema.defines("exclusiveMaximum") && - schema.at("maximum").is_number() && - schema.at("exclusiveMaximum").is_number()); + schema.is_object()); + + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number()); + const auto *exclusive_maximum{schema.try_at("exclusiveMaximum")}; + ONLY_CONTINUE_IF(exclusive_maximum && exclusive_maximum->is_number()); return APPLIES_TO_KEYWORDS("exclusiveMaximum", "maximum"); } diff --git a/vendor/blaze/src/alterschema/common/exclusive_minimum_number_and_minimum.h b/vendor/blaze/src/alterschema/common/exclusive_minimum_number_and_minimum.h index 58a5626ed..3e940564f 100644 --- a/vendor/blaze/src/alterschema/common/exclusive_minimum_number_and_minimum.h +++ b/vendor/blaze/src/alterschema/common/exclusive_minimum_number_and_minimum.h @@ -22,10 +22,12 @@ class ExclusiveMinimumNumberAndMinimum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("minimum") && - schema.defines("exclusiveMinimum") && - schema.at("minimum").is_number() && - schema.at("exclusiveMinimum").is_number()); + schema.is_object()); + + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number()); + const auto *exclusive_minimum{schema.try_at("exclusiveMinimum")}; + ONLY_CONTINUE_IF(exclusive_minimum && exclusive_minimum->is_number()); return APPLIES_TO_KEYWORDS("exclusiveMinimum", "minimum"); } diff --git a/vendor/blaze/src/alterschema/common/ignored_metaschema.h b/vendor/blaze/src/alterschema/common/ignored_metaschema.h index 1d37d6bf5..24766cc58 100644 --- a/vendor/blaze/src/alterschema/common/ignored_metaschema.h +++ b/vendor/blaze/src/alterschema/common/ignored_metaschema.h @@ -17,8 +17,9 @@ class IgnoredMetaschema final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> SchemaTransformRule::Result override { - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && - schema.at("$schema").is_string()); + ONLY_CONTINUE_IF(schema.is_object()); + const auto *schema_keyword{schema.try_at("$schema")}; + ONLY_CONTINUE_IF(schema_keyword && schema_keyword->is_string()); const auto dialect{sourcemeta::core::dialect(schema)}; ONLY_CONTINUE_IF(!dialect.empty()); ONLY_CONTINUE_IF(dialect != location.dialect); diff --git a/vendor/blaze/src/alterschema/common/maximum_real_for_integer.h b/vendor/blaze/src/alterschema/common/maximum_real_for_integer.h index ac99fab38..3dc648a74 100644 --- a/vendor/blaze/src/alterschema/common/maximum_real_for_integer.h +++ b/vendor/blaze/src/alterschema/common/maximum_real_for_integer.h @@ -26,13 +26,14 @@ class MaximumRealForInteger final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, 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("maximum") && - (schema.at("maximum").is_real() || - (schema.at("maximum").is_decimal() && - !schema.at("maximum").to_decimal().is_integer()))); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number() && + !maximum->is_integral()); return APPLIES_TO_KEYWORDS("maximum"); } diff --git a/vendor/blaze/src/alterschema/common/minimum_real_for_integer.h b/vendor/blaze/src/alterschema/common/minimum_real_for_integer.h index 20180fa02..8eb4dac04 100644 --- a/vendor/blaze/src/alterschema/common/minimum_real_for_integer.h +++ b/vendor/blaze/src/alterschema/common/minimum_real_for_integer.h @@ -26,13 +26,14 @@ class MinimumRealForInteger final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_3, Vocabularies::Known::JSON_Schema_Draft_2, 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("minimum") && - (schema.at("minimum").is_real() || - (schema.at("minimum").is_decimal() && - !schema.at("minimum").to_decimal().is_integer()))); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_string() && + type->to_string() == "integer"); + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number() && + !minimum->is_integral()); return APPLIES_TO_KEYWORDS("minimum"); } diff --git a/vendor/blaze/src/alterschema/common/modern_official_dialect_with_empty_fragment.h b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_empty_fragment.h index 849d70bca..e3e8daf3c 100644 --- a/vendor/blaze/src/alterschema/common/modern_official_dialect_with_empty_fragment.h +++ b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_empty_fragment.h @@ -17,9 +17,10 @@ class ModernOfficialDialectWithEmptyFragment final const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> 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()}; + ONLY_CONTINUE_IF(schema.is_object()); + const auto *schema_keyword{schema.try_at("$schema")}; + ONLY_CONTINUE_IF(schema_keyword && schema_keyword->is_string()); + const auto &dialect{schema_keyword->to_string()}; ONLY_CONTINUE_IF( dialect == "https://json-schema.org/draft/2019-09/schema#" || dialect == "https://json-schema.org/draft/2019-09/hyper-schema#" || diff --git a/vendor/blaze/src/alterschema/common/modern_official_dialect_with_http.h b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_http.h index facad8625..b98b62252 100644 --- a/vendor/blaze/src/alterschema/common/modern_official_dialect_with_http.h +++ b/vendor/blaze/src/alterschema/common/modern_official_dialect_with_http.h @@ -23,9 +23,10 @@ class ModernOfficialDialectWithHttp final : public SchemaTransformRule { location.base_dialect == SchemaBaseDialect::JSON_Schema_2020_12_Hyper || location.base_dialect == SchemaBaseDialect::JSON_Schema_2019_09 || location.base_dialect == SchemaBaseDialect::JSON_Schema_2019_09_Hyper); - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("$schema") && - schema.at("$schema").is_string()); - const auto &dialect{schema.at("$schema").to_string()}; + ONLY_CONTINUE_IF(schema.is_object()); + const auto *schema_keyword{schema.try_at("$schema")}; + ONLY_CONTINUE_IF(schema_keyword && schema_keyword->is_string()); + const auto &dialect{schema_keyword->to_string()}; ONLY_CONTINUE_IF(dialect.starts_with("http://json-schema.org/")); ONLY_CONTINUE_IF( dialect == "http://json-schema.org/draft/2020-12/schema" || diff --git a/vendor/blaze/src/alterschema/common/non_applicable_additional_items.h b/vendor/blaze/src/alterschema/common/non_applicable_additional_items.h index 7d6d356a2..5164f3e4d 100644 --- a/vendor/blaze/src/alterschema/common/non_applicable_additional_items.h +++ b/vendor/blaze/src/alterschema/common/non_applicable_additional_items.h @@ -30,9 +30,10 @@ class NonApplicableAdditionalItems final : public SchemaTransformRule { ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); - if (schema.defines("items") && is_schema(schema.at("items"))) { + const auto *items{schema.try_at("items")}; + if (items && is_schema(*items)) { return APPLIES_TO_KEYWORDS(KEYWORD, "items"); - } else if (!schema.defines("items")) { + } else if (!items) { return APPLIES_TO_KEYWORDS(KEYWORD); } else { return false; diff --git a/vendor/blaze/src/alterschema/common/non_applicable_enum_validation_keywords.h b/vendor/blaze/src/alterschema/common/non_applicable_enum_validation_keywords.h index 249f33ddd..e1076d2ce 100644 --- a/vendor/blaze/src/alterschema/common/non_applicable_enum_validation_keywords.h +++ b/vendor/blaze/src/alterschema/common/non_applicable_enum_validation_keywords.h @@ -28,16 +28,22 @@ class NonApplicableEnumValidationKeywords final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2_Hyper, Vocabularies::Known::JSON_Schema_Draft_1, Vocabularies::Known::JSON_Schema_Draft_1_Hyper}) && - schema.is_object() && schema.defines("enum") && - schema.at("enum").is_array() && !schema.defines("type")); + schema.is_object() && !schema.defines("type")); + + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array()); sourcemeta::core::JSON::TypeSet enum_types; - for (const auto &value : schema.at("enum").as_array()) { + for (const auto &value : enum_value->as_array()) { enum_types.set(std::to_underlying(value.type())); } ONLY_CONTINUE_IF(enum_types.any()); + const bool is_draft3{vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_Draft_3, + Vocabularies::Known::JSON_Schema_Draft_3_Hyper})}; + std::vector positions; for (const auto &entry : schema.as_object()) { const auto &metadata = walker(entry.first, vocabularies); @@ -47,6 +53,10 @@ class NonApplicableEnumValidationKeywords final : public SchemaTransformRule { continue; } + if (is_draft3 && entry.first == "required" && entry.second.is_boolean()) { + continue; + } + // Check if there's any overlap between keyword's applicable types and // enum types if ((metadata.instances & enum_types).none()) { diff --git a/vendor/blaze/src/alterschema/common/non_applicable_type_specific_keywords.h b/vendor/blaze/src/alterschema/common/non_applicable_type_specific_keywords.h index d67e15817..edab3ec0c 100644 --- a/vendor/blaze/src/alterschema/common/non_applicable_type_specific_keywords.h +++ b/vendor/blaze/src/alterschema/common/non_applicable_type_specific_keywords.h @@ -18,6 +18,7 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { -> SchemaTransformRule::Result override { ONLY_CONTINUE_IF(schema.is_object()); + const auto *type_value{schema.try_at("type")}; auto current_types{vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation, @@ -31,8 +32,8 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_1_Hyper, Vocabularies::Known::JSON_Schema_Draft_0, Vocabularies::Known::JSON_Schema_Draft_0_Hyper}) && - schema.defines("type") - ? parse_schema_type(schema.at("type")) + type_value + ? parse_schema_type(*type_value) : sourcemeta::core::JSON::TypeSet{}}; if (vocabularies.contains_any( @@ -43,10 +44,12 @@ class NonApplicableTypeSpecificKeywords 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}) && - schema.defines("enum") && schema.at("enum").is_array()) { - for (const auto &entry : schema.at("enum").as_array()) { - current_types.set(std::to_underlying(entry.type())); + Vocabularies::Known::JSON_Schema_Draft_1})) { + const auto *enum_value{schema.try_at("enum")}; + if (enum_value && enum_value->is_array()) { + for (const auto &entry : enum_value->as_array()) { + current_types.set(std::to_underlying(entry.type())); + } } } @@ -54,9 +57,11 @@ class NonApplicableTypeSpecificKeywords final : public SchemaTransformRule { {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")) { - current_types.set(std::to_underlying(schema.at("const").type())); + Vocabularies::Known::JSON_Schema_Draft_6})) { + const auto *const_value{schema.try_at("const")}; + if (const_value) { + current_types.set(std::to_underlying(const_value->type())); + } } // This means that the schema has no explicit type constraints, diff --git a/vendor/blaze/src/alterschema/common/oneof_false_simplify.h b/vendor/blaze/src/alterschema/common/oneof_false_simplify.h index 258700f61..befd95f4b 100644 --- a/vendor/blaze/src/alterschema/common/oneof_false_simplify.h +++ b/vendor/blaze/src/alterschema/common/oneof_false_simplify.h @@ -22,11 +22,12 @@ class OneOfFalseSimplify final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Applicator, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines(KEYWORD) && - !schema.defines("not") && schema.at(KEYWORD).is_array() && - schema.at(KEYWORD).size() == 1); + schema.is_object() && !schema.defines("not")); - const auto &entry{schema.at(KEYWORD).front()}; + const auto *one_of{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(one_of && one_of->is_array() && one_of->size() == 1); + + const auto &entry{one_of->front()}; ONLY_CONTINUE_IF(entry.is_boolean() && !entry.to_boolean()); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); diff --git a/vendor/blaze/src/alterschema/common/oneof_to_anyof_disjoint_types.h b/vendor/blaze/src/alterschema/common/oneof_to_anyof_disjoint_types.h index 2e403cf77..c8551c2ec 100644 --- a/vendor/blaze/src/alterschema/common/oneof_to_anyof_disjoint_types.h +++ b/vendor/blaze/src/alterschema/common/oneof_to_anyof_disjoint_types.h @@ -24,9 +24,11 @@ class OneOfToAnyOfDisjointTypes final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines(KEYWORD) && - schema.at(KEYWORD).is_array() && - schema.at(KEYWORD).size() > 1); + schema.is_object()); + + const auto *oneof_value{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(oneof_value && oneof_value->is_array() && + oneof_value->size() > 1); const auto has_validation_vocabulary{vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, @@ -44,27 +46,30 @@ class OneOfToAnyOfDisjointTypes final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6})}; - const auto &oneof{schema.at(KEYWORD)}; std::vector type_sets; - type_sets.reserve(oneof.size()); + type_sets.reserve(oneof_value->size()); - for (const auto &branch : oneof.as_array()) { + for (const auto &branch : oneof_value->as_array()) { ONLY_CONTINUE_IF(branch.is_object()); - const auto has_type{branch.defines("type")}; - const auto has_const{has_const_vocabulary && branch.defines("const")}; - const auto has_enum{has_validation_vocabulary && branch.defines("enum") && - branch.at("enum").is_array()}; + const auto *type_value{branch.try_at("type")}; + const auto *const_value{has_const_vocabulary ? branch.try_at("const") + : nullptr}; + const auto *enum_value{has_validation_vocabulary ? branch.try_at("enum") + : nullptr}; + const auto has_enum{enum_value && enum_value->is_array()}; - if (has_type) { - type_sets.push_back(parse_schema_type(branch.at("type"))); - } else if (has_const && !has_enum) { + if (type_value) { + const auto branch_types{parse_schema_type(*type_value)}; + ONLY_CONTINUE_IF(branch_types.any()); + type_sets.push_back(branch_types); + } else if (const_value && !has_enum) { JSON::TypeSet branch_types; - branch_types.set(std::to_underlying(branch.at("const").type())); + branch_types.set(std::to_underlying(const_value->type())); type_sets.push_back(branch_types); - } else if (has_enum && !has_const) { + } else if (has_enum && !const_value) { JSON::TypeSet branch_types; - for (const auto &item : branch.at("enum").as_array()) { + for (const auto &item : enum_value->as_array()) { branch_types.set(std::to_underlying(item.type())); } type_sets.push_back(branch_types); diff --git a/vendor/blaze/src/alterschema/common/orphan_definitions.h b/vendor/blaze/src/alterschema/common/orphan_definitions.h index fe7a7faf6..faed95009 100644 --- a/vendor/blaze/src/alterschema/common/orphan_definitions.h +++ b/vendor/blaze/src/alterschema/common/orphan_definitions.h @@ -52,16 +52,36 @@ class OrphanDefinitions final : public SchemaTransformRule { schema.at(container).erase(pointer.at(1).to_property()); } - if (schema.defines("$defs") && schema.at("$defs").empty()) { + const auto *defs{schema.try_at("$defs")}; + if (defs && defs->empty()) { schema.erase("$defs"); } - if (schema.defines("definitions") && schema.at("definitions").empty()) { + const auto *definitions{schema.try_at("definitions")}; + if (definitions && definitions->empty()) { schema.erase("definitions"); } } private: + static auto + subtree_has_dynamic_anchor(const sourcemeta::core::SchemaFrame &frame, + const WeakPointer &entry_pointer) -> bool { + for (const auto &[key, location] : frame.locations()) { + if (key.first != sourcemeta::core::SchemaReferenceType::Dynamic) { + continue; + } + if (location.type != + sourcemeta::core::SchemaFrame::LocationType::Anchor) { + continue; + } + if (location.pointer.starts_with(entry_pointer)) { + return true; + } + } + return false; + } + static auto has_reachable_reference_through( const sourcemeta::core::SchemaFrame &frame, const sourcemeta::core::SchemaFrame::Location &base, @@ -117,7 +137,9 @@ class OrphanDefinitions final : public SchemaTransformRule { if (entry_location.has_value() && !frame.is_reachable(base, entry_location->get(), walker, resolver) && !has_reachable_reference_through(frame, base, walker, resolver, - absolute_entry_pointer)) { + absolute_entry_pointer) && + !(!frame.standalone() && + subtree_has_dynamic_anchor(frame, absolute_entry_pointer))) { orphans.push_back(Pointer{container, entry.first}); } } diff --git a/vendor/blaze/src/alterschema/common/required_properties_in_properties.h b/vendor/blaze/src/alterschema/common/required_properties_in_properties.h index 9ed3b6bd3..1fc7c7a07 100644 --- a/vendor/blaze/src/alterschema/common/required_properties_in_properties.h +++ b/vendor/blaze/src/alterschema/common/required_properties_in_properties.h @@ -31,15 +31,19 @@ class RequiredPropertiesInProperties final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3})) && - schema.is_object() && schema.defines("required") && - schema.at("required").is_array() && !schema.at("required").empty() && - (!schema.defines("additionalProperties") || - (schema.at("additionalProperties").is_boolean() && - schema.at("additionalProperties").to_boolean()))); + schema.is_object()); + + const auto *required{schema.try_at("required")}; + ONLY_CONTINUE_IF(required && required->is_array() && !required->empty()); + + const auto *additional_properties{schema.try_at("additionalProperties")}; + ONLY_CONTINUE_IF(!additional_properties || + (additional_properties->is_boolean() && + additional_properties->to_boolean())); std::vector locations; std::size_t index{0}; - for (const auto &property : schema.at("required").as_array()) { + for (const auto &property : required->as_array()) { if (property.is_string() && !this->defined_in_properties_sibling(schema, property.to_string()) && !WALK_UP_IN_PLACE_APPLICATORS( @@ -74,8 +78,8 @@ class RequiredPropertiesInProperties final : public SchemaTransformRule { defined_in_properties_sibling(const JSON &schema, const JSON::String &property) const -> bool { assert(schema.is_object()); - return schema.defines("properties") && - schema.at("properties").is_object() && - schema.at("properties").defines(property); + const auto *properties{schema.try_at("properties")}; + return properties && properties->is_object() && + properties->defines(property); }; }; diff --git a/vendor/blaze/src/alterschema/common/single_type_array.h b/vendor/blaze/src/alterschema/common/single_type_array.h index 310f82d34..a8234d0f4 100644 --- a/vendor/blaze/src/alterschema/common/single_type_array.h +++ b/vendor/blaze/src/alterschema/common/single_type_array.h @@ -26,10 +26,11 @@ class SingleTypeArray final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2, Vocabularies::Known::JSON_Schema_Draft_1, Vocabularies::Known::JSON_Schema_Draft_0}) && - schema.is_object() && schema.defines("type") && - schema.at("type").is_array() && - schema.at("type").size() == 1 && - schema.at("type").front().is_string()); + schema.is_object()); + + const auto *type{schema.try_at("type")}; + ONLY_CONTINUE_IF(type && type->is_array() && type->size() == 1 && + type->front().is_string()); return APPLIES_TO_KEYWORDS("type"); } diff --git a/vendor/blaze/src/alterschema/common/unnecessary_allof_ref_wrapper_draft.h b/vendor/blaze/src/alterschema/common/unnecessary_allof_ref_wrapper_draft.h index 6aed8a6ce..e7f9f9235 100644 --- a/vendor/blaze/src/alterschema/common/unnecessary_allof_ref_wrapper_draft.h +++ b/vendor/blaze/src/alterschema/common/unnecessary_allof_ref_wrapper_draft.h @@ -20,16 +20,15 @@ class UnnecessaryAllOfRefWrapperDraft final : public SchemaTransformRule { vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4})); - ONLY_CONTINUE_IF(schema.is_object() && schema.size() == 1 && - schema.defines("allOf") && schema.at("allOf").is_array()); - - const auto &all_of{schema.at("allOf")}; + ONLY_CONTINUE_IF(schema.is_object() && schema.size() == 1); + const auto *all_of{schema.try_at("allOf")}; + ONLY_CONTINUE_IF(all_of && all_of->is_array()); // In Draft 7 and older, `$ref` overrides sibling keywords, so we can only // elevate it if it is the only keyword of the only branch, and the outer // subschema only declares `allOf` - ONLY_CONTINUE_IF(all_of.size() == 1); - const auto &entry{all_of.at(0)}; + ONLY_CONTINUE_IF(all_of->size() == 1); + const auto &entry{all_of->at(0)}; ONLY_CONTINUE_IF(entry.is_object()); ONLY_CONTINUE_IF(entry.size() == 1 && entry.defines("$ref")); diff --git a/vendor/blaze/src/alterschema/common/unsatisfiable_drop_validation.h b/vendor/blaze/src/alterschema/common/unsatisfiable_drop_validation.h index 2c5fb8373..f96f9da78 100644 --- a/vendor/blaze/src/alterschema/common/unsatisfiable_drop_validation.h +++ b/vendor/blaze/src/alterschema/common/unsatisfiable_drop_validation.h @@ -21,9 +21,11 @@ class UnsatisfiableDropValidation final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Applicator, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("not") && - schema.at("not").is_boolean() && - schema.at("not").to_boolean()); + schema.is_object()); + + const auto *not_value{schema.try_at("not")}; + ONLY_CONTINUE_IF(not_value && not_value->is_boolean() && + not_value->to_boolean()); std::vector positions; for (const auto &entry : schema.as_object()) { diff --git a/vendor/blaze/src/alterschema/common/unsatisfiable_in_place_applicator_type.h b/vendor/blaze/src/alterschema/common/unsatisfiable_in_place_applicator_type.h index 72967d7f0..498e088a3 100644 --- a/vendor/blaze/src/alterschema/common/unsatisfiable_in_place_applicator_type.h +++ b/vendor/blaze/src/alterschema/common/unsatisfiable_in_place_applicator_type.h @@ -29,6 +29,7 @@ class UnsatisfiableInPlaceApplicatorType final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_1, Vocabularies::Known::JSON_Schema_Draft_0})); const auto parent_types{parse_schema_type(schema.at("type"))}; + ONLY_CONTINUE_IF(parent_types.any()); std::vector locations; @@ -45,23 +46,31 @@ class UnsatisfiableInPlaceApplicatorType final : public SchemaTransformRule { const auto &branches{entry.second}; for (std::size_t index = 0; index < branches.size(); ++index) { const auto &branch{branches.at(index)}; - if (!branch.is_object() || !branch.defines("type")) { + if (!branch.is_object()) { + continue; + } + const auto *branch_type{branch.try_at("type")}; + if (!branch_type) { continue; } - const auto branch_types{parse_schema_type(branch.at("type"))}; - if ((parent_types & branch_types).none()) { + const auto branch_types{parse_schema_type(*branch_type)}; + if (branch_types.any() && (parent_types & branch_types).none()) { locations.push_back(Pointer{keyword, index}); } } } else if (keyword_type == SchemaKeywordType::ApplicatorValueInPlaceMaybe) { - if (!entry.second.is_object() || !entry.second.defines("type")) { + if (!entry.second.is_object()) { + continue; + } + const auto *branch_type{entry.second.try_at("type")}; + if (!branch_type) { continue; } - const auto branch_types{parse_schema_type(entry.second.at("type"))}; - if ((parent_types & branch_types).none()) { + const auto branch_types{parse_schema_type(*branch_type)}; + if (branch_types.any() && (parent_types & branch_types).none()) { locations.push_back(Pointer{keyword}); } } diff --git a/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema.h b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema.h index beee4defc..077c992d6 100644 --- a/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema.h +++ b/vendor/blaze/src/alterschema/include/sourcemeta/blaze/alterschema.h @@ -45,6 +45,21 @@ enum class AlterSchemaMode : std::uint8_t { /// are syntax sugar to other keywords, potentially decreasing human /// readability in favor of explicitness Canonicalizer, + + /// Rules that upgrade a JSON Schema document up to JSON Schema Draft 4 + UpgradeDraft4, + + /// Rules that upgrade a JSON Schema document up to JSON Schema Draft 6 + UpgradeDraft6, + + /// Rules that upgrade a JSON Schema document up to JSON Schema Draft 7 + UpgradeDraft7, + + /// Rules that upgrade a JSON Schema document up to JSON Schema 2019-09 + Upgrade201909, + + /// Rules that upgrade a JSON Schema document up to JSON Schema 2020-12 + Upgrade202012, }; /// @ingroup alterschema diff --git a/vendor/blaze/src/alterschema/linter/const_not_in_enum.h b/vendor/blaze/src/alterschema/linter/const_not_in_enum.h index d0245d896..4412c12ab 100644 --- a/vendor/blaze/src/alterschema/linter/const_not_in_enum.h +++ b/vendor/blaze/src/alterschema/linter/const_not_in_enum.h @@ -22,9 +22,13 @@ class ConstNotInEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("const") && - schema.defines("enum") && schema.at("enum").is_array() && - !schema.at("enum").contains(schema.at("const"))); + schema.is_object()); + + const auto *const_value{schema.try_at("const")}; + ONLY_CONTINUE_IF(const_value); + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array() && + !enum_value->contains(*const_value)); return APPLIES_TO_KEYWORDS("const", "enum"); } }; diff --git a/vendor/blaze/src/alterschema/linter/dependent_required_default.h b/vendor/blaze/src/alterschema/linter/dependent_required_default.h index 5fdcd1127..8e5bdd3fe 100644 --- a/vendor/blaze/src/alterschema/linter/dependent_required_default.h +++ b/vendor/blaze/src/alterschema/linter/dependent_required_default.h @@ -21,9 +21,11 @@ class DependentRequiredDefault final : public SchemaTransformRule { vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation}) && - schema.is_object() && schema.defines("dependentRequired") && - schema.at("dependentRequired").is_object() && - schema.at("dependentRequired").empty()); + schema.is_object()); + + const auto *dependent_required{schema.try_at("dependentRequired")}; + ONLY_CONTINUE_IF(dependent_required && dependent_required->is_object() && + dependent_required->empty()); return APPLIES_TO_KEYWORDS("dependentRequired"); } diff --git a/vendor/blaze/src/alterschema/linter/duplicate_examples.h b/vendor/blaze/src/alterschema/linter/duplicate_examples.h index fadda50ef..eaeca1ea3 100644 --- a/vendor/blaze/src/alterschema/linter/duplicate_examples.h +++ b/vendor/blaze/src/alterschema/linter/duplicate_examples.h @@ -21,9 +21,10 @@ class DuplicateExamples final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Meta_Data, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("examples") && - schema.at("examples").is_array() && - !schema.at("examples").unique()); + schema.is_object()); + + const auto *examples{schema.try_at("examples")}; + ONLY_CONTINUE_IF(examples && examples->is_array() && !examples->unique()); return APPLIES_TO_KEYWORDS("examples"); } diff --git a/vendor/blaze/src/alterschema/linter/else_empty.h b/vendor/blaze/src/alterschema/linter/else_empty.h index c4f64a0b3..cddc20c21 100644 --- a/vendor/blaze/src/alterschema/linter/else_empty.h +++ b/vendor/blaze/src/alterschema/linter/else_empty.h @@ -15,16 +15,18 @@ class ElseEmpty final : public SchemaTransformRule { const SchemaFrame &frame, const SchemaFrame::Location &location, const SchemaWalker &, const SchemaResolver &) const -> SchemaTransformRule::Result override { - ONLY_CONTINUE_IF( - vocabularies.contains_any( - {Vocabularies::Known::JSON_Schema_2020_12_Applicator, - Vocabularies::Known::JSON_Schema_2019_09_Applicator, - Vocabularies::Known::JSON_Schema_Draft_7}) && - schema.is_object() && schema.defines(KEYWORD) && - is_schema(schema.at(KEYWORD)) && is_empty_schema(schema.at(KEYWORD)) && - (schema.at(KEYWORD).is_object() || - (!schema.defines("if") || - !(schema.at("if").is_boolean() && schema.at("if").to_boolean())))); + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7}) && + schema.is_object()); + + const auto *else_value{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(else_value && is_schema(*else_value) && + is_empty_schema(*else_value)); + const auto *if_value{schema.try_at("if")}; + ONLY_CONTINUE_IF(else_value->is_object() || !if_value || + !(if_value->is_boolean() && if_value->to_boolean())); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); return APPLIES_TO_KEYWORDS(KEYWORD); diff --git a/vendor/blaze/src/alterschema/linter/enum_to_const.h b/vendor/blaze/src/alterschema/linter/enum_to_const.h index e147622ec..9d3c7fc5b 100644 --- a/vendor/blaze/src/alterschema/linter/enum_to_const.h +++ b/vendor/blaze/src/alterschema/linter/enum_to_const.h @@ -21,9 +21,11 @@ class EnumToConst final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_2019_09_Validation, Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && !schema.defines("const") && - schema.defines("enum") && schema.at("enum").is_array() && - schema.at("enum").size() == 1); + schema.is_object() && !schema.defines("const")); + + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array() && + enum_value->size() == 1); return APPLIES_TO_KEYWORDS("enum"); } diff --git a/vendor/blaze/src/alterschema/linter/equal_numeric_bounds_to_const.h b/vendor/blaze/src/alterschema/linter/equal_numeric_bounds_to_const.h index 9b45a5db8..f3733f458 100644 --- a/vendor/blaze/src/alterschema/linter/equal_numeric_bounds_to_const.h +++ b/vendor/blaze/src/alterschema/linter/equal_numeric_bounds_to_const.h @@ -17,20 +17,22 @@ class EqualNumericBoundsToConst final : public SchemaTransformRule { 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, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6, + }) && + schema.is_object()); + + const auto *type{schema.try_at("type")}; 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.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")); + type && type->is_string() && + (type->to_string() == "integer" || type->to_string() == "number")); + const auto *minimum{schema.try_at("minimum")}; + ONLY_CONTINUE_IF(minimum && minimum->is_number()); + const auto *maximum{schema.try_at("maximum")}; + ONLY_CONTINUE_IF(maximum && maximum->is_number() && *minimum == *maximum); return APPLIES_TO_KEYWORDS("minimum", "maximum"); } diff --git a/vendor/blaze/src/alterschema/linter/forbid_empty_enum.h b/vendor/blaze/src/alterschema/linter/forbid_empty_enum.h index 44b315516..10a2319f4 100644 --- a/vendor/blaze/src/alterschema/linter/forbid_empty_enum.h +++ b/vendor/blaze/src/alterschema/linter/forbid_empty_enum.h @@ -22,9 +22,11 @@ class ForbidEmptyEnum final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && !schema.defines("not") && - schema.defines("enum") && schema.at("enum").is_array() && - schema.at("enum").empty()); + schema.is_object() && !schema.defines("not")); + + const auto *enum_value{schema.try_at("enum")}; + ONLY_CONTINUE_IF(enum_value && enum_value->is_array() && + enum_value->empty()); ONLY_CONTINUE_IF(!frame.has_references_through(location.pointer)); return APPLIES_TO_KEYWORDS("enum"); } diff --git a/vendor/blaze/src/alterschema/linter/incoherent_min_max_contains.h b/vendor/blaze/src/alterschema/linter/incoherent_min_max_contains.h index 9c8915515..960e2bb73 100644 --- a/vendor/blaze/src/alterschema/linter/incoherent_min_max_contains.h +++ b/vendor/blaze/src/alterschema/linter/incoherent_min_max_contains.h @@ -21,13 +21,13 @@ class IncoherentMinMaxContains final : public SchemaTransformRule { 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()); + schema.is_object() && schema.defines("contains")); + + const auto *min_contains{schema.try_at("minContains")}; + ONLY_CONTINUE_IF(min_contains && min_contains->is_integer()); + const auto *max_contains{schema.try_at("maxContains")}; + ONLY_CONTINUE_IF(max_contains && max_contains->is_integer() && + min_contains->to_integer() > max_contains->to_integer()); return APPLIES_TO_KEYWORDS("minContains", "maxContains"); } }; diff --git a/vendor/blaze/src/alterschema/linter/items_array_default.h b/vendor/blaze/src/alterschema/linter/items_array_default.h index 3c4fbf540..1131959b0 100644 --- a/vendor/blaze/src/alterschema/linter/items_array_default.h +++ b/vendor/blaze/src/alterschema/linter/items_array_default.h @@ -26,9 +26,10 @@ class ItemsArrayDefault final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2_Hyper, Vocabularies::Known::JSON_Schema_Draft_1, Vocabularies::Known::JSON_Schema_Draft_1_Hyper}) && - schema.is_object() && schema.defines("items") && - schema.at("items").is_array() && - schema.at("items").empty()); + schema.is_object()); + + const auto *items{schema.try_at("items")}; + ONLY_CONTINUE_IF(items && items->is_array() && items->empty()); return APPLIES_TO_KEYWORDS("items"); } diff --git a/vendor/blaze/src/alterschema/linter/multiple_of_default.h b/vendor/blaze/src/alterschema/linter/multiple_of_default.h index 2d123845f..62bae0e8b 100644 --- a/vendor/blaze/src/alterschema/linter/multiple_of_default.h +++ b/vendor/blaze/src/alterschema/linter/multiple_of_default.h @@ -22,14 +22,15 @@ class MultipleOfDefault final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("multipleOf") && - ((schema.at("multipleOf").is_integer() && - schema.at("multipleOf").to_integer() == 1) || - (schema.at("multipleOf").is_real() && - schema.at("multipleOf").to_real() == 1.0) || - (schema.at("multipleOf").is_decimal() && - schema.at("multipleOf").to_decimal() == - sourcemeta::core::Decimal{1}))); + schema.is_object()); + + const auto *multiple_of{schema.try_at("multipleOf")}; + ONLY_CONTINUE_IF( + multiple_of && + ((multiple_of->is_integer() && multiple_of->to_integer() == 1) || + (multiple_of->is_real() && multiple_of->to_real() == 1.0) || + (multiple_of->is_decimal() && + multiple_of->to_decimal() == sourcemeta::core::Decimal{1}))); return APPLIES_TO_KEYWORDS("multipleOf"); } diff --git a/vendor/blaze/src/alterschema/linter/pattern_properties_default.h b/vendor/blaze/src/alterschema/linter/pattern_properties_default.h index 2e6fb1315..2d43fa131 100644 --- a/vendor/blaze/src/alterschema/linter/pattern_properties_default.h +++ b/vendor/blaze/src/alterschema/linter/pattern_properties_default.h @@ -24,10 +24,11 @@ class PatternPropertiesDefault final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4, Vocabularies::Known::JSON_Schema_Draft_3}) && - schema.is_object() && - schema.defines("patternProperties") && - schema.at("patternProperties").is_object() && - schema.at("patternProperties").empty()); + schema.is_object()); + + const auto *pattern_properties{schema.try_at("patternProperties")}; + ONLY_CONTINUE_IF(pattern_properties && pattern_properties->is_object() && + pattern_properties->empty()); return APPLIES_TO_KEYWORDS("patternProperties"); } diff --git a/vendor/blaze/src/alterschema/linter/portable_anchor_names.h b/vendor/blaze/src/alterschema/linter/portable_anchor_names.h new file mode 100644 index 000000000..7be1ccc2f --- /dev/null +++ b/vendor/blaze/src/alterschema/linter/portable_anchor_names.h @@ -0,0 +1,105 @@ +class PortableAnchorNames final : public SchemaTransformRule { +public: + using mutates = std::false_type; + using reframe_after_transform = std::false_type; + PortableAnchorNames() + : SchemaTransformRule{ + "portable_anchor_names", + "Keep anchors within the safe allowed character set across JSON " + "Schema dialects (`^[A-Za-z][A-Za-z0-9_.-]*$`)"} {}; + + [[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_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_4})); + ONLY_CONTINUE_IF(schema.is_object()); + + std::vector offenders; + + if (vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Core, + Vocabularies::Known::JSON_Schema_2019_09_Core})) { + this->check_anchor_keyword(schema, ANCHOR, offenders); + } + + if (vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Core})) { + this->check_anchor_keyword(schema, DYNAMIC_ANCHOR, offenders); + } + + if (vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6, + Vocabularies::Known::JSON_Schema_Draft_4})) { + const auto &id_keyword{ + vocabularies.contains_any({Vocabularies::Known::JSON_Schema_Draft_4}) + ? ID_DRAFT_4 + : ID_MODERN}; + this->check_id_fragment(schema, id_keyword, offenders); + } + + ONLY_CONTINUE_IF(!offenders.empty()); + return APPLIES_TO_POINTERS(std::move(offenders)); + } + +private: + static inline const sourcemeta::core::JSON::String ANCHOR{"$anchor"}; + static inline const sourcemeta::core::JSON::String DYNAMIC_ANCHOR{ + "$dynamicAnchor"}; + static inline const sourcemeta::core::JSON::String ID_MODERN{"$id"}; + static inline const sourcemeta::core::JSON::String ID_DRAFT_4{"id"}; + static inline const Regex SAFE_ANCHOR_PATTERN{ + to_regex("^[A-Za-z][A-Za-z0-9_.-]*$").value()}; + + static auto + check_anchor_keyword(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON::String &keyword, + std::vector &offenders) -> void { + if (!schema.defines(keyword) || !schema.at(keyword).is_string()) { + return; + } + + const auto &value{schema.at(keyword).to_string()}; + if (value.empty()) { + return; + } + + if (!matches(SAFE_ANCHOR_PATTERN, value)) { + offenders.push_back(Pointer{keyword}); + } + } + + static auto check_id_fragment(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON::String &keyword, + std::vector &offenders) -> void { + if (!schema.defines(keyword) || !schema.at(keyword).is_string()) { + return; + } + + const auto &value{schema.at(keyword).to_string()}; + if (value.find('#') == sourcemeta::core::JSON::String::npos) { + return; + } + + const sourcemeta::core::URI uri{value}; + const auto fragment{uri.fragment()}; + if (!fragment.has_value() || fragment.value().empty()) { + return; + } + + if (!matches(SAFE_ANCHOR_PATTERN, + sourcemeta::core::JSON::String{fragment.value()})) { + offenders.push_back(Pointer{keyword}); + } + } +}; diff --git a/vendor/blaze/src/alterschema/linter/properties_default.h b/vendor/blaze/src/alterschema/linter/properties_default.h index 5e8489477..3fb6a8966 100644 --- a/vendor/blaze/src/alterschema/linter/properties_default.h +++ b/vendor/blaze/src/alterschema/linter/properties_default.h @@ -28,9 +28,11 @@ class PropertiesDefault final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2_Hyper, Vocabularies::Known::JSON_Schema_Draft_1, Vocabularies::Known::JSON_Schema_Draft_1_Hyper}) && - schema.is_object() && schema.defines("properties") && - schema.at("properties").is_object() && - schema.at("properties").empty()); + schema.is_object()); + + const auto *properties{schema.try_at("properties")}; + ONLY_CONTINUE_IF(properties && properties->is_object() && + properties->empty()); return APPLIES_TO_KEYWORDS("properties"); } diff --git a/vendor/blaze/src/alterschema/linter/property_names_type_default.h b/vendor/blaze/src/alterschema/linter/property_names_type_default.h index 46626e282..7aa9f2670 100644 --- a/vendor/blaze/src/alterschema/linter/property_names_type_default.h +++ b/vendor/blaze/src/alterschema/linter/property_names_type_default.h @@ -17,23 +17,24 @@ class PropertyNamesTypeDefault final : public SchemaTransformRule { const sourcemeta::core::SchemaWalker &, const sourcemeta::core::SchemaResolver &) const -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7, + Vocabularies::Known::JSON_Schema_Draft_6}) && + schema.is_object()); + + const auto *property_names{schema.try_at("propertyNames")}; + ONLY_CONTINUE_IF(property_names && property_names->is_object()); + const auto *type{property_names->try_at("type")}; ONLY_CONTINUE_IF( - vocabularies.contains_any( - {Vocabularies::Known::JSON_Schema_2020_12_Applicator, - Vocabularies::Known::JSON_Schema_2019_09_Applicator, - Vocabularies::Known::JSON_Schema_Draft_7, - Vocabularies::Known::JSON_Schema_Draft_6}) && - schema.is_object() && schema.defines("propertyNames") && - schema.at("propertyNames").is_object() && - schema.at("propertyNames").defines("type") && - ((schema.at("propertyNames").at("type").is_string() && - schema.at("propertyNames").at("type").to_string() == "string") || - (schema.at("propertyNames").at("type").is_array() && - std::all_of(schema.at("propertyNames").at("type").as_array().begin(), - schema.at("propertyNames").at("type").as_array().end(), - [](const auto &item) { - return item.is_string() && item.to_string() == "string"; - })))); + type && ((type->is_string() && type->to_string() == "string") || + (type->is_array() && + std::all_of(type->as_array().begin(), type->as_array().end(), + [](const auto &item) { + return item.is_string() && + item.to_string() == "string"; + })))); return APPLIES_TO_POINTERS({{"propertyNames", "type"}}); } diff --git a/vendor/blaze/src/alterschema/linter/simple_properties_identifiers.h b/vendor/blaze/src/alterschema/linter/simple_properties_identifiers.h index 35661f1ca..168cfd75e 100644 --- a/vendor/blaze/src/alterschema/linter/simple_properties_identifiers.h +++ b/vendor/blaze/src/alterschema/linter/simple_properties_identifiers.h @@ -30,9 +30,10 @@ class SimplePropertiesIdentifiers final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2_Hyper, Vocabularies::Known::JSON_Schema_Draft_1, Vocabularies::Known::JSON_Schema_Draft_1_Hyper})); - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("properties") && - schema.at("properties").is_object() && - !schema.at("properties").empty()); + ONLY_CONTINUE_IF(schema.is_object()); + const auto *properties{schema.try_at("properties")}; + ONLY_CONTINUE_IF(properties && properties->is_object() && + !properties->empty()); if (vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Core, @@ -55,7 +56,7 @@ class SimplePropertiesIdentifiers final : public SchemaTransformRule { } std::vector offenders; - for (const auto &entry : schema.at("properties").as_object()) { + for (const auto &entry : properties->as_object()) { static const Regex IDENTIFIER_PATTERN{ to_regex("^[A-Za-z_][A-Za-z0-9_]*$").value()}; if (!matches(IDENTIFIER_PATTERN, entry.first)) { diff --git a/vendor/blaze/src/alterschema/linter/then_empty.h b/vendor/blaze/src/alterschema/linter/then_empty.h index eacf299a4..647da3ee1 100644 --- a/vendor/blaze/src/alterschema/linter/then_empty.h +++ b/vendor/blaze/src/alterschema/linter/then_empty.h @@ -15,16 +15,18 @@ class ThenEmpty final : public SchemaTransformRule { const SchemaFrame &frame, const SchemaFrame::Location &location, const SchemaWalker &, const SchemaResolver &) const -> SchemaTransformRule::Result override { - ONLY_CONTINUE_IF( - vocabularies.contains_any( - {Vocabularies::Known::JSON_Schema_2020_12_Applicator, - Vocabularies::Known::JSON_Schema_2019_09_Applicator, - Vocabularies::Known::JSON_Schema_Draft_7}) && - schema.is_object() && schema.defines(KEYWORD) && - is_schema(schema.at(KEYWORD)) && is_empty_schema(schema.at(KEYWORD)) && - (schema.at(KEYWORD).is_object() || - (!schema.defines("if") || - !(schema.at("if").is_boolean() && schema.at("if").to_boolean())))); + ONLY_CONTINUE_IF(vocabularies.contains_any( + {Vocabularies::Known::JSON_Schema_2020_12_Applicator, + Vocabularies::Known::JSON_Schema_2019_09_Applicator, + Vocabularies::Known::JSON_Schema_Draft_7}) && + schema.is_object()); + + const auto *then_value{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(then_value && is_schema(*then_value) && + is_empty_schema(*then_value)); + const auto *if_value{schema.try_at("if")}; + ONLY_CONTINUE_IF(then_value->is_object() || !if_value || + !(if_value->is_boolean() && if_value->to_boolean())); ONLY_CONTINUE_IF(!frame.has_references_through( location.pointer, WeakPointer::Token{std::cref(KEYWORD)})); return APPLIES_TO_KEYWORDS(KEYWORD); diff --git a/vendor/blaze/src/alterschema/linter/top_level_description.h b/vendor/blaze/src/alterschema/linter/top_level_description.h index d26d076e9..6b511dae6 100644 --- a/vendor/blaze/src/alterschema/linter/top_level_description.h +++ b/vendor/blaze/src/alterschema/linter/top_level_description.h @@ -28,11 +28,11 @@ class TopLevelDescription final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2, Vocabularies::Known::JSON_Schema_Draft_1})); ONLY_CONTINUE_IF(schema.is_object()); - if (schema.defines("description") && schema.at("description").is_string() && - schema.at("description").empty()) { + const auto *description{schema.try_at("description")}; + if (description && description->is_string() && description->empty()) { return APPLIES_TO_KEYWORDS("description"); } else { - return !schema.defines("description"); + return !description; } } }; diff --git a/vendor/blaze/src/alterschema/linter/top_level_examples.h b/vendor/blaze/src/alterschema/linter/top_level_examples.h index f4ae68bbd..ff6fd27fa 100644 --- a/vendor/blaze/src/alterschema/linter/top_level_examples.h +++ b/vendor/blaze/src/alterschema/linter/top_level_examples.h @@ -24,11 +24,11 @@ class TopLevelExamples final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6})); ONLY_CONTINUE_IF(schema.is_object()); - if (schema.defines("examples") && schema.at("examples").is_array() && - schema.at("examples").empty()) { + const auto *examples{schema.try_at("examples")}; + if (examples && examples->is_array() && examples->empty()) { return APPLIES_TO_KEYWORDS("examples"); } else { - return !schema.defines("examples"); + return !examples; } } }; diff --git a/vendor/blaze/src/alterschema/linter/top_level_title.h b/vendor/blaze/src/alterschema/linter/top_level_title.h index c6360dab2..583a83bc0 100644 --- a/vendor/blaze/src/alterschema/linter/top_level_title.h +++ b/vendor/blaze/src/alterschema/linter/top_level_title.h @@ -28,11 +28,11 @@ class TopLevelTitle final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_2, Vocabularies::Known::JSON_Schema_Draft_1})); ONLY_CONTINUE_IF(schema.is_object()); - if (schema.defines("title") && schema.at("title").is_string() && - schema.at("title").empty()) { + const auto *title{schema.try_at("title")}; + if (title && title->is_string() && title->empty()) { return APPLIES_TO_KEYWORDS("title"); } else { - return !schema.defines("title"); + return !title; } } }; diff --git a/vendor/blaze/src/alterschema/linter/unnecessary_allof_ref_wrapper_modern.h b/vendor/blaze/src/alterschema/linter/unnecessary_allof_ref_wrapper_modern.h index 2615b2d97..183266b46 100644 --- a/vendor/blaze/src/alterschema/linter/unnecessary_allof_ref_wrapper_modern.h +++ b/vendor/blaze/src/alterschema/linter/unnecessary_allof_ref_wrapper_modern.h @@ -19,10 +19,12 @@ class UnnecessaryAllOfRefWrapperModern final : public SchemaTransformRule { ONLY_CONTINUE_IF(vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Applicator, Vocabularies::Known::JSON_Schema_2019_09_Applicator})); - ONLY_CONTINUE_IF(schema.is_object() && schema.defines("allOf") && - schema.at("allOf").is_array()); + ONLY_CONTINUE_IF(schema.is_object()); - const auto &all_of{schema.at("allOf")}; + const auto *all_of_value{schema.try_at("allOf")}; + ONLY_CONTINUE_IF(all_of_value && all_of_value->is_array()); + + const auto &all_of{*all_of_value}; // Don't do anything if there is more than one branch and ALL branches // define `$ref` (a common multiple composition pattern) diff --git a/vendor/blaze/src/alterschema/linter/unnecessary_allof_wrapper.h b/vendor/blaze/src/alterschema/linter/unnecessary_allof_wrapper.h index 8762db1e4..6f2c2200b 100644 --- a/vendor/blaze/src/alterschema/linter/unnecessary_allof_wrapper.h +++ b/vendor/blaze/src/alterschema/linter/unnecessary_allof_wrapper.h @@ -21,9 +21,11 @@ class UnnecessaryAllOfWrapper final : public SchemaTransformRule { Vocabularies::Known::JSON_Schema_Draft_7, Vocabularies::Known::JSON_Schema_Draft_6, Vocabularies::Known::JSON_Schema_Draft_4})); - ONLY_CONTINUE_IF(schema.is_object() && schema.defines(KEYWORD) && - schema.at(KEYWORD).is_array() && - !schema.at(KEYWORD).empty()); + ONLY_CONTINUE_IF(schema.is_object()); + + const auto *all_of_value{schema.try_at(KEYWORD)}; + ONLY_CONTINUE_IF(all_of_value && all_of_value->is_array() && + !all_of_value->empty()); std::unordered_set dependency_blocked; for (const auto &entry : schema.as_object()) { @@ -41,18 +43,19 @@ class UnnecessaryAllOfWrapper final : public SchemaTransformRule { } } + const auto *parent_type_value{schema.try_at("type")}; const JSON::TypeSet parent_types{ - schema.defines("type") && + parent_type_value && 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}) - ? parse_schema_type(schema.at("type")) + ? parse_schema_type(*parent_type_value) : JSON::TypeSet{}}; - const auto &all_of{schema.at(KEYWORD)}; + const auto &all_of{*all_of_value}; std::vector locations; std::unordered_set elevated; diff --git a/vendor/blaze/src/alterschema/linter/unsatisfiable_max_contains.h b/vendor/blaze/src/alterschema/linter/unsatisfiable_max_contains.h index 29522c9c9..9d02a2ce1 100644 --- a/vendor/blaze/src/alterschema/linter/unsatisfiable_max_contains.h +++ b/vendor/blaze/src/alterschema/linter/unsatisfiable_max_contains.h @@ -22,11 +22,13 @@ class UnsatisfiableMaxContains final : public SchemaTransformRule { vocabularies.contains_any( {Vocabularies::Known::JSON_Schema_2020_12_Validation, Vocabularies::Known::JSON_Schema_2019_09_Validation}) && - schema.is_object() && schema.defines("maxContains") && - schema.at("maxContains").is_integer() && schema.defines("maxItems") && - schema.at("maxItems").is_integer() && - schema.at("maxContains").to_integer() >= - schema.at("maxItems").to_integer()); + schema.is_object()); + + const auto *max_contains{schema.try_at("maxContains")}; + ONLY_CONTINUE_IF(max_contains && max_contains->is_integer()); + const auto *max_items{schema.try_at("maxItems")}; + ONLY_CONTINUE_IF(max_items && max_items->is_integer() && + max_contains->to_integer() >= max_items->to_integer()); return APPLIES_TO_KEYWORDS("maxContains", "maxItems"); } diff --git a/vendor/blaze/src/alterschema/linter/unsatisfiable_min_properties.h b/vendor/blaze/src/alterschema/linter/unsatisfiable_min_properties.h index 4616ca8ee..42ab40e82 100644 --- a/vendor/blaze/src/alterschema/linter/unsatisfiable_min_properties.h +++ b/vendor/blaze/src/alterschema/linter/unsatisfiable_min_properties.h @@ -17,19 +17,21 @@ class UnsatisfiableMinProperties final : public SchemaTransformRule { 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, - Vocabularies::Known::JSON_Schema_Draft_7, - Vocabularies::Known::JSON_Schema_Draft_6, - Vocabularies::Known::JSON_Schema_Draft_4}) && - schema.is_object() && schema.defines("minProperties") && - schema.at("minProperties").is_integer() && schema.defines("required") && - schema.at("required").is_array() && schema.at("required").unique() && - std::cmp_greater_equal(schema.at("required").size(), - static_cast( - schema.at("minProperties").to_integer()))); + 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}) && + schema.is_object()); + + const auto *min_properties{schema.try_at("minProperties")}; + ONLY_CONTINUE_IF(min_properties && min_properties->is_integer()); + const auto *required{schema.try_at("required")}; + ONLY_CONTINUE_IF(required && required->is_array() && required->unique() && + std::cmp_greater_equal(required->size(), + static_cast( + min_properties->to_integer()))); return APPLIES_TO_KEYWORDS("minProperties", "required"); } diff --git a/vendor/blaze/src/alterschema/linter/valid_examples.h b/vendor/blaze/src/alterschema/linter/valid_examples.h index 63e8a1954..738daf353 100644 --- a/vendor/blaze/src/alterschema/linter/valid_examples.h +++ b/vendor/blaze/src/alterschema/linter/valid_examples.h @@ -23,8 +23,10 @@ class ValidExamples final : public SchemaTransformRule { 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()); + schema.is_object()); + + const auto *examples{schema.try_at("examples")}; + ONLY_CONTINUE_IF(examples && examples->is_array() && !examples->empty()); if (vocabularies.contains_any({Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_6, @@ -50,7 +52,7 @@ class ValidExamples final : public SchemaTransformRule { return false; } - for (const auto &example : schema.at("examples").as_array()) { + for (const auto &example : examples->as_array()) { SimpleOutput output{example}; Evaluator evaluator; const auto result{ diff --git a/vendor/blaze/src/alterschema/upgrade/helpers.h b/vendor/blaze/src/alterschema/upgrade/helpers.h new file mode 100644 index 000000000..24763f3fc --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/helpers.h @@ -0,0 +1,85 @@ +static const std::string DIALECT_OVERRIDE_KEYWORD{ + "x-sourcemeta-dialect-override-subschema"}; + +static auto mark_dialect_override(sourcemeta::core::JSON &schema, + const std::string_view dialect) -> void { + schema.assign(DIALECT_OVERRIDE_KEYWORD, + sourcemeta::core::JSON{std::string{dialect}}); +} + +static auto current_dialect_or_override(const sourcemeta::core::JSON &schema) + -> std::string_view { + if (!schema.is_object()) { + return {}; + } + const auto *override_value{schema.try_at(DIALECT_OVERRIDE_KEYWORD)}; + if (override_value != nullptr && override_value->is_string()) { + return override_value->to_string(); + } + if (schema.defines("$schema") && schema.at("$schema").is_string()) { + return schema.at("$schema").to_string(); + } + return {}; +} + +static auto +subschema_at_dialect(const sourcemeta::core::JSON &schema, + const sourcemeta::core::SchemaFrame::Location &location, + const std::string_view dialect) -> bool { + const auto current{current_dialect_or_override(schema)}; + if (!current.empty()) { + return current == dialect; + } + return schema.is_object() && location.pointer.empty(); +} + +static auto drop_dialect_overrides(sourcemeta::core::JSON &schema, + const bool is_root) -> void { + if (schema.is_array()) { + for (auto &item : schema.as_array()) { + drop_dialect_overrides(item, false); + } + return; + } + + if (!schema.is_object()) { + return; + } + + if (!is_root && schema.defines("$schema") && + schema.at("$schema").is_string()) { + return; + } + + schema.erase(DIALECT_OVERRIDE_KEYWORD); + + std::vector keys; + keys.reserve(schema.size()); + for (const auto &entry : schema.as_object()) { + keys.push_back(entry.first); + } + for (const auto &key : keys) { + drop_dialect_overrides(schema.at(key), false); + } +} + +struct AnchorCharPolicy { + std::function is_valid_first; + std::function is_valid_body; +}; + +static auto sanitize_anchor_with_policy(const std::string_view original, + const std::set &in_use, + const AnchorCharPolicy &policy) + -> std::string { + std::string sanitized; + sanitized.reserve(original.size()); + for (const char character : original) { + sanitized.push_back(policy.is_valid_body(character) ? character : '-'); + } + while (sanitized.empty() || !policy.is_valid_first(sanitized.front()) || + in_use.contains(sanitized)) { + sanitized.insert(0, "x-"); + } + return sanitized; +} diff --git a/vendor/blaze/src/alterschema/upgrade/prefix_promoted_2020_12_keywords.h b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_2020_12_keywords.h new file mode 100644 index 000000000..a95c05900 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_2020_12_keywords.h @@ -0,0 +1,65 @@ +class PrefixPromoted202012Keywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + PrefixPromoted202012Keywords() + : SchemaTransformRule{"prefix_promoted_2020_12_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(Vocabularies::Known::JSON_Schema_2019_09_Core) && + schema.is_object()); + + return schema.defines_any({"prefixItems", "$dynamicAnchor", "$dynamicRef"}); + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + this->renames_.clear(); + for (const auto &keyword : KEYWORDS) { + const std::string keyword_name{keyword}; + if (!schema.defines(keyword_name)) { + continue; + } + + std::string prefixed_name{"x-" + keyword_name}; + while (schema.defines(prefixed_name)) { + prefixed_name.insert(0, "x-"); + } + + this->renames_.emplace(keyword_name, prefixed_name); + schema.rename(keyword_name, std::move(prefixed_name)); + } + } + + [[nodiscard]] auto rereference(const std::string_view, + const sourcemeta::core::Pointer &, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer override { + for (const auto &[old_name, new_name] : this->renames_) { + const auto result{ + target.rebase(current.concat(sourcemeta::core::Pointer{old_name}), + current.concat(sourcemeta::core::Pointer{new_name}))}; + if (result != target) { + return result; + } + } + + return target; + } + +private: + static inline const std::array KEYWORDS{ + {"prefixItems", "$dynamicAnchor", "$dynamicRef"}}; + + mutable std::unordered_map renames_; +}; diff --git a/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_2019_09_keywords.h b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_2019_09_keywords.h new file mode 100644 index 000000000..ac08552f7 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_2019_09_keywords.h @@ -0,0 +1,74 @@ +class PrefixPromoted201909Keywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + PrefixPromoted201909Keywords() + : SchemaTransformRule{"prefix_promoted_2019_09_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(Vocabularies::Known::JSON_Schema_Draft_7) && + schema.is_object()); + + for (const auto &keyword : KEYWORDS) { + if (schema.defines(std::string{keyword})) { + return true; + } + } + + return false; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + this->renames_.clear(); + for (const auto &keyword : KEYWORDS) { + const std::string keyword_name{keyword}; + if (!schema.defines(keyword_name)) { + continue; + } + + std::string prefixed_name{"x-" + keyword_name}; + while (schema.defines(prefixed_name)) { + prefixed_name.insert(0, "x-"); + } + + this->renames_.emplace(keyword_name, prefixed_name); + schema.rename(keyword_name, std::move(prefixed_name)); + } + } + + [[nodiscard]] auto rereference(const std::string_view, + const sourcemeta::core::Pointer &, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer override { + for (const auto &[old_name, new_name] : this->renames_) { + const auto result{ + target.rebase(current.concat(sourcemeta::core::Pointer{old_name}), + current.concat(sourcemeta::core::Pointer{new_name}))}; + if (result != target) { + return result; + } + } + + return target; + } + +private: + static inline const std::array KEYWORDS{ + {"$anchor", "$recursiveAnchor", "$recursiveRef", "$vocabulary", "$defs", + "dependentSchemas", "dependentRequired", "unevaluatedItems", + "unevaluatedProperties", "maxContains", "minContains", "contentSchema", + "deprecated"}}; + + mutable std::unordered_map renames_; +}; diff --git a/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_4_keywords.h b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_4_keywords.h new file mode 100644 index 000000000..1956f2ce5 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_4_keywords.h @@ -0,0 +1,72 @@ +class PrefixPromotedDraft4Keywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + PrefixPromotedDraft4Keywords() + : SchemaTransformRule{"prefix_promoted_draft_4_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(Vocabularies::Known::JSON_Schema_Draft_3) && + schema.is_object()); + + for (const auto &keyword : KEYWORDS) { + if (schema.defines(std::string{keyword})) { + return true; + } + } + + return false; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + this->renames_.clear(); + for (const auto &keyword : KEYWORDS) { + const std::string keyword_name{keyword}; + if (!schema.defines(keyword_name)) { + continue; + } + + std::string prefixed_name{"x-" + keyword_name}; + while (schema.defines(prefixed_name)) { + prefixed_name.insert(0, "x-"); + } + + this->renames_.emplace(keyword_name, prefixed_name); + schema.rename(keyword_name, std::move(prefixed_name)); + } + } + + [[nodiscard]] auto rereference(const std::string_view, + const sourcemeta::core::Pointer &, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer override { + for (const auto &[old_name, new_name] : this->renames_) { + const auto result{ + target.rebase(current.concat(sourcemeta::core::Pointer{old_name}), + current.concat(sourcemeta::core::Pointer{new_name}))}; + if (result != target) { + return result; + } + } + + return target; + } + +private: + static inline const std::array KEYWORDS{ + {"multipleOf", "maxProperties", "minProperties", "allOf", "anyOf", + "oneOf", "not"}}; + + mutable std::unordered_map renames_; +}; diff --git a/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_6_keywords.h b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_6_keywords.h new file mode 100644 index 000000000..328e502d8 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_6_keywords.h @@ -0,0 +1,71 @@ +class PrefixPromotedDraft6Keywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + PrefixPromotedDraft6Keywords() + : SchemaTransformRule{"prefix_promoted_draft_6_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(Vocabularies::Known::JSON_Schema_Draft_4) && + schema.is_object()); + + for (const auto &keyword : KEYWORDS) { + if (schema.defines(std::string{keyword})) { + return true; + } + } + + return false; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + this->renames_.clear(); + for (const auto &keyword : KEYWORDS) { + const std::string keyword_name{keyword}; + if (!schema.defines(keyword_name)) { + continue; + } + + std::string prefixed_name{"x-" + keyword_name}; + while (schema.defines(prefixed_name)) { + prefixed_name.insert(0, "x-"); + } + + this->renames_.emplace(keyword_name, prefixed_name); + schema.rename(keyword_name, std::move(prefixed_name)); + } + } + + [[nodiscard]] auto rereference(const std::string_view, + const sourcemeta::core::Pointer &, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer override { + for (const auto &[old_name, new_name] : this->renames_) { + const auto result{ + target.rebase(current.concat(sourcemeta::core::Pointer{old_name}), + current.concat(sourcemeta::core::Pointer{new_name}))}; + if (result != target) { + return result; + } + } + + return target; + } + +private: + static inline const std::array KEYWORDS{ + {"const", "contains", "propertyNames", "examples"}}; + + mutable std::unordered_map renames_; +}; diff --git a/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_7_keywords.h b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_7_keywords.h new file mode 100644 index 000000000..1021ca34c --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/prefix_promoted_draft_7_keywords.h @@ -0,0 +1,72 @@ +class PrefixPromotedDraft7Keywords final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + PrefixPromotedDraft7Keywords() + : SchemaTransformRule{"prefix_promoted_draft_7_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(Vocabularies::Known::JSON_Schema_Draft_6) && + schema.is_object()); + + for (const auto &keyword : KEYWORDS) { + if (schema.defines(std::string{keyword})) { + return true; + } + } + + return false; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + this->renames_.clear(); + for (const auto &keyword : KEYWORDS) { + const std::string keyword_name{keyword}; + if (!schema.defines(keyword_name)) { + continue; + } + + std::string prefixed_name{"x-" + keyword_name}; + while (schema.defines(prefixed_name)) { + prefixed_name.insert(0, "x-"); + } + + this->renames_.emplace(keyword_name, prefixed_name); + schema.rename(keyword_name, std::move(prefixed_name)); + } + } + + [[nodiscard]] auto rereference(const std::string_view, + const sourcemeta::core::Pointer &, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer override { + for (const auto &[old_name, new_name] : this->renames_) { + const auto result{ + target.rebase(current.concat(sourcemeta::core::Pointer{old_name}), + current.concat(sourcemeta::core::Pointer{new_name}))}; + if (result != target) { + return result; + } + } + + return target; + } + +private: + static inline const std::array KEYWORDS{ + {"$comment", "if", "then", "else", "readOnly", "writeOnly", + "contentMediaType", "contentEncoding"}}; + + mutable std::unordered_map renames_; +}; diff --git a/vendor/blaze/src/alterschema/upgrade/upgrade_2019_09_to_2020_12.h b/vendor/blaze/src/alterschema/upgrade/upgrade_2019_09_to_2020_12.h new file mode 100644 index 000000000..124e8e95d --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/upgrade_2019_09_to_2020_12.h @@ -0,0 +1,583 @@ +class Upgrade201909To202012 final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + Upgrade201909To202012() + : SchemaTransformRule{"upgrade_2019_09_to_2020_12", ""} {}; + + [[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(Vocabularies::Known::JSON_Schema_2019_09_Core) && + schema.is_object()); + + const bool is_resource_scope{ + location.type == + sourcemeta::core::SchemaFrame::LocationType::Resource || + location.pointer.empty()}; + + if (is_resource_scope) { + compute_anchor_sanitization(root, frame, location); + if (!this->anchor_renames_.empty() || + !this->anchor_ref_rewrites_.empty()) { + this->descendant_has_pending_pattern_ = + any_descendant_has_pending_pattern(root, frame, location); + this->resource_has_recursive_anchor_ = + compute_resource_has_recursive_anchor(root, frame, location); + this->document_has_unevaluated_items_ = + compute_document_has_unevaluated_items(root, frame, walker, + resolver); + this->is_inside_contains_wrapper_ = false; + return Result{std::vector{}, + std::string{"sanitize"}}; + } + } + + if (enclosing_resource_has_pending_sanitization(root, frame, location)) { + return false; + } + + this->is_inside_contains_wrapper_ = + location_inside_contains_wrapper(location); + + if (any_descendant_has_pending_pattern(root, frame, location)) { + return false; + } + + this->resource_has_recursive_anchor_ = + compute_resource_has_recursive_anchor(root, frame, location); + this->document_has_unevaluated_items_ = + compute_document_has_unevaluated_items(root, frame, walker, resolver); + return true; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &result) const + -> void override { + const bool is_sanitize_phase{result.description.has_value() && + result.description.value() == "sanitize"}; + if (is_sanitize_phase) { + apply_anchor_sanitization(schema); + if (this->descendant_has_pending_pattern_) { + return; + } + } + + this->renames_.clear(); + + if (schema.defines("$recursiveAnchor") && + schema.at("$recursiveAnchor").is_boolean()) { + if (schema.at("$recursiveAnchor").to_boolean()) { + schema.rename("$recursiveAnchor", "$dynamicAnchor"); + schema.at("$dynamicAnchor") + .into(sourcemeta::core::JSON{std::string{"meta"}}); + } else { + schema.erase("$recursiveAnchor"); + } + } + + if (schema.defines("$recursiveRef")) { + schema.rename("$recursiveRef", "$dynamicRef"); + if (this->resource_has_recursive_anchor_) { + schema.at("$dynamicRef") + .into(sourcemeta::core::JSON{std::string{"#meta"}}); + } + } + + if (schema.defines("items") && schema.at("items").is_array()) { + if (schema.at("items").empty()) { + schema.erase("items"); + } else { + this->renames_.emplace_back(sourcemeta::core::Pointer{"items"}, + sourcemeta::core::Pointer{"prefixItems"}); + schema.rename("items", "prefixItems"); + } + if (schema.defines("additionalItems")) { + this->renames_.emplace_back( + sourcemeta::core::Pointer{"additionalItems"}, + sourcemeta::core::Pointer{"items"}); + schema.rename("additionalItems", "items"); + } + } else if (schema.defines("additionalItems")) { + schema.erase("additionalItems"); + } + + if (schema.defines("contains") && !this->is_inside_contains_wrapper_ && + this->document_has_unevaluated_items_) { + auto wrapper_inner{sourcemeta::core::JSON::make_object()}; + wrapper_inner.assign("contains", schema.at("contains")); + if (schema.defines("minContains")) { + wrapper_inner.assign("minContains", schema.at("minContains")); + schema.erase("minContains"); + } + if (schema.defines("maxContains")) { + wrapper_inner.assign("maxContains", schema.at("maxContains")); + schema.erase("maxContains"); + } + + auto inner_not{sourcemeta::core::JSON::make_object()}; + inner_not.assign("not", std::move(wrapper_inner)); + + if (!schema.defines("not")) { + schema.rename("contains", "not"); + schema.at("not").into(std::move(inner_not)); + this->renames_.emplace_back( + sourcemeta::core::Pointer{"contains"}, + sourcemeta::core::Pointer{"not", "not", "contains"}); + } else { + schema.erase("contains"); + auto outer_not{sourcemeta::core::JSON::make_object()}; + outer_not.assign("not", std::move(inner_not)); + if (schema.defines("allOf") && schema.at("allOf").is_array()) { + const auto allof_index{schema.at("allOf").size()}; + schema.at("allOf").push_back(std::move(outer_not)); + this->renames_.emplace_back( + sourcemeta::core::Pointer{"contains"}, + sourcemeta::core::Pointer{ + "allOf", + static_cast( + allof_index), + "not", "not", "contains"}); + } else { + auto allof_array{sourcemeta::core::JSON::make_array()}; + allof_array.push_back(std::move(outer_not)); + schema.assign("allOf", std::move(allof_array)); + this->renames_.emplace_back( + sourcemeta::core::Pointer{"contains"}, + sourcemeta::core::Pointer{ + "allOf", + static_cast(0), + "not", "not", "contains"}); + } + } + } + + if (schema.defines("$schema") && schema.at("$schema").is_string() && + schema.at("$schema").to_string() == DRAFT_2019_09_URL) { + schema.assign("$schema", + sourcemeta::core::JSON{std::string{DRAFT_2020_12_URL}}); + drop_dialect_overrides(schema, true); + } else { + mark_dialect_override(schema, DRAFT_2020_12_URL); + } + } + + [[nodiscard]] auto rereference(const std::string_view, + const sourcemeta::core::Pointer &, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer override { + for (const auto &[old_pointer, new_pointer] : this->renames_) { + const auto result{target.rebase(current.concat(old_pointer), + current.concat(new_pointer))}; + if (result != target) { + return result; + } + } + + return target; + } + +private: + static constexpr std::string_view DRAFT_2019_09_URL{ + "https://json-schema.org/draft/2019-09/schema"}; + static constexpr std::string_view DRAFT_2020_12_URL{ + "https://json-schema.org/draft/2020-12/schema"}; + + struct AnchorRename { + sourcemeta::core::Pointer subschema_pointer; + std::string new_name; + }; + + struct AnchorRefRewrite { + sourcemeta::core::Pointer ref_pointer; + std::string new_value; + }; + + mutable std::vector< + std::pair> + renames_; + mutable bool resource_has_recursive_anchor_{false}; + mutable bool is_inside_contains_wrapper_{false}; + mutable bool descendant_has_pending_pattern_{false}; + mutable bool document_has_unevaluated_items_{false}; + mutable std::vector anchor_renames_; + mutable std::vector anchor_ref_rewrites_; + + static auto compute_document_has_unevaluated_items( + const sourcemeta::core::JSON &root, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaWalker &walker, + const sourcemeta::core::SchemaResolver &resolver) -> bool { + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + const auto absolute{sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &subschema{sourcemeta::core::get(root, absolute)}; + if (!subschema.is_object() || !subschema.defines("unevaluatedItems")) { + continue; + } + const auto location_vocabularies{ + frame.vocabularies(entry.second, resolver)}; + const auto &keyword_metadata{ + walker("unevaluatedItems", location_vocabularies)}; + if (keyword_metadata.type != + sourcemeta::core::SchemaKeywordType::Unknown) { + return true; + } + } + return false; + } + + static auto any_descendant_has_pending_pattern( + const sourcemeta::core::JSON &root, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &location) -> bool { + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + if (entry.second.pointer.size() <= location.pointer.size() || + !entry.second.pointer.starts_with(location.pointer)) { + continue; + } + const auto absolute{sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &descendant{sourcemeta::core::get(root, absolute)}; + if (has_pending_pattern(descendant, entry.second)) { + return true; + } + } + return false; + } + + static auto is_2020_12_anchor_first_char(const char character) -> bool { + return (character >= 'A' && character <= 'Z') || + (character >= 'a' && character <= 'z') || character == '_'; + } + + static auto is_2020_12_anchor_body_char(const char character) -> bool { + return (character >= 'A' && character <= 'Z') || + (character >= 'a' && character <= 'z') || + (character >= '0' && character <= '9') || character == '_' || + character == '.' || character == '-'; + } + + static auto is_valid_2020_12_anchor(const std::string_view name) -> bool { + if (name.empty()) { + return false; + } + if (!is_2020_12_anchor_first_char(name.front())) { + return false; + } + for (std::size_t index{1}; index < name.size(); ++index) { + if (!is_2020_12_anchor_body_char(name[index])) { + return false; + } + } + return true; + } + + static auto location_inside_contains_wrapper( + const sourcemeta::core::SchemaFrame::Location &location) -> bool { + if (location.pointer.size() < 2) { + return false; + } + const auto &last{location.pointer.back()}; + if (!last.is_property() || last.to_property() != "not") { + return false; + } + const auto &second_last{location.pointer.at(location.pointer.size() - 2)}; + if (!second_last.is_property() || second_last.to_property() != "not") { + return false; + } + return true; + } + + static auto + has_pending_pattern(const sourcemeta::core::JSON &subschema, + const sourcemeta::core::SchemaFrame::Location &location) + -> bool { + if (!subschema.is_object()) { + return false; + } + if (!subschema.defines_any({"$schema", "$recursiveAnchor", "$recursiveRef", + "items", "additionalItems", "contains"})) { + return false; + } + if (subschema.defines("$schema") && subschema.at("$schema").is_string() && + subschema.at("$schema").to_string() == DRAFT_2019_09_URL) { + return true; + } + if (subschema.defines_any( + {"$recursiveAnchor", "$recursiveRef", "additionalItems"})) { + return true; + } + if (subschema.defines("items") && subschema.at("items").is_array()) { + return true; + } + if (subschema.defines("contains") && + !location_inside_contains_wrapper(location)) { + return true; + } + return false; + } + + static auto find_enclosing_resource( + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location ¤t_location) + -> std::optional> { + std::optional< + std::reference_wrapper> + closest; + for (const auto &entry : frame.locations()) { + const bool entry_is_resource_scope{ + entry.second.type == + sourcemeta::core::SchemaFrame::LocationType::Resource || + entry.second.pointer.empty()}; + if (!entry_is_resource_scope) { + continue; + } + if (entry.second.pointer.size() > current_location.pointer.size()) { + continue; + } + if (!current_location.pointer.starts_with(entry.second.pointer)) { + continue; + } + if (!closest.has_value() || + entry.second.pointer.size() > closest.value().get().pointer.size()) { + closest = std::cref(entry.second); + } + } + return closest; + } + + static auto + pointer_within_resource(const sourcemeta::core::WeakPointer &candidate, + const sourcemeta::core::WeakPointer &resource_pointer) + -> bool { + if (!candidate.starts_with(resource_pointer)) { + return false; + } + return true; + } + + auto compute_anchor_sanitization( + const sourcemeta::core::JSON &root, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location &resource_location) const + -> void { + this->anchor_renames_.clear(); + this->anchor_ref_rewrites_.clear(); + + const auto &resource_pointer{resource_location.pointer}; + + std::set existing_valid; + std::vector> invalid; + + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Anchor) { + continue; + } + if (entry.first.first != sourcemeta::core::SchemaReferenceType::Static) { + continue; + } + if (!pointer_within_resource(entry.second.pointer, resource_pointer)) { + continue; + } + const sourcemeta::core::URI anchor_uri{entry.first.second}; + const auto fragment{anchor_uri.fragment()}; + if (!fragment.has_value() || fragment.value().empty()) { + continue; + } + const std::string anchor_name{fragment.value()}; + if (is_valid_2020_12_anchor(anchor_name)) { + existing_valid.insert(anchor_name); + } else { + invalid.emplace_back(anchor_name, entry.second.pointer); + } + } + + if (invalid.empty()) { + return; + } + + static const AnchorCharPolicy POLICY{ + .is_valid_first = &is_2020_12_anchor_first_char, + .is_valid_body = &is_2020_12_anchor_body_char}; + + std::map rename_map; + std::set in_use{existing_valid}; + for (const auto &[original, pointer] : invalid) { + if (rename_map.contains(original)) { + continue; + } + in_use.erase(original); + const auto sanitized{ + sanitize_anchor_with_policy(original, in_use, POLICY)}; + rename_map.emplace(original, sanitized); + in_use.insert(sanitized); + } + + std::set processed_pointers; + for (const auto &[original, pointer] : invalid) { + const auto pointer_str{sourcemeta::core::to_string(pointer)}; + if (processed_pointers.contains(pointer_str)) { + continue; + } + processed_pointers.insert(pointer_str); + + const auto absolute{sourcemeta::core::to_pointer(pointer)}; + const auto &subschema{sourcemeta::core::get(root, absolute)}; + if (!subschema.is_object() || !subschema.defines("$anchor") || + !subschema.at("$anchor").is_string()) { + continue; + } + const auto current_value{subschema.at("$anchor").to_string()}; + const auto rename_iter{rename_map.find(current_value)}; + if (rename_iter == rename_map.end()) { + continue; + } + + const auto relative_weak{pointer.resolve_from(resource_pointer)}; + this->anchor_renames_.push_back( + {sourcemeta::core::to_pointer(relative_weak), rename_iter->second}); + } + + for (const auto &reference : frame.references()) { + if (!pointer_within_resource(reference.first.second, resource_pointer)) { + continue; + } + if (!reference.second.fragment.has_value()) { + continue; + } + const auto &fragment{reference.second.fragment.value()}; + if (fragment.empty() || fragment.front() == '/') { + continue; + } + const auto rename_iter{rename_map.find(std::string{fragment})}; + if (rename_iter == rename_map.end()) { + continue; + } + + sourcemeta::core::URI ref_uri{reference.second.original}; + ref_uri.fragment(rename_iter->second); + const auto new_value{ref_uri.recompose()}; + + const auto relative_weak{ + reference.first.second.resolve_from(resource_pointer)}; + this->anchor_ref_rewrites_.push_back( + {sourcemeta::core::to_pointer(relative_weak), new_value}); + } + } + + static auto enclosing_resource_has_pending_sanitization( + const sourcemeta::core::JSON &root, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location ¤t_location) -> bool { + const auto closest{find_enclosing_resource(frame, current_location)}; + if (!closest.has_value()) { + return false; + } + const auto &resource_pointer{closest.value().get().pointer}; + + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Anchor) { + continue; + } + if (entry.first.first != sourcemeta::core::SchemaReferenceType::Static) { + continue; + } + if (!pointer_within_resource(entry.second.pointer, resource_pointer)) { + continue; + } + const sourcemeta::core::URI anchor_uri{entry.first.second}; + const auto fragment{anchor_uri.fragment()}; + if (!fragment.has_value() || fragment.value().empty()) { + continue; + } + if (!is_valid_2020_12_anchor(fragment.value())) { + const auto absolute{sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &subschema{sourcemeta::core::get(root, absolute)}; + if (subschema.is_object() && subschema.defines("$anchor") && + subschema.at("$anchor").is_string() && + subschema.at("$anchor").to_string() == fragment.value()) { + return true; + } + } + } + return false; + } + + auto apply_anchor_sanitization(sourcemeta::core::JSON &schema) const -> void { + for (const auto &rename : this->anchor_renames_) { + auto &target{sourcemeta::core::get(schema, rename.subschema_pointer)}; + target.at("$anchor").into(sourcemeta::core::JSON{rename.new_name}); + } + for (const auto &rewrite : this->anchor_ref_rewrites_) { + auto &target{sourcemeta::core::get(schema, rewrite.ref_pointer)}; + target.into(sourcemeta::core::JSON{rewrite.new_value}); + } + } + + static auto compute_resource_has_recursive_anchor( + const sourcemeta::core::JSON &root, + const sourcemeta::core::SchemaFrame &frame, + const sourcemeta::core::SchemaFrame::Location ¤t_location) -> bool { + const auto closest{find_enclosing_resource(frame, current_location)}; + if (!closest.has_value()) { + return false; + } + + const auto &resource_pointer{closest.value().get().pointer}; + std::set seen; + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + if (!entry.second.pointer.starts_with(resource_pointer)) { + continue; + } + if (entry.second.type == + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.pointer.size() > resource_pointer.size()) { + continue; + } + const auto pointer_str{sourcemeta::core::to_string(entry.second.pointer)}; + if (seen.contains(pointer_str)) { + continue; + } + seen.insert(pointer_str); + + const auto absolute{sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &subschema{sourcemeta::core::get(root, absolute)}; + if (!subschema.is_object()) { + continue; + } + if (subschema.defines("$recursiveAnchor") && + subschema.at("$recursiveAnchor").is_boolean() && + subschema.at("$recursiveAnchor").to_boolean()) { + return true; + } + } + return false; + } +}; diff --git a/vendor/blaze/src/alterschema/upgrade/upgrade_dialect_override_cleanup.h b/vendor/blaze/src/alterschema/upgrade/upgrade_dialect_override_cleanup.h new file mode 100644 index 000000000..5dbbd6540 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/upgrade_dialect_override_cleanup.h @@ -0,0 +1,47 @@ +class UpgradeDialectOverrideCleanup final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UpgradeDialectOverrideCleanup() + : SchemaTransformRule{"upgrade_dialect_override_cleanup", ""} {}; + + [[nodiscard]] auto + condition(const sourcemeta::core::JSON &schema, + const sourcemeta::core::JSON &, + const sourcemeta::core::Vocabularies &, + const sourcemeta::core::SchemaFrame &, + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::SchemaWalker &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF(location.pointer.empty() && schema.is_object()); + + const auto *override_value{schema.try_at(DIALECT_OVERRIDE_KEYWORD)}; + ONLY_CONTINUE_IF(override_value != nullptr && override_value->is_string()); + + return true; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + if (!schema.defines("$schema")) { + sourcemeta::core::JSON dialect_value{ + schema.at(DIALECT_OVERRIDE_KEYWORD).to_string()}; + + bool placed{false}; + for (const auto &entry : schema.as_object()) { + if (entry.first != DIALECT_OVERRIDE_KEYWORD) { + schema.try_assign_before("$schema", dialect_value, entry.first); + placed = true; + break; + } + } + + if (!placed) { + schema.assign("$schema", std::move(dialect_value)); + } + } + + drop_dialect_overrides(schema, true); + } +}; diff --git a/vendor/blaze/src/alterschema/upgrade/upgrade_draft_3_to_draft_4.h b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_3_to_draft_4.h new file mode 100644 index 000000000..dd50e14d2 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_3_to_draft_4.h @@ -0,0 +1,396 @@ +class UpgradeDraft3ToDraft4 final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UpgradeDraft3ToDraft4() + : SchemaTransformRule{"upgrade_draft_3_to_draft_4", ""} {}; + + [[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 &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_3) && + schema.is_object()); + + const bool root_via_default_dialect = + location.pointer.empty() && !schema.defines("$schema"); + + ONLY_CONTINUE_IF(has_pending_draft_3_pattern(schema) || + root_via_default_dialect); + + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + + if (!is_strict_descendant(location.pointer, entry.second.pointer)) { + continue; + } + + const auto entry_pointer{ + sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &entry_schema{sourcemeta::core::get(root, entry_pointer)}; + + if (entry_schema.is_object() && entry_schema.defines("$ref")) { + continue; + } + + if (has_pending_draft_3_pattern(entry_schema)) { + return false; + } + } + + return true; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + rewrite_type_any(schema); + rewrite_type_array_with_subschemas(schema); + rewrite_disallow(schema); + rewrite_extends(schema); + rewrite_divisible_by(schema); + rewrite_required_property_booleans(schema); + rewrite_dependencies_string_form(schema); + + if (schema.defines("$schema") && schema.at("$schema").is_string() && + schema.at("$schema").to_string() == DRAFT_3_URL) { + schema.assign("$schema", sourcemeta::core::JSON{DRAFT_4_URL}); + drop_dialect_overrides(schema, true); + } else { + mark_dialect_override(schema, DRAFT_4_URL); + } + } + +private: + static inline const std::string DRAFT_3_URL{ + "http://json-schema.org/draft-03/schema#"}; + static inline const std::string DRAFT_4_URL{ + "http://json-schema.org/draft-04/schema#"}; + + static auto + has_pending_draft_3_pattern(const sourcemeta::core::JSON &subschema) -> bool { + if (!subschema.is_object()) { + return false; + } + + if (subschema.defines("$schema") && subschema.at("$schema").is_string() && + subschema.at("$schema").to_string() == DRAFT_3_URL) { + return true; + } + + const auto *type_value{subschema.try_at("type")}; + if (type_value != nullptr) { + if (type_value->is_string() && type_value->to_string() == "any") { + return true; + } + if (type_value->is_array()) { + for (const auto &element : type_value->as_array()) { + if (element.is_string() && element.to_string() == "any") { + return true; + } + if (element.is_object()) { + return true; + } + } + } + } + + const auto *disallow_value{subschema.try_at("disallow")}; + if (disallow_value != nullptr && + (disallow_value->is_string() || disallow_value->is_array() || + disallow_value->is_object())) { + return true; + } + + const auto *extends_value{subschema.try_at("extends")}; + if (extends_value != nullptr && + (extends_value->is_array() || extends_value->is_object())) { + return true; + } + + if (subschema.defines("divisibleBy")) { + return true; + } + + const auto *properties{subschema.try_at("properties")}; + if (properties != nullptr && properties->is_object()) { + for (const auto &entry : properties->as_object()) { + if (entry.second.is_object() && entry.second.defines("required") && + entry.second.at("required").is_boolean()) { + return true; + } + } + } + + const auto *dependencies{subschema.try_at("dependencies")}; + if (dependencies != nullptr && dependencies->is_object()) { + for (const auto &entry : dependencies->as_object()) { + if (entry.second.is_string()) { + return true; + } + } + } + + return false; + } + + static auto rewrite_type_any(sourcemeta::core::JSON &schema) -> void { + if (!schema.defines("type")) { + return; + } + auto &type_value{schema.at("type")}; + if (type_value.is_string()) { + if (type_value.to_string() == "any") { + schema.erase("type"); + } + return; + } + if (!type_value.is_array()) { + return; + } + bool collapses{false}; + for (const auto &element : type_value.as_array()) { + if (element.is_string() && element.to_string() == "any") { + collapses = true; + break; + } + } + if (collapses) { + schema.erase("type"); + } + } + + static auto rewrite_type_array_with_subschemas(sourcemeta::core::JSON &schema) + -> void { + if (!schema.defines("type")) { + return; + } + auto &type_value{schema.at("type")}; + if (!type_value.is_array()) { + return; + } + bool has_subschema{false}; + for (const auto &element : type_value.as_array()) { + if (element.is_object()) { + has_subschema = true; + break; + } + } + if (!has_subschema) { + return; + } + + auto branches{sourcemeta::core::JSON::make_array()}; + for (const auto &element : type_value.as_array()) { + if (element.is_string()) { + auto branch{sourcemeta::core::JSON::make_object()}; + branch.assign("type", element); + branches.push_back(std::move(branch)); + } else if (element.is_object()) { + branches.push_back(element); + } + } + schema.erase("type"); + schema.assign("anyOf", std::move(branches)); + } + + static auto type_string_to_branch(const std::string &type_name) + -> sourcemeta::core::JSON { + auto branch{sourcemeta::core::JSON::make_object()}; + branch.assign("type", sourcemeta::core::JSON{type_name}); + return branch; + } + + static auto rewrite_disallow(sourcemeta::core::JSON &schema) -> void { + if (!schema.defines("disallow") || schema.defines("not")) { + return; + } + + const auto &disallow{schema.at("disallow")}; + if (!disallow.is_string() && !disallow.is_array() && + !disallow.is_object()) { + return; + } + + if (disallow.is_string() && disallow.to_string() == "any") { + schema.erase("disallow"); + schema.assign("not", sourcemeta::core::JSON::make_object()); + return; + } + + if (disallow.is_array()) { + for (const auto &element : disallow.as_array()) { + if (element.is_string() && element.to_string() == "any") { + schema.erase("disallow"); + schema.assign("not", sourcemeta::core::JSON::make_object()); + return; + } + } + } + + auto negated{sourcemeta::core::JSON::make_object()}; + if (disallow.is_string()) { + negated.assign("type", disallow); + } else if (disallow.is_array()) { + bool has_subschema{false}; + for (const auto &element : disallow.as_array()) { + if (element.is_object()) { + has_subschema = true; + break; + } + } + if (!has_subschema) { + negated.assign("type", disallow); + } else { + auto branches{sourcemeta::core::JSON::make_array()}; + for (const auto &element : disallow.as_array()) { + if (element.is_string()) { + branches.push_back(type_string_to_branch(element.to_string())); + } else if (element.is_object()) { + branches.push_back(element); + } + } + negated.assign("anyOf", std::move(branches)); + } + } else { + negated = disallow; + } + + schema.erase("disallow"); + schema.assign("not", std::move(negated)); + } + + static auto rewrite_extends(sourcemeta::core::JSON &schema) -> void { + if (!schema.defines("extends") || schema.defines("allOf")) { + return; + } + + const auto &extends{schema.at("extends")}; + if (!extends.is_array() && !extends.is_object()) { + return; + } + + auto value{extends}; + schema.erase("extends"); + + if (value.is_array()) { + schema.assign("allOf", std::move(value)); + return; + } + + auto array{sourcemeta::core::JSON::make_array()}; + array.push_back(std::move(value)); + schema.assign("allOf", std::move(array)); + } + + static auto rewrite_divisible_by(sourcemeta::core::JSON &schema) -> void { + if (!schema.defines("divisibleBy") || schema.defines("multipleOf")) { + return; + } + schema.rename("divisibleBy", "multipleOf"); + } + + static auto rewrite_required_property_booleans(sourcemeta::core::JSON &schema) + -> void { + if (!schema.defines("properties") || !schema.at("properties").is_object()) { + return; + } + + std::vector newly_required; + auto &properties{schema.at("properties")}; + std::vector property_keys; + property_keys.reserve(properties.size()); + for (const auto &entry : properties.as_object()) { + property_keys.push_back(entry.first); + } + + for (const auto &key : property_keys) { + auto &property{properties.at(key)}; + if (!property.is_object() || !property.defines("required")) { + continue; + } + const auto &required_value{property.at("required")}; + if (!required_value.is_boolean()) { + continue; + } + const bool is_required{required_value.to_boolean()}; + property.erase("required"); + if (is_required) { + newly_required.push_back(key); + } + } + + if (newly_required.empty()) { + return; + } + + if (!schema.defines("required") || !schema.at("required").is_array()) { + auto fresh{sourcemeta::core::JSON::make_array()}; + for (const auto &name : newly_required) { + fresh.push_back(sourcemeta::core::JSON{name}); + } + schema.try_assign_before("required", fresh, "properties"); + return; + } + + auto &existing{schema.at("required")}; + std::set already; + for (const auto &item : existing.as_array()) { + if (item.is_string()) { + already.insert(item.to_string()); + } + } + for (const auto &name : newly_required) { + if (already.contains(name)) { + continue; + } + existing.push_back(sourcemeta::core::JSON{name}); + already.insert(name); + } + } + + static auto rewrite_dependencies_string_form(sourcemeta::core::JSON &schema) + -> void { + if (!schema.defines("dependencies") || + !schema.at("dependencies").is_object()) { + return; + } + auto &dependencies{schema.at("dependencies")}; + std::vector string_keys; + for (const auto &entry : dependencies.as_object()) { + if (entry.second.is_string()) { + string_keys.push_back(entry.first); + } + } + for (const auto &key : string_keys) { + const auto value{dependencies.at(key).to_string()}; + auto array{sourcemeta::core::JSON::make_array()}; + array.push_back(sourcemeta::core::JSON{value}); + dependencies.assign(key, std::move(array)); + } + } + + static auto + is_strict_descendant(const sourcemeta::core::WeakPointer &ancestor, + const sourcemeta::core::WeakPointer &candidate) -> bool { + if (candidate.size() <= ancestor.size()) { + return false; + } + for (std::size_t index{0}; index < ancestor.size(); ++index) { + if (!(ancestor.at(index) == candidate.at(index))) { + return false; + } + } + return true; + } +}; diff --git a/vendor/blaze/src/alterschema/upgrade/upgrade_draft_4_to_draft_6.h b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_4_to_draft_6.h new file mode 100644 index 000000000..65798d0d6 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_4_to_draft_6.h @@ -0,0 +1,624 @@ +class UpgradeDraft4ToDraft6 final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UpgradeDraft4ToDraft6() + : SchemaTransformRule{"upgrade_draft_4_to_draft_6", ""} {}; + + [[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 &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_4) && + schema.is_object()); + + const bool is_resource_scope = + location.type == + sourcemeta::core::SchemaFrame::LocationType::Resource || + location.pointer.empty(); + + const bool sanitization_branch = + is_resource_scope && resource_needs_anchor_sanitization(schema); + + const bool other_branch = has_pending_draft_4_pattern(schema); + + const bool root_via_default_dialect = + location.pointer.empty() && !schema.defines("$schema"); + + ONLY_CONTINUE_IF(sanitization_branch || other_branch || + root_via_default_dialect); + + if (!sanitization_branch && other_branch && + enclosing_resource_has_pending_sanitization(location, root, frame)) { + return false; + } + + if (!sanitization_branch) { + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + + if (!is_strict_descendant(location.pointer, entry.second.pointer)) { + continue; + } + + const auto entry_pointer{ + sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &entry_schema{sourcemeta::core::get(root, entry_pointer)}; + + if (entry_schema.is_object() && entry_schema.defines("$ref")) { + continue; + } + + if (has_pending_draft_4_pattern(entry_schema)) { + return false; + } + } + } + + if (sanitization_branch) { + return Result{std::vector{}, + std::string{"sanitize"}}; + } + + return true; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &result) const + -> void override { + if (result.description.has_value()) { + const auto renames{build_resource_rename_map(schema)}; + std::optional resource_base; + if (schema.defines("id") && schema.at("id").is_string()) { + const sourcemeta::core::URI uri{schema.at("id").to_string()}; + const auto without_fragment{uri.recompose_without_fragment()}; + if (without_fragment.has_value() && !without_fragment.value().empty()) { + resource_base = without_fragment.value(); + } + } + apply_anchor_renames_in_resource(schema, true, renames, resource_base); + if (resource_has_descendant_with_pending_pattern(schema, true)) { + return; + } + } + + if (schema.defines("id") && schema.at("id").is_string() && + !schema.defines("$id")) { + schema.rename("id", "$id"); + } + + if (schema.defines("exclusiveMinimum") && + schema.at("exclusiveMinimum").is_boolean()) { + const bool exclusive{schema.at("exclusiveMinimum").to_boolean()}; + schema.erase("exclusiveMinimum"); + if (exclusive && schema.defines("minimum") && + schema.at("minimum").is_number()) { + schema.rename("minimum", "exclusiveMinimum"); + } + } + + if (schema.defines("exclusiveMaximum") && + schema.at("exclusiveMaximum").is_boolean()) { + const bool exclusive{schema.at("exclusiveMaximum").to_boolean()}; + schema.erase("exclusiveMaximum"); + if (exclusive && schema.defines("maximum") && + schema.at("maximum").is_number()) { + schema.rename("maximum", "exclusiveMaximum"); + } + } + + if (schema.defines("$schema") && schema.at("$schema").is_string() && + schema.at("$schema").to_string() == DRAFT_4_URL) { + schema.assign("$schema", sourcemeta::core::JSON{DRAFT_6_URL}); + drop_dialect_overrides(schema, true); + } else { + mark_dialect_override(schema, DRAFT_6_URL); + } + } + +private: + static inline const std::string DRAFT_4_URL{ + "http://json-schema.org/draft-04/schema#"}; + static inline const std::string DRAFT_6_URL{ + "http://json-schema.org/draft-06/schema#"}; + static inline const std::array PROMOTED_KEYWORDS{ + {"const", "contains", "propertyNames", "examples"}}; + + static auto + has_pending_draft_4_pattern(const sourcemeta::core::JSON &subschema) -> bool { + if (!subschema.is_object()) { + return false; + } + + if (subschema.defines("$schema") && subschema.at("$schema").is_string() && + subschema.at("$schema").to_string() == DRAFT_4_URL) { + return true; + } + + if (subschema.defines("id") && subschema.at("id").is_string() && + !subschema.defines("$id")) { + const auto fragment{extract_id_fragment(subschema.at("id"))}; + if (!fragment.has_value() || fragment.value().empty() || + is_strict_plain_name(fragment.value())) { + return true; + } + } + + const auto *exclusive_minimum{subschema.try_at("exclusiveMinimum")}; + if (exclusive_minimum != nullptr && exclusive_minimum->is_boolean()) { + return true; + } + + const auto *exclusive_maximum{subschema.try_at("exclusiveMaximum")}; + if (exclusive_maximum != nullptr && exclusive_maximum->is_boolean()) { + return true; + } + + for (const auto &keyword : PROMOTED_KEYWORDS) { + if (subschema.defines(std::string{keyword})) { + return true; + } + } + + return false; + } + + static auto + is_strict_descendant(const sourcemeta::core::WeakPointer &ancestor, + const sourcemeta::core::WeakPointer &candidate) -> bool { + if (candidate.size() <= ancestor.size()) { + return false; + } + for (std::size_t index{0}; index < ancestor.size(); ++index) { + if (!(ancestor.at(index) == candidate.at(index))) { + return false; + } + } + return true; + } + + static auto is_strict_plain_name_first_char(const char character) -> bool { + return (character >= 'A' && character <= 'Z') || + (character >= 'a' && character <= 'z'); + } + + static auto is_strict_plain_name_body_char(const char character) -> bool { + return (character >= 'A' && character <= 'Z') || + (character >= 'a' && character <= 'z') || + (character >= '0' && character <= '9') || character == '_' || + character == ':' || character == '.' || character == '-'; + } + + static auto is_strict_plain_name(const std::string_view fragment) -> bool { + if (fragment.empty()) { + return false; + } + if (!is_strict_plain_name_first_char(fragment.front())) { + return false; + } + for (std::size_t index{1}; index < fragment.size(); ++index) { + if (!is_strict_plain_name_body_char(fragment[index])) { + return false; + } + } + return true; + } + + static auto sanitize_anchor_name(const std::string_view original, + const std::set &existing_names) + -> std::string { + static const AnchorCharPolicy POLICY{ + .is_valid_first = &is_strict_plain_name_first_char, + .is_valid_body = &is_strict_plain_name_body_char}; + return sanitize_anchor_with_policy(original, existing_names, POLICY); + } + + static auto extract_id_fragment(const sourcemeta::core::JSON &id_value) + -> std::optional { + if (!id_value.is_string()) { + return std::nullopt; + } + const sourcemeta::core::URI uri{id_value.to_string()}; + const auto fragment{uri.fragment()}; + if (!fragment.has_value()) { + return std::nullopt; + } + return std::string{fragment.value()}; + } + + static auto + subschema_id_fragment_is_invalid(const sourcemeta::core::JSON &subschema) + -> bool { + if (!subschema.is_object() || !subschema.defines("id") || + !subschema.at("id").is_string()) { + return false; + } + const auto fragment{extract_id_fragment(subschema.at("id"))}; + if (!fragment.has_value() || fragment.value().empty()) { + return false; + } + return !is_strict_plain_name(fragment.value()); + } + + static auto + subschema_starts_sub_resource(const sourcemeta::core::JSON &subschema) + -> bool { + if (!subschema.is_object() || !subschema.defines("id") || + !subschema.at("id").is_string()) { + return false; + } + const sourcemeta::core::URI uri{subschema.at("id").to_string()}; + if (uri.is_fragment_only()) { + return false; + } + const auto without_fragment{uri.recompose_without_fragment()}; + return without_fragment.has_value() && !without_fragment.value().empty(); + } + + static auto collect_resource_anchors(const sourcemeta::core::JSON &subschema, + const bool is_root, + std::set &result) -> void { + if (!subschema.is_object()) { + return; + } + + if (!is_root && subschema_starts_sub_resource(subschema)) { + return; + } + + if (subschema.defines("id") && subschema.at("id").is_string()) { + const auto fragment{extract_id_fragment(subschema.at("id"))}; + if (fragment.has_value() && !fragment.value().empty()) { + result.insert(fragment.value()); + } + } + + for (const std::string_view object_keyword : + {"definitions", "properties", "patternProperties", "dependencies"}) { + if (subschema.defines(std::string{object_keyword}) && + subschema.at(std::string{object_keyword}).is_object()) { + for (const auto &entry : + subschema.at(std::string{object_keyword}).as_object()) { + collect_resource_anchors(entry.second, false, result); + } + } + } + + for (const std::string_view array_keyword : {"allOf", "anyOf", "oneOf"}) { + if (subschema.defines(std::string{array_keyword}) && + subschema.at(std::string{array_keyword}).is_array()) { + for (const auto &item : + subschema.at(std::string{array_keyword}).as_array()) { + collect_resource_anchors(item, false, result); + } + } + } + + for (const std::string_view single_keyword : + {"additionalProperties", "additionalItems", "not"}) { + if (subschema.defines(std::string{single_keyword})) { + collect_resource_anchors(subschema.at(std::string{single_keyword}), + false, result); + } + } + + if (subschema.defines("items")) { + const auto &items{subschema.at("items")}; + if (items.is_array()) { + for (const auto &item : items.as_array()) { + collect_resource_anchors(item, false, result); + } + } else { + collect_resource_anchors(items, false, result); + } + } + } + + static auto collect_invalid_anchors(const sourcemeta::core::JSON &subschema, + const bool is_root, + std::vector &result) + -> void { + if (!subschema.is_object()) { + return; + } + + if (!is_root && subschema_starts_sub_resource(subschema)) { + return; + } + + if (subschema_id_fragment_is_invalid(subschema)) { + const auto fragment{extract_id_fragment(subschema.at("id"))}; + result.push_back(fragment.value()); + } + + for (const std::string_view object_keyword : + {"definitions", "properties", "patternProperties", "dependencies"}) { + if (subschema.defines(std::string{object_keyword}) && + subschema.at(std::string{object_keyword}).is_object()) { + for (const auto &entry : + subschema.at(std::string{object_keyword}).as_object()) { + collect_invalid_anchors(entry.second, false, result); + } + } + } + + for (const std::string_view array_keyword : {"allOf", "anyOf", "oneOf"}) { + if (subschema.defines(std::string{array_keyword}) && + subschema.at(std::string{array_keyword}).is_array()) { + for (const auto &item : + subschema.at(std::string{array_keyword}).as_array()) { + collect_invalid_anchors(item, false, result); + } + } + } + + for (const std::string_view single_keyword : + {"additionalProperties", "additionalItems", "not"}) { + if (subschema.defines(std::string{single_keyword})) { + collect_invalid_anchors(subschema.at(std::string{single_keyword}), + false, result); + } + } + + if (subschema.defines("items")) { + const auto &items{subschema.at("items")}; + if (items.is_array()) { + for (const auto &item : items.as_array()) { + collect_invalid_anchors(item, false, result); + } + } else { + collect_invalid_anchors(items, false, result); + } + } + } + + static auto + build_resource_rename_map(const sourcemeta::core::JSON &resource_root) + -> std::map { + std::set existing; + collect_resource_anchors(resource_root, true, existing); + + std::vector invalid; + collect_invalid_anchors(resource_root, true, invalid); + + std::map renames; + std::set in_use{existing}; + for (const auto &original : invalid) { + if (renames.contains(original)) { + continue; + } + in_use.erase(original); + const auto sanitized{sanitize_anchor_name(original, in_use)}; + renames.emplace(original, sanitized); + in_use.insert(sanitized); + } + return renames; + } + + static auto resource_has_descendant_with_pending_pattern( + const sourcemeta::core::JSON &subschema, const bool is_root) -> bool { + if (!subschema.is_object()) { + return false; + } + if (!is_root && subschema_starts_sub_resource(subschema)) { + return false; + } + if (!is_root && has_pending_draft_4_pattern(subschema)) { + return true; + } + + for (const std::string_view object_keyword : + {"definitions", "properties", "patternProperties", "dependencies"}) { + if (subschema.defines(std::string{object_keyword}) && + subschema.at(std::string{object_keyword}).is_object()) { + for (const auto &entry : + subschema.at(std::string{object_keyword}).as_object()) { + if (resource_has_descendant_with_pending_pattern(entry.second, + false)) { + return true; + } + } + } + } + + for (const std::string_view array_keyword : {"allOf", "anyOf", "oneOf"}) { + if (subschema.defines(std::string{array_keyword}) && + subschema.at(std::string{array_keyword}).is_array()) { + for (const auto &item : + subschema.at(std::string{array_keyword}).as_array()) { + if (resource_has_descendant_with_pending_pattern(item, false)) { + return true; + } + } + } + } + + for (const std::string_view single_keyword : + {"additionalProperties", "additionalItems", "not"}) { + if (subschema.defines(std::string{single_keyword})) { + if (resource_has_descendant_with_pending_pattern( + subschema.at(std::string{single_keyword}), false)) { + return true; + } + } + } + + if (subschema.defines("items")) { + const auto &items{subschema.at("items")}; + if (items.is_array()) { + for (const auto &item : items.as_array()) { + if (resource_has_descendant_with_pending_pattern(item, false)) { + return true; + } + } + } else if (resource_has_descendant_with_pending_pattern(items, false)) { + return true; + } + } + + return false; + } + + static auto apply_anchor_renames_in_resource( + sourcemeta::core::JSON &subschema, const bool is_root, + const std::map &renames, + const std::optional &resource_base) -> void { + if (!subschema.is_object()) { + return; + } + + if (!is_root && subschema_starts_sub_resource(subschema)) { + return; + } + + if (subschema.defines("id") && subschema.at("id").is_string()) { + const auto &id_string{subschema.at("id").to_string()}; + const sourcemeta::core::URI uri{id_string}; + const auto fragment{uri.fragment()}; + if (fragment.has_value() && !fragment.value().empty()) { + const auto rename_iter{renames.find(std::string{fragment.value()})}; + if (rename_iter != renames.end()) { + if (uri.is_fragment_only()) { + subschema.assign("id", + sourcemeta::core::JSON{"#" + rename_iter->second}); + } else { + const auto without_fragment{uri.recompose_without_fragment()}; + subschema.assign( + "id", sourcemeta::core::JSON{(without_fragment.has_value() + ? without_fragment.value() + : std::string{}) + + "#" + rename_iter->second}); + } + } + } + } + + if (subschema.defines("$ref") && subschema.at("$ref").is_string()) { + const auto &ref_string{subschema.at("$ref").to_string()}; + const sourcemeta::core::URI ref_uri{ref_string}; + const auto fragment{ref_uri.fragment()}; + if (fragment.has_value() && + renames.contains(std::string{fragment.value()})) { + const auto without_fragment{ref_uri.recompose_without_fragment()}; + const bool same_base = + ref_uri.is_fragment_only() || + (resource_base.has_value() && without_fragment.has_value() && + without_fragment.value() == resource_base.value()); + if (same_base) { + const auto &new_name{renames.at(std::string{fragment.value()})}; + subschema.assign( + "$ref", sourcemeta::core::JSON{ + ref_uri.is_fragment_only() + ? "#" + new_name + : (without_fragment.value() + "#" + new_name)}); + } + } + } + + for (const std::string_view object_keyword : + {"definitions", "properties", "patternProperties", "dependencies"}) { + if (subschema.defines(std::string{object_keyword}) && + subschema.at(std::string{object_keyword}).is_object()) { + std::vector keys; + keys.reserve(subschema.at(std::string{object_keyword}).size()); + for (const auto &entry : + subschema.at(std::string{object_keyword}).as_object()) { + keys.push_back(entry.first); + } + for (const auto &key : keys) { + apply_anchor_renames_in_resource( + subschema.at(std::string{object_keyword}).at(key), false, renames, + resource_base); + } + } + } + + for (const std::string_view array_keyword : {"allOf", "anyOf", "oneOf"}) { + if (subschema.defines(std::string{array_keyword}) && + subschema.at(std::string{array_keyword}).is_array()) { + auto &array_value{subschema.at(std::string{array_keyword})}; + for (std::size_t index{0}; index < array_value.size(); ++index) { + apply_anchor_renames_in_resource(array_value.at(index), false, + renames, resource_base); + } + } + } + + for (const std::string_view single_keyword : + {"additionalProperties", "additionalItems", "not"}) { + if (subschema.defines(std::string{single_keyword})) { + apply_anchor_renames_in_resource( + subschema.at(std::string{single_keyword}), false, renames, + resource_base); + } + } + + if (subschema.defines("items")) { + auto &items{subschema.at("items")}; + if (items.is_array()) { + for (std::size_t index{0}; index < items.size(); ++index) { + apply_anchor_renames_in_resource(items.at(index), false, renames, + resource_base); + } + } else { + apply_anchor_renames_in_resource(items, false, renames, resource_base); + } + } + } + + static auto resource_needs_anchor_sanitization( + const sourcemeta::core::JSON &resource_root) -> bool { + std::vector invalid; + collect_invalid_anchors(resource_root, true, invalid); + return !invalid.empty(); + } + + static auto enclosing_resource_has_pending_sanitization( + const sourcemeta::core::SchemaFrame::Location &location, + const sourcemeta::core::JSON &root, + const sourcemeta::core::SchemaFrame &frame) -> bool { + std::optional closest; + for (const auto &entry : frame.locations()) { + const bool entry_is_resource_scope = + entry.second.type == + sourcemeta::core::SchemaFrame::LocationType::Resource || + entry.second.pointer.empty(); + if (!entry_is_resource_scope) { + continue; + } + if (entry.second.pointer.size() > location.pointer.size()) { + continue; + } + bool is_ancestor{true}; + for (std::size_t index{0}; index < entry.second.pointer.size(); ++index) { + if (!(entry.second.pointer.at(index) == location.pointer.at(index))) { + is_ancestor = false; + break; + } + } + if (!is_ancestor) { + continue; + } + if (!closest.has_value() || + entry.second.pointer.size() > closest.value().size()) { + closest = entry.second.pointer; + } + } + if (!closest.has_value()) { + return false; + } + const auto closest_pointer{sourcemeta::core::to_pointer(closest.value())}; + const auto &resource_schema{sourcemeta::core::get(root, closest_pointer)}; + return resource_needs_anchor_sanitization(resource_schema); + } +}; diff --git a/vendor/blaze/src/alterschema/upgrade/upgrade_draft_6_to_draft_7.h b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_6_to_draft_7.h new file mode 100644 index 000000000..0edd739d8 --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_6_to_draft_7.h @@ -0,0 +1,106 @@ +class UpgradeDraft6ToDraft7 final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UpgradeDraft6ToDraft7() + : SchemaTransformRule{"upgrade_draft_6_to_draft_7", ""} {}; + + [[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 &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_6) && + subschema_at_dialect(schema, location, DRAFT_6_URL)); + + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + + if (!is_strict_descendant(location.pointer, entry.second.pointer)) { + continue; + } + + const auto entry_pointer{ + sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &entry_schema{sourcemeta::core::get(root, entry_pointer)}; + + if (entry_schema.is_object() && entry_schema.defines("$ref")) { + continue; + } + + if (has_pending_pattern(entry_schema)) { + return false; + } + } + + return true; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + if (schema.defines("$schema") && schema.at("$schema").is_string() && + schema.at("$schema").to_string() == DRAFT_6_URL) { + schema.assign("$schema", sourcemeta::core::JSON{DRAFT_7_URL}); + drop_dialect_overrides(schema, true); + } else { + mark_dialect_override(schema, DRAFT_7_URL); + } + } + +private: + static inline const std::string DRAFT_4_URL{ + "http://json-schema.org/draft-04/schema#"}; + static inline const std::string DRAFT_6_URL{ + "http://json-schema.org/draft-06/schema#"}; + static inline const std::string DRAFT_7_URL{ + "http://json-schema.org/draft-07/schema#"}; + static inline const std::array PROMOTED_KEYWORDS{ + {"$comment", "if", "then", "else", "readOnly", "writeOnly", + "contentMediaType", "contentEncoding"}}; + + static auto has_pending_pattern(const sourcemeta::core::JSON &subschema) + -> bool { + if (!subschema.is_object()) { + return false; + } + + if (subschema.defines("$schema") && subschema.at("$schema").is_string()) { + const auto &dialect{subschema.at("$schema").to_string()}; + if (dialect == DRAFT_4_URL || dialect == DRAFT_6_URL) { + return true; + } + } + + for (const auto &keyword : PROMOTED_KEYWORDS) { + if (subschema.defines(std::string{keyword})) { + return true; + } + } + + return false; + } + + static auto + is_strict_descendant(const sourcemeta::core::WeakPointer &ancestor, + const sourcemeta::core::WeakPointer &candidate) -> bool { + if (candidate.size() <= ancestor.size()) { + return false; + } + for (std::size_t index{0}; index < ancestor.size(); ++index) { + if (!(ancestor.at(index) == candidate.at(index))) { + return false; + } + } + return true; + } +}; diff --git a/vendor/blaze/src/alterschema/upgrade/upgrade_draft_7_to_draft_2019_09.h b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_7_to_draft_2019_09.h new file mode 100644 index 000000000..07cedbacb --- /dev/null +++ b/vendor/blaze/src/alterschema/upgrade/upgrade_draft_7_to_draft_2019_09.h @@ -0,0 +1,393 @@ +class UpgradeDraft7To201909 final : public SchemaTransformRule { +public: + using mutates = std::true_type; + using reframe_after_transform = std::true_type; + UpgradeDraft7To201909() + : SchemaTransformRule{"upgrade_draft_7_to_2019_09", ""} {}; + + [[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 &, + const sourcemeta::core::SchemaResolver &) const + -> SchemaTransformRule::Result override { + ONLY_CONTINUE_IF( + vocabularies.contains(Vocabularies::Known::JSON_Schema_Draft_7) && + schema.is_object()); + + ONLY_CONTINUE_IF(subschema_at_dialect(schema, location, DRAFT_7_URL) || + has_actionable_id_fragment(schema) || + has_actionable_dependencies(schema) || + has_actionable_ref_siblings(schema)); + + for (const auto &entry : frame.locations()) { + if (entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Resource && + entry.second.type != + sourcemeta::core::SchemaFrame::LocationType::Subschema) { + continue; + } + + if (entry.second.pointer.size() <= location.pointer.size() || + !entry.second.pointer.starts_with(location.pointer)) { + continue; + } + + const auto entry_pointer{ + sourcemeta::core::to_pointer(entry.second.pointer)}; + const auto &entry_schema{sourcemeta::core::get(root, entry_pointer)}; + + if (has_descendant_pending_pattern(entry_schema, entry.second.dialect)) { + return false; + } + } + + return true; + } + + auto transform(sourcemeta::core::JSON &schema, const Result &) const + -> void override { + this->renames_.clear(); + this->prefix_ref_siblings(schema); + this->split_id_fragment(schema); + this->split_dependencies(schema); + if (bump_schema(schema)) { + drop_dialect_overrides(schema, true); + } else { + mark_dialect_override(schema, DRAFT_2019_09_URL); + } + } + + [[nodiscard]] auto rereference(const std::string_view, + const sourcemeta::core::Pointer &, + const sourcemeta::core::Pointer &target, + const sourcemeta::core::Pointer ¤t) const + -> sourcemeta::core::Pointer override { + for (const auto &[old_pointer, new_pointer] : this->renames_) { + const auto result{target.rebase(current.concat(old_pointer), + current.concat(new_pointer))}; + if (result != target) { + return result; + } + } + + return target; + } + +private: + static constexpr std::string_view DRAFT_4_URL{ + "http://json-schema.org/draft-04/schema#"}; + static constexpr std::string_view DRAFT_6_URL{ + "http://json-schema.org/draft-06/schema#"}; + static constexpr std::string_view DRAFT_7_URL{ + "http://json-schema.org/draft-07/schema#"}; + static constexpr std::string_view DRAFT_2019_09_URL{ + "https://json-schema.org/draft/2019-09/schema"}; + + static inline const std::array SHADOW_EXEMPT_KEYWORDS{ + {"$schema", "$id", "title", "description", "default", "examples", + "$comment", "readOnly", "writeOnly", "deprecated", "contentMediaType", + "contentEncoding"}}; + + static inline const std::array + PROMOTED_2019_09_KEYWORDS{{"$anchor", "$recursiveAnchor", "$recursiveRef", + "$vocabulary", "$defs", "dependentSchemas", + "dependentRequired", "unevaluatedItems", + "unevaluatedProperties", "maxContains", + "minContains", "contentSchema", "deprecated"}}; + + static inline const std::array PROMOTED_DRAFT_7_KEYWORDS{ + {"$comment", "if", "then", "else", "readOnly", "writeOnly", + "contentMediaType", "contentEncoding"}}; + + static inline const std::array PROMOTED_DRAFT_6_KEYWORDS{ + {"const", "contains", "propertyNames", "examples"}}; + + mutable std::vector< + std::pair> + renames_; + + static auto is_shadow_exempt(const std::string_view keyword) -> bool { + return std::ranges::any_of( + SHADOW_EXEMPT_KEYWORDS, + [&keyword](const auto &candidate) { return candidate == keyword; }); + } + + static auto is_plain_name_fragment(const std::string_view fragment) -> bool { + if (fragment.empty()) { + return false; + } + + for (const auto character : fragment) { + const bool is_alpha{(character >= 'A' && character <= 'Z') || + (character >= 'a' && character <= 'z')}; + const bool is_digit{character >= '0' && character <= '9'}; + const bool is_punct{character == '_' || character == ':' || + character == '.' || character == '-'}; + if (!is_alpha && !is_digit && !is_punct) { + return false; + } + } + + return true; + } + + static auto + has_actionable_id_fragment(const sourcemeta::core::JSON &subschema) -> bool { + if (!(subschema.defines("$id") && subschema.at("$id").is_string())) { + return false; + } + + const sourcemeta::core::URI uri{subschema.at("$id").to_string()}; + const auto fragment{uri.fragment()}; + return fragment.has_value() && (fragment.value().empty() || + is_plain_name_fragment(fragment.value())); + } + + static auto + has_actionable_dependencies(const sourcemeta::core::JSON &subschema) -> bool { + if (!(subschema.defines("dependencies") && + subschema.at("dependencies").is_object())) { + return false; + } + + if (subschema.defines("dependentRequired") || + subschema.defines("dependentSchemas")) { + return false; + } + + for (const auto &entry : subschema.at("dependencies").as_object()) { + if (!entry.second.is_array() && !entry.second.is_object() && + !entry.second.is_boolean()) { + return false; + } + } + + return true; + } + + static auto + has_actionable_ref_siblings(const sourcemeta::core::JSON &subschema) -> bool { + if (!subschema.defines("$ref")) { + return false; + } + + for (const auto &entry : subschema.as_object()) { + if (entry.first == "$ref" || is_shadow_exempt(entry.first) || + entry.first.starts_with("x-")) { + continue; + } + return true; + } + + return false; + } + + auto prefix_ref_siblings(sourcemeta::core::JSON &schema) const -> void { + if (!schema.defines("$ref")) { + return; + } + + std::vector siblings_to_prefix; + for (const auto &entry : schema.as_object()) { + if (entry.first == "$ref" || is_shadow_exempt(entry.first) || + entry.first.starts_with("x-")) { + continue; + } + + siblings_to_prefix.push_back(entry.first); + } + + for (const auto &keyword : siblings_to_prefix) { + std::string prefixed_name{"x-" + keyword}; + while (schema.defines(prefixed_name)) { + prefixed_name.insert(0, "x-"); + } + + this->renames_.emplace_back(sourcemeta::core::Pointer{keyword}, + sourcemeta::core::Pointer{prefixed_name}); + schema.rename(keyword, std::move(prefixed_name)); + } + } + + static auto split_id_fragment(sourcemeta::core::JSON &schema) -> void { + if (!(schema.defines("$id") && schema.at("$id").is_string())) { + return; + } + + const sourcemeta::core::URI uri{schema.at("$id").to_string()}; + const auto fragment{uri.fragment()}; + if (!fragment.has_value()) { + return; + } + + const auto &fragment_value{fragment.value()}; + const bool plain_name{is_plain_name_fragment(fragment_value)}; + + if (uri.is_fragment_only()) { + if (plain_name) { + schema.rename("$id", "$anchor"); + schema.at("$anchor").into( + sourcemeta::core::JSON{std::string{fragment_value}}); + } else if (fragment_value.empty()) { + schema.erase("$id"); + } + return; + } + + if (!plain_name && !fragment_value.empty()) { + return; + } + + const auto without_fragment{uri.recompose_without_fragment()}; + if (!without_fragment.has_value()) { + return; + } + + schema.assign("$id", sourcemeta::core::JSON{without_fragment.value()}); + if (plain_name) { + schema.assign("$anchor", + sourcemeta::core::JSON{std::string{fragment_value}}); + } + } + + auto split_dependencies(sourcemeta::core::JSON &schema) const -> void { + if (!has_actionable_dependencies(schema)) { + return; + } + + auto dependent_required{sourcemeta::core::JSON::make_object()}; + auto dependent_schemas{sourcemeta::core::JSON::make_object()}; + + for (const auto &entry : schema.at("dependencies").as_object()) { + if (entry.second.is_array()) { + dependent_required.assign(entry.first, entry.second); + } else { + dependent_schemas.assign(entry.first, entry.second); + } + } + + if (dependent_required.empty() && dependent_schemas.empty()) { + schema.erase("dependencies"); + return; + } + + if (!dependent_required.empty() && !dependent_schemas.empty()) { + for (const auto &entry : dependent_schemas.as_object()) { + this->renames_.emplace_back( + sourcemeta::core::Pointer{"dependencies", entry.first}, + sourcemeta::core::Pointer{"dependentSchemas", entry.first}); + } + for (const auto &entry : dependent_required.as_object()) { + this->renames_.emplace_back( + sourcemeta::core::Pointer{"dependencies", entry.first}, + sourcemeta::core::Pointer{"dependentRequired", entry.first}); + } + schema.try_assign_before("dependentSchemas", dependent_schemas, + "dependencies"); + schema.rename("dependencies", "dependentRequired"); + schema.at("dependentRequired").into(std::move(dependent_required)); + return; + } + + if (!dependent_schemas.empty()) { + this->renames_.emplace_back( + sourcemeta::core::Pointer{"dependencies"}, + sourcemeta::core::Pointer{"dependentSchemas"}); + schema.rename("dependencies", "dependentSchemas"); + schema.at("dependentSchemas").into(std::move(dependent_schemas)); + return; + } + + this->renames_.emplace_back(sourcemeta::core::Pointer{"dependencies"}, + sourcemeta::core::Pointer{"dependentRequired"}); + schema.rename("dependencies", "dependentRequired"); + schema.at("dependentRequired").into(std::move(dependent_required)); + } + + static auto bump_schema(sourcemeta::core::JSON &schema) -> bool { + if (schema.defines("$schema") && schema.at("$schema").is_string() && + schema.at("$schema").to_string() == DRAFT_7_URL) { + schema.assign("$schema", + sourcemeta::core::JSON{std::string{DRAFT_2019_09_URL}}); + return true; + } + return false; + } + + static auto has_pending_pattern(const sourcemeta::core::JSON &subschema) + -> bool { + if (!subschema.is_object()) { + return false; + } + + if (current_dialect_or_override(subschema) == DRAFT_7_URL) { + return true; + } + + return has_actionable_id_fragment(subschema) || + has_actionable_dependencies(subschema) || + has_actionable_ref_siblings(subschema); + } + + static auto + has_descendant_pending_pattern(const sourcemeta::core::JSON &subschema, + const std::string_view descendant_dialect) + -> bool { + if (!subschema.is_object()) { + return false; + } + + if (subschema.defines("$schema") && subschema.at("$schema").is_string()) { + const auto &dialect{subschema.at("$schema").to_string()}; + if (dialect == DRAFT_4_URL || dialect == DRAFT_6_URL || + dialect == DRAFT_7_URL) { + return true; + } + } + + if (subschema.defines("id") && subschema.at("id").is_string() && + !subschema.defines("$id")) { + return true; + } + + const auto *exclusive_minimum{subschema.try_at("exclusiveMinimum")}; + if (exclusive_minimum != nullptr && exclusive_minimum->is_boolean()) { + return true; + } + + const auto *exclusive_maximum{subschema.try_at("exclusiveMaximum")}; + if (exclusive_maximum != nullptr && exclusive_maximum->is_boolean()) { + return true; + } + + if (descendant_dialect == DRAFT_4_URL) { + for (const auto &keyword : PROMOTED_DRAFT_6_KEYWORDS) { + if (subschema.defines(std::string{keyword})) { + return true; + } + } + } + + if (descendant_dialect == DRAFT_6_URL) { + for (const auto &keyword : PROMOTED_DRAFT_7_KEYWORDS) { + if (subschema.defines(std::string{keyword})) { + return true; + } + } + } + + if (descendant_dialect == DRAFT_7_URL) { + for (const auto &keyword : PROMOTED_2019_09_KEYWORDS) { + if (subschema.defines(std::string{keyword})) { + return true; + } + } + } + + return has_pending_pattern(subschema); + } +}; diff --git a/vendor/blaze/src/compiler/compile_helpers.h b/vendor/blaze/src/compiler/compile_helpers.h index 6de78735c..03984f6d9 100644 --- a/vendor/blaze/src/compiler/compile_helpers.h +++ b/vendor/blaze/src/compiler/compile_helpers.h @@ -355,6 +355,54 @@ is_circular(const sourcemeta::core::SchemaFrame &frame, return false; } +// The set of property names that this schema declares as required at this +// level +inline auto required_properties(const SchemaContext &schema_context) + -> ValueStringSet { + using Known = sourcemeta::core::Vocabularies::Known; + 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)}; + + ValueStringSet result; + + if (imports_validation_vocabulary && schema_context.schema.is_object() && + schema_context.schema.defines("required") && + schema_context.schema.at("required").is_array()) { + for (const auto &entry : schema_context.schema.at("required").as_array()) { + if (entry.is_string()) { + result.insert(entry.to_string()); + } + } + + return result; + } + + const auto imports_draft3_vocabulary{ + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3) || + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3_Hyper)}; + + if (imports_draft3_vocabulary && schema_context.schema.is_object() && + schema_context.schema.defines("properties") && + schema_context.schema.at("properties").is_object()) { + for (const auto &entry : + schema_context.schema.at("properties").as_object()) { + if (entry.second.is_object() && entry.second.defines("required") && + entry.second.at("required").is_boolean() && + entry.second.at("required").to_boolean()) { + result.insert(entry.first); + } + } + } + + return result; +} + } // namespace sourcemeta::blaze #endif diff --git a/vendor/blaze/src/compiler/default_compiler.cc b/vendor/blaze/src/compiler/default_compiler.cc index 54c66f381..c4e23cfc3 100644 --- a/vendor/blaze/src/compiler/default_compiler.cc +++ b/vendor/blaze/src/compiler/default_compiler.cc @@ -2,6 +2,7 @@ #include "default_compiler_2019_09.h" #include "default_compiler_2020_12.h" +#include "default_compiler_draft3.h" #include "default_compiler_draft4.h" #include "default_compiler_draft6.h" #include "default_compiler_draft7.h" @@ -41,6 +42,8 @@ auto sourcemeta::blaze::default_schema_compiler( Known::JSON_Schema_Draft_6_Hyper, Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, + Known::JSON_Schema_Draft_3, + Known::JSON_Schema_Draft_3_Hyper, Known::OpenAPI_3_1_Base, Known::OpenAPI_3_2_Base}; @@ -138,9 +141,9 @@ auto sourcemeta::blaze::default_schema_compiler( // As per compatibility optional test COMPILE(Known::JSON_Schema_2020_12_Applicator, "dependencies", - compiler_draft4_applicator_dependencies); + compiler_draft3_applicator_dependencies); - COMPILE(Known::JSON_Schema_2020_12_Core, "$ref", compiler_draft4_core_ref); + COMPILE(Known::JSON_Schema_2020_12_Core, "$ref", compiler_draft3_core_ref); COMPILE(Known::JSON_Schema_2020_12_Applicator, "allOf", compiler_draft4_applicator_allof); @@ -152,13 +155,13 @@ auto sourcemeta::blaze::default_schema_compiler( compiler_draft4_applicator_not); COMPILE(Known::JSON_Schema_2020_12_Validation, "enum", - compiler_draft4_validation_enum); + compiler_draft3_validation_enum); COMPILE(Known::JSON_Schema_2020_12_Validation, "uniqueItems", - compiler_draft4_validation_uniqueitems); + compiler_draft3_validation_uniqueitems); COMPILE(Known::JSON_Schema_2020_12_Validation, "maxItems", - compiler_draft4_validation_maxitems); + compiler_draft3_validation_maxitems); COMPILE(Known::JSON_Schema_2020_12_Validation, "minItems", - compiler_draft4_validation_minitems); + compiler_draft3_validation_minitems); COMPILE(Known::JSON_Schema_2020_12_Validation, "required", compiler_draft4_validation_required); COMPILE(Known::JSON_Schema_2020_12_Validation, "maxProperties", @@ -166,17 +169,17 @@ auto sourcemeta::blaze::default_schema_compiler( COMPILE(Known::JSON_Schema_2020_12_Validation, "minProperties", compiler_draft4_validation_minproperties); COMPILE(Known::JSON_Schema_2020_12_Validation, "maximum", - compiler_draft4_validation_maximum); + compiler_draft3_validation_maximum); COMPILE(Known::JSON_Schema_2020_12_Validation, "minimum", - compiler_draft4_validation_minimum); + compiler_draft3_validation_minimum); COMPILE(Known::JSON_Schema_2020_12_Validation, "multipleOf", - compiler_draft4_validation_multipleof); + compiler_draft3_validation_divisibleby); COMPILE(Known::JSON_Schema_2020_12_Validation, "maxLength", - compiler_draft4_validation_maxlength); + compiler_draft3_validation_maxlength); COMPILE(Known::JSON_Schema_2020_12_Validation, "minLength", - compiler_draft4_validation_minlength); + compiler_draft3_validation_minlength); COMPILE(Known::JSON_Schema_2020_12_Validation, "pattern", - compiler_draft4_validation_pattern); + compiler_draft3_validation_pattern); // ******************************************** // 2019-09 @@ -239,9 +242,9 @@ auto sourcemeta::blaze::default_schema_compiler( // As per compatibility optional test COMPILE(Known::JSON_Schema_2019_09_Applicator, "dependencies", - compiler_draft4_applicator_dependencies); + compiler_draft3_applicator_dependencies); - COMPILE(Known::JSON_Schema_2019_09_Core, "$ref", compiler_draft4_core_ref); + COMPILE(Known::JSON_Schema_2019_09_Core, "$ref", compiler_draft3_core_ref); COMPILE(Known::JSON_Schema_2019_09_Applicator, "allOf", compiler_draft4_applicator_allof); @@ -253,13 +256,13 @@ auto sourcemeta::blaze::default_schema_compiler( compiler_draft4_applicator_not); COMPILE(Known::JSON_Schema_2019_09_Validation, "enum", - compiler_draft4_validation_enum); + compiler_draft3_validation_enum); COMPILE(Known::JSON_Schema_2019_09_Validation, "uniqueItems", - compiler_draft4_validation_uniqueitems); + compiler_draft3_validation_uniqueitems); COMPILE(Known::JSON_Schema_2019_09_Validation, "maxItems", - compiler_draft4_validation_maxitems); + compiler_draft3_validation_maxitems); COMPILE(Known::JSON_Schema_2019_09_Validation, "minItems", - compiler_draft4_validation_minitems); + compiler_draft3_validation_minitems); COMPILE(Known::JSON_Schema_2019_09_Validation, "required", compiler_draft4_validation_required); COMPILE(Known::JSON_Schema_2019_09_Validation, "maxProperties", @@ -267,24 +270,24 @@ auto sourcemeta::blaze::default_schema_compiler( COMPILE(Known::JSON_Schema_2019_09_Validation, "minProperties", compiler_draft4_validation_minproperties); COMPILE(Known::JSON_Schema_2019_09_Validation, "maximum", - compiler_draft4_validation_maximum); + compiler_draft3_validation_maximum); COMPILE(Known::JSON_Schema_2019_09_Validation, "minimum", - compiler_draft4_validation_minimum); + compiler_draft3_validation_minimum); COMPILE(Known::JSON_Schema_2019_09_Validation, "multipleOf", - compiler_draft4_validation_multipleof); + compiler_draft3_validation_divisibleby); COMPILE(Known::JSON_Schema_2019_09_Validation, "maxLength", - compiler_draft4_validation_maxlength); + compiler_draft3_validation_maxlength); COMPILE(Known::JSON_Schema_2019_09_Validation, "minLength", - compiler_draft4_validation_minlength); + compiler_draft3_validation_minlength); COMPILE(Known::JSON_Schema_2019_09_Validation, "pattern", - compiler_draft4_validation_pattern); + compiler_draft3_validation_pattern); // ******************************************** // DRAFT 7 // ******************************************** COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "$ref", compiler_draft4_core_ref); + "$ref", compiler_draft3_core_ref); STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_7, "$ref"); STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_7_Hyper, "$ref"); @@ -322,18 +325,18 @@ auto sourcemeta::blaze::default_schema_compiler( 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); + "enum", compiler_draft3_validation_enum); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "items", compiler_draft4_applicator_items); + "items", compiler_draft3_applicator_items); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "additionalItems", compiler_draft4_applicator_additionalitems); + "additionalItems", compiler_draft3_applicator_additionalitems); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "uniqueItems", compiler_draft4_validation_uniqueitems); + "uniqueItems", compiler_draft3_validation_uniqueitems); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "maxItems", compiler_draft4_validation_maxitems); + "maxItems", compiler_draft3_validation_maxitems); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "minItems", compiler_draft4_validation_minitems); + "minItems", compiler_draft3_validation_minitems); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, "required", compiler_draft4_validation_required); @@ -342,36 +345,36 @@ auto sourcemeta::blaze::default_schema_compiler( 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); + "properties", compiler_draft3_applicator_properties); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, "patternProperties", - compiler_draft4_applicator_patternproperties); + compiler_draft3_applicator_patternproperties); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, "additionalProperties", - compiler_draft4_applicator_additionalproperties); + compiler_draft3_applicator_additionalproperties); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "dependencies", compiler_draft4_applicator_dependencies); + "dependencies", compiler_draft3_applicator_dependencies); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "maximum", compiler_draft4_validation_maximum); + "maximum", compiler_draft3_validation_maximum); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "minimum", compiler_draft4_validation_minimum); + "minimum", compiler_draft3_validation_minimum); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "multipleOf", compiler_draft4_validation_multipleof); + "multipleOf", compiler_draft3_validation_divisibleby); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "maxLength", compiler_draft4_validation_maxlength); + "maxLength", compiler_draft3_validation_maxlength); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "minLength", compiler_draft4_validation_minlength); + "minLength", compiler_draft3_validation_minlength); COMPILE_ANY(Known::JSON_Schema_Draft_7, Known::JSON_Schema_Draft_7_Hyper, - "pattern", compiler_draft4_validation_pattern); + "pattern", compiler_draft3_validation_pattern); // ******************************************** // DRAFT 6 // ******************************************** COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "$ref", compiler_draft4_core_ref); + "$ref", compiler_draft3_core_ref); STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_6, "$ref"); STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_6_Hyper, "$ref"); @@ -406,18 +409,18 @@ auto sourcemeta::blaze::default_schema_compiler( 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); + "enum", compiler_draft3_validation_enum); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "items", compiler_draft4_applicator_items); + "items", compiler_draft3_applicator_items); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "additionalItems", compiler_draft4_applicator_additionalitems); + "additionalItems", compiler_draft3_applicator_additionalitems); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "uniqueItems", compiler_draft4_validation_uniqueitems); + "uniqueItems", compiler_draft3_validation_uniqueitems); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "maxItems", compiler_draft4_validation_maxitems); + "maxItems", compiler_draft3_validation_maxitems); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "minItems", compiler_draft4_validation_minitems); + "minItems", compiler_draft3_validation_minitems); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, "required", compiler_draft4_validation_required); @@ -426,36 +429,36 @@ auto sourcemeta::blaze::default_schema_compiler( 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); + "properties", compiler_draft3_applicator_properties); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, "patternProperties", - compiler_draft4_applicator_patternproperties); + compiler_draft3_applicator_patternproperties); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, "additionalProperties", - compiler_draft4_applicator_additionalproperties); + compiler_draft3_applicator_additionalproperties); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "dependencies", compiler_draft4_applicator_dependencies); + "dependencies", compiler_draft3_applicator_dependencies); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "maximum", compiler_draft4_validation_maximum); + "maximum", compiler_draft3_validation_maximum); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "minimum", compiler_draft4_validation_minimum); + "minimum", compiler_draft3_validation_minimum); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "multipleOf", compiler_draft4_validation_multipleof); + "multipleOf", compiler_draft3_validation_divisibleby); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "maxLength", compiler_draft4_validation_maxlength); + "maxLength", compiler_draft3_validation_maxlength); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "minLength", compiler_draft4_validation_minlength); + "minLength", compiler_draft3_validation_minlength); COMPILE_ANY(Known::JSON_Schema_Draft_6, Known::JSON_Schema_Draft_6_Hyper, - "pattern", compiler_draft4_validation_pattern); + "pattern", compiler_draft3_validation_pattern); // ******************************************** // DRAFT 4 // ******************************************** COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "$ref", compiler_draft4_core_ref); + "$ref", compiler_draft3_core_ref); STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_4, "$ref"); STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_4_Hyper, "$ref"); @@ -469,25 +472,25 @@ auto sourcemeta::blaze::default_schema_compiler( 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); + "properties", compiler_draft3_applicator_properties); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, "patternProperties", - compiler_draft4_applicator_patternproperties); + compiler_draft3_applicator_patternproperties); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, "additionalProperties", - compiler_draft4_applicator_additionalproperties); + compiler_draft3_applicator_additionalproperties); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "items", compiler_draft4_applicator_items); + "items", compiler_draft3_applicator_items); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "additionalItems", compiler_draft4_applicator_additionalitems); + "additionalItems", compiler_draft3_applicator_additionalitems); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "dependencies", compiler_draft4_applicator_dependencies); + "dependencies", compiler_draft3_applicator_dependencies); // Any COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "type", compiler_draft4_validation_type); + "type", compiler_draft3_validation_type); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "enum", compiler_draft4_validation_enum); + "enum", compiler_draft3_validation_enum); // Object COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, @@ -499,27 +502,86 @@ auto sourcemeta::blaze::default_schema_compiler( // Array COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "uniqueItems", compiler_draft4_validation_uniqueitems); + "uniqueItems", compiler_draft3_validation_uniqueitems); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "maxItems", compiler_draft4_validation_maxitems); + "maxItems", compiler_draft3_validation_maxitems); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "minItems", compiler_draft4_validation_minitems); + "minItems", compiler_draft3_validation_minitems); // String COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "pattern", compiler_draft4_validation_pattern); + "pattern", compiler_draft3_validation_pattern); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "maxLength", compiler_draft4_validation_maxlength); + "maxLength", compiler_draft3_validation_maxlength); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "minLength", compiler_draft4_validation_minlength); + "minLength", compiler_draft3_validation_minlength); // Number COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "maximum", compiler_draft4_validation_maximum); + "maximum", compiler_draft3_validation_maximum); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "minimum", compiler_draft4_validation_minimum); + "minimum", compiler_draft3_validation_minimum); COMPILE_ANY(Known::JSON_Schema_Draft_4, Known::JSON_Schema_Draft_4_Hyper, - "multipleOf", compiler_draft4_validation_multipleof); + "multipleOf", compiler_draft3_validation_divisibleby); + + // ******************************************** + // DRAFT 3 + // ******************************************** + + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "$ref", compiler_draft3_core_ref); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_3, "$ref"); + STOP_IF_SIBLING_KEYWORD(Known::JSON_Schema_Draft_3_Hyper, "$ref"); + + // Applicators + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "extends", compiler_draft3_applicator_extends); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "properties", compiler_draft3_applicator_properties); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "patternProperties", + compiler_draft3_applicator_patternproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "additionalProperties", + compiler_draft3_applicator_additionalproperties); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "items", compiler_draft3_applicator_items); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "additionalItems", compiler_draft3_applicator_additionalitems); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "dependencies", compiler_draft3_applicator_dependencies); + + // Any + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "type", compiler_draft3_validation_type); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "disallow", compiler_draft3_validation_disallow); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "enum", compiler_draft3_validation_enum); + + // Array + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "uniqueItems", compiler_draft3_validation_uniqueitems); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "maxItems", compiler_draft3_validation_maxitems); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "minItems", compiler_draft3_validation_minitems); + + // String + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "pattern", compiler_draft3_validation_pattern); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "maxLength", compiler_draft3_validation_maxlength); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "minLength", compiler_draft3_validation_minlength); + + // Number + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "maximum", compiler_draft3_validation_maximum); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "minimum", compiler_draft3_validation_minimum); + COMPILE_ANY(Known::JSON_Schema_Draft_3, Known::JSON_Schema_Draft_3_Hyper, + "divisibleBy", compiler_draft3_validation_divisibleby); // ******************************************** // OpenAPI diff --git a/vendor/blaze/src/compiler/default_compiler_2019_09.h b/vendor/blaze/src/compiler/default_compiler_2019_09.h index 8b76fd0aa..32ebf57b9 100644 --- a/vendor/blaze/src/compiler/default_compiler_2019_09.h +++ b/vendor/blaze/src/compiler/default_compiler_2019_09.h @@ -202,7 +202,7 @@ auto compiler_2019_09_applicator_additionalproperties( const DynamicContext &dynamic_context, const Instructions &) -> Instructions { - return compiler_draft4_applicator_additionalproperties_with_options( + return compiler_draft3_applicator_additionalproperties_with_options( context, schema_context, dynamic_context, context.mode == Mode::Exhaustive, requires_evaluation(context, schema_context)); @@ -220,12 +220,12 @@ auto compiler_2019_09_applicator_items(const Context &context, })}; if (schema_context.schema.at(dynamic_context.keyword).is_array()) { - return compiler_draft4_applicator_items_with_options( + return compiler_draft3_applicator_items_with_options( context, schema_context, dynamic_context, context.mode == Mode::Exhaustive, track); } - return compiler_draft4_applicator_items_with_options( + return compiler_draft3_applicator_items_with_options( context, schema_context, dynamic_context, context.mode == Mode::Exhaustive, track && !schema_context.schema.defines("unevaluatedItems")); @@ -242,7 +242,7 @@ auto compiler_2019_09_applicator_additionalitems( return dependency.first.ends_with("unevaluatedItems"); })}; - return compiler_draft4_applicator_additionalitems_with_options( + return compiler_draft3_applicator_additionalitems_with_options( context, schema_context, dynamic_context, context.mode == Mode::Exhaustive, track && !schema_context.schema.defines("unevaluatedItems")); @@ -419,7 +419,7 @@ auto compiler_2019_09_core_recursiveref(const Context &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, + return compiler_draft3_core_ref(context, schema_context, dynamic_context, current); } @@ -431,7 +431,7 @@ 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( + return compiler_draft3_applicator_properties_with_options( context, schema_context, dynamic_context, current, context.mode == Mode::Exhaustive, requires_evaluation(context, schema_context)); @@ -441,7 +441,7 @@ 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( + return compiler_draft3_applicator_patternproperties_with_options( context, schema_context, dynamic_context, context.mode == Mode::Exhaustive, requires_evaluation(context, schema_context)); diff --git a/vendor/blaze/src/compiler/default_compiler_2020_12.h b/vendor/blaze/src/compiler/default_compiler_2020_12.h index f91aba2e4..d4fe9695a 100644 --- a/vendor/blaze/src/compiler/default_compiler_2020_12.h +++ b/vendor/blaze/src/compiler/default_compiler_2020_12.h @@ -21,7 +21,7 @@ auto compiler_2020_12_applicator_prefixitems( return dependency.first.ends_with("unevaluatedItems"); })}; - return compiler_draft4_applicator_items_array( + return compiler_draft3_applicator_items_array( context, schema_context, dynamic_context, context.mode == Mode::Exhaustive, track); } @@ -42,7 +42,7 @@ auto compiler_2020_12_applicator_items(const Context &context, return dependency.first.ends_with("unevaluatedItems"); })}; - return compiler_draft4_applicator_additionalitems_from_cursor( + return compiler_draft3_applicator_additionalitems_from_cursor( context, schema_context, dynamic_context, cursor, context.mode == Mode::Exhaustive, track && !schema_context.schema.defines("unevaluatedItems")); @@ -74,7 +74,7 @@ auto compiler_2020_12_core_dynamicref(const Context &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, + return compiler_draft3_core_ref(context, schema_context, dynamic_context, current); } diff --git a/vendor/blaze/src/compiler/default_compiler_draft3.h b/vendor/blaze/src/compiler/default_compiler_draft3.h new file mode 100644 index 000000000..0b11d1f98 --- /dev/null +++ b/vendor/blaze/src/compiler/default_compiler_draft3.h @@ -0,0 +1,2443 @@ +#ifndef SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT3_H_ +#define SOURCEMETA_BLAZE_COMPILER_DEFAULT_COMPILER_DRAFT3_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 compile_required_assertions(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions ¤t, + ValueStringSet properties_set) + -> Instructions { + 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 (properties_set.empty()) { + return {}; + } else if (properties_set.size() > 1) { + 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) { + 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))}; + } +} + +auto compiler_draft3_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})}; +} + +// 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); + ValueStringSet required; + for (const auto &entry : required_properties(schema_context)) { + // Only count the required property if its indeed in "properties" + if (properties.defines(entry.first)) { + required.insert(entry.first); + } + } + + 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 draft3_any_type_instructions(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context) + -> Instructions { + if (context.mode != Mode::Exhaustive) { + return {}; + } + + using sourcemeta::core::JSON; + ValueTypes types{}; + types.set(std::to_underlying(JSON::Type::Null)); + types.set(std::to_underlying(JSON::Type::Boolean)); + types.set(std::to_underlying(JSON::Type::Object)); + types.set(std::to_underlying(JSON::Type::Array)); + types.set(std::to_underlying(JSON::Type::Integer)); + types.set(std::to_underlying(JSON::Type::Real)); + types.set(std::to_underlying(JSON::Type::Decimal)); + types.set(std::to_underlying(JSON::Type::String)); + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; +} + +auto draft3_set_type_bits(const std::string &name, ValueTypes &types) -> bool { + using sourcemeta::core::JSON; + if (name == "null") { + types.set(std::to_underlying(JSON::Type::Null)); + } else if (name == "boolean") { + types.set(std::to_underlying(JSON::Type::Boolean)); + } else if (name == "object") { + types.set(std::to_underlying(JSON::Type::Object)); + } else if (name == "array") { + types.set(std::to_underlying(JSON::Type::Array)); + } else if (name == "integer") { + types.set(std::to_underlying(JSON::Type::Integer)); + } else if (name == "string") { + types.set(std::to_underlying(JSON::Type::String)); + } else if (name == "number") { + types.set(std::to_underlying(JSON::Type::Real)); + types.set(std::to_underlying(JSON::Type::Integer)); + types.set(std::to_underlying(JSON::Type::Decimal)); + } else { + return false; + } + return true; +} + +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_draft3_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"}; + + ValueStringSet required{required_properties(schema_context)}; + + auto properties{compile_properties(context, schema_context, + effective_dynamic_context, current)}; + + if (context.mode == Mode::FastValidation && !required.empty() && + schema_context.schema.defines("additionalProperties") && + schema_context.schema.at("additionalProperties").is_boolean() && + !schema_context.schema.at("additionalProperties").to_boolean() && + required.size() == + schema_context.schema.at(dynamic_context.keyword).size() && + std::ranges::all_of(properties, [&required](const auto &property) { + return required.contains(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 (assume_object) { + 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 && required.contains(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 && required.contains(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()) { + for (const auto &req : required) { + const auto &req_name{req.first}; + 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); + } + } + + if (fusion_entries.size() > 32) { + return children; + } + + 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_draft3_applicator_properties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions ¤t) + -> Instructions { + auto property_instructions{compiler_draft3_applicator_properties_with_options( + context, schema_context, dynamic_context, current, false, false)}; + + using Known = sourcemeta::core::Vocabularies::Known; + const auto is_draft3{ + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3) || + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3_Hyper)}; + if (!is_draft3) { + return property_instructions; + } + + ValueStringSet required{required_properties(schema_context)}; + if (required.empty()) { + return property_instructions; + } + + auto required_instructions{compile_required_assertions( + context, schema_context, dynamic_context, current, std::move(required))}; + if (required_instructions.empty()) { + return property_instructions; + } + + Instructions result{std::move(required_instructions)}; + for (auto &&step : property_instructions) { + result.push_back(std::move(step)); + } + return result; +} + +auto compiler_draft3_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_draft3_applicator_patternproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + return compiler_draft3_applicator_patternproperties_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft3_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() && + is_closed_properties_required(schema_context.schema, + required_properties(schema_context))) { + 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_draft3_applicator_additionalproperties( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + return compiler_draft3_applicator_additionalproperties_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft3_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_draft3_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_draft3_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_draft3_applicator_items_array( + context, schema_context, dynamic_context, annotate, track_evaluation); +} + +auto compiler_draft3_applicator_items(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + return compiler_draft3_applicator_items_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft3_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_draft3_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_draft3_applicator_additionalitems_from_cursor( + context, schema_context, dynamic_context, cursor, annotate, + track_evaluation); +} + +auto compiler_draft3_applicator_additionalitems( + const Context &context, const SchemaContext &schema_context, + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + return compiler_draft3_applicator_additionalitems_with_options( + context, schema_context, dynamic_context, false, false); +} + +auto compiler_draft3_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).empty()) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionFail, context, + schema_context, dynamic_context, ValueNone{})}; + } + + 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_draft3_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_draft3_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_draft3_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{ + 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{static_cast(value - 1)})}; +} + +auto compiler_draft3_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_draft3_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_draft3_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_draft3_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_draft3_validation_type(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + const auto &value{schema_context.schema.at(dynamic_context.keyword)}; + + using Known = sourcemeta::core::Vocabularies::Known; + const auto is_draft3{ + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3) || + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3_Hyper)}; + + if (is_draft3) { + if (value.is_string() && value.to_string() == "any") { + return draft3_any_type_instructions(context, schema_context, + dynamic_context); + } + + if (value.is_array()) { + bool has_object{false}; + for (const auto &element : value.as_array()) { + if (element.is_string() && element.to_string() == "any") { + return draft3_any_type_instructions(context, schema_context, + dynamic_context); + } + if (element.is_object()) { + has_object = true; + } + } + + if (has_object) { + if (context.mode == Mode::FastValidation && value.size() == 1) { + return compile( + context, schema_context, dynamic_context, + {static_cast(0)}); + } + + Instructions disjunctors; + for (std::uint64_t index = 0; index < value.size(); index++) { + const auto &element{value.at(index)}; + Instructions branch; + + if (element.is_object()) { + branch = compile( + context, schema_context, relative_dynamic_context(), + {static_cast(index)}); + } else if (element.is_string()) { + const auto &type_string{element.to_string()}; + if (type_string == "null") { + branch.push_back( + make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON::Type::Null)); + } else if (type_string == "boolean") { + branch.push_back( + make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON::Type::Boolean)); + } else if (type_string == "object") { + branch.push_back( + make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON::Type::Object)); + } else if (type_string == "array") { + branch.push_back( + make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON::Type::Array)); + } else if (type_string == "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)); + branch.push_back(make( + sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, relative_dynamic_context(), types)); + } else if (type_string == "integer") { + branch.push_back( + make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON::Type::Integer)); + } else if (type_string == "string") { + branch.push_back( + make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, relative_dynamic_context(), + sourcemeta::core::JSON::Type::String)); + } else { + continue; + } + } else { + continue; + } + + disjunctors.push_back( + make(sourcemeta::blaze::InstructionIndex::ControlGroup, context, + schema_context, relative_dynamic_context(), ValueNone{}, + std::move(branch))); + } + + return {make(sourcemeta::blaze::InstructionIndex::LogicalOr, context, + schema_context, dynamic_context, ValueBoolean{false}, + std::move(disjunctors))}; + } + } + } + + if (value.is_string()) { + const auto &type{value.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 &candidate) { return candidate.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 &candidate) { return candidate.is_boolean(); })) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::Boolean)}; + } else if (type == "object") { + if (!is_draft3) { + 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 &candidate) { return candidate.is_object(); })) { + return {}; + } + + if (!is_draft3 && 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 &candidate) { return candidate.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 &candidate) { return candidate.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 &candidate) { return candidate.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 &candidate) { return candidate.is_string(); })) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrict, + context, schema_context, dynamic_context, + sourcemeta::core::JSON::Type::String)}; + } else { + return {}; + } + } else if (value.is_array() && value.size() == 1 && + value.front().is_string()) { + const auto &type{value.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 (value.is_array()) { + ValueTypes types{}; + for (const auto &element : value.as_array()) { + assert(element.is_string()); + const auto &type_string{element.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)); + } + } + + if (!types.any()) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionFail, context, + schema_context, dynamic_context, ValueNone{})}; + } + + return {make(sourcemeta::blaze::InstructionIndex::AssertionTypeStrictAny, + context, schema_context, dynamic_context, types)}; + } + + return {}; +} + +auto compiler_draft3_validation_disallow(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + const auto &value{schema_context.schema.at(dynamic_context.keyword)}; + + const auto contains_any{ + (value.is_string() && value.to_string() == "any") || + (value.is_array() && + std::ranges::any_of(value.as_array(), [](const auto &element) { + return element.is_string() && element.to_string() == "any"; + }))}; + if (contains_any) { + return {make(sourcemeta::blaze::InstructionIndex::AssertionFail, context, + schema_context, dynamic_context, ValueNone{})}; + } + + ValueTypes types{}; + Instructions subschema_nots; + + if (value.is_string()) { + draft3_set_type_bits(value.to_string(), types); + } else if (value.is_array()) { + for (std::uint64_t index = 0; index < value.size(); index++) { + const auto &element{value.at(index)}; + if (element.is_string()) { + draft3_set_type_bits(element.to_string(), types); + } else if (element.is_object()) { + sourcemeta::core::WeakPointer index_suffix; + index_suffix.push_back(index); + const auto element_uri{ + sourcemeta::core::to_uri( + schema_context.relative_pointer.concat(index_suffix), + schema_context.base) + .canonicalize() + .recompose()}; + + auto inner_instructions{ + compile(context, schema_context, relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer, element_uri)}; + + const auto element_relative_pointer{ + schema_context.relative_pointer.concat(index_suffix)}; + const SchemaContext element_schema_context{ + .relative_pointer = element_relative_pointer, + .schema = schema_context.schema, + .vocabularies = schema_context.vocabularies, + .base = schema_context.base, + .is_property_name = schema_context.is_property_name}; + + const auto element_base_schema_location{ + sourcemeta::blaze::make_weak_pointer(dynamic_context.keyword, + index)}; + const DynamicContext element_dynamic_context{ + .keyword = KEYWORD_EMPTY, + .base_schema_location = element_base_schema_location, + .base_instance_location = dynamic_context.base_instance_location}; + + subschema_nots.push_back( + make(sourcemeta::blaze::InstructionIndex::LogicalNot, context, + element_schema_context, element_dynamic_context, ValueNone{}, + std::move(inner_instructions))); + } + } + } + + Instructions result; + if (types.any()) { + result.push_back( + make(sourcemeta::blaze::InstructionIndex::AssertionNotTypeStrictAny, + context, schema_context, dynamic_context, types)); + } + for (auto &&instruction : subschema_nots) { + result.push_back(std::move(instruction)); + } + return result; +} + +auto compiler_draft3_applicator_extends(const Context &context, + const SchemaContext &schema_context, + const DynamicContext &dynamic_context, + const Instructions &) -> Instructions { + assert(!context.uses_dynamic_scopes); + + const auto &value{schema_context.schema.at(dynamic_context.keyword)}; + + if (value.is_object()) { + if (context.mode == Mode::FastValidation) { + return compile(context, schema_context, dynamic_context, + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer); + } + + auto inner{compile(context, schema_context, relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; + if (inner.empty()) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::LogicalAnd, context, + schema_context, dynamic_context, ValueNone{}, + std::move(inner))}; + } + + if (!value.is_array()) { + return {}; + } + + if (value.empty()) { + return {}; + } + + Instructions children; + + if (context.mode == Mode::FastValidation) { + for (std::uint64_t index = 0; index < value.size(); index++) { + for (auto &&step : compile( + context, schema_context, dynamic_context, + {static_cast(index)})) { + children.push_back(std::move(step)); + } + } + + return children; + } + + for (std::uint64_t index = 0; index < value.size(); index++) { + auto arm{ + compile(context, schema_context, relative_dynamic_context(), + {static_cast(index)})}; + if (arm.empty()) { + continue; + } + children.push_back(make(sourcemeta::blaze::InstructionIndex::ControlGroup, + context, schema_context, relative_dynamic_context(), + ValueNone{}, std::move(arm))); + } + + if (children.empty()) { + return {}; + } + + return {make(sourcemeta::blaze::InstructionIndex::LogicalAnd, context, + schema_context, dynamic_context, ValueNone{}, + std::move(children))}; +} + +auto compiler_draft3_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 {}; + } + + using Known = sourcemeta::core::Vocabularies::Known; + const auto is_draft3{ + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3) || + schema_context.vocabularies.contains(Known::JSON_Schema_Draft_3_Hyper)}; + + 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); + } + } else if (is_draft3 && entry.second.is_string()) { + dependencies.emplace(entry.first, + std::vector{ + entry.second.to_string()}); + } + } + + if (!dependencies.empty()) { + children.push_back(make( + sourcemeta::blaze::InstructionIndex::AssertionPropertyDependencies, + context, schema_context, dynamic_context, std::move(dependencies))); + } + + return children; +} + +auto compiler_draft3_validation_divisibleby( + 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_draft4.h b/vendor/blaze/src/compiler/default_compiler_draft4.h index 4f8bf9ec7..08f9a5517 100644 --- a/vendor/blaze/src/compiler/default_compiler_draft4.h +++ b/vendor/blaze/src/compiler/default_compiler_draft4.h @@ -4,446 +4,17 @@ #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 // std::ranges::any_of, std::ranges::all_of, std::ranges::none_of, std::find_if +#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; -} +#include "default_compiler_draft3.h" 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, @@ -453,121 +24,10 @@ auto compiler_draft4_validation_required(const Context &context, 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()))}; - } + return compile_required_assertions( + context, schema_context, dynamic_context, current, + json_array_to_string_set( + schema_context.schema.at(dynamic_context.keyword))); } auto compiler_draft4_applicator_allof(const Context &context, @@ -718,197 +178,90 @@ auto compiler_draft4_applicator_oneof(const Context &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()); - } +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; } - } - 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; - } + subschemas += 1; } - return false; -} + Instructions children{compile(context, schema_context, + relative_dynamic_context(), + sourcemeta::core::empty_weak_pointer, + sourcemeta::core::empty_weak_pointer)}; -auto is_integer_type_bounded_pattern(const Instructions &children) -> bool { - if (children.size() != 3) { - return false; - } + // 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"); + })}; - 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; - } + // 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))}; } - - 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; - } +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 {}; } - return has_type && has_min; -} + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); -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(); - } + if (schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() != "object") { + return {}; } - 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(); - } + // 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 {minimum, maximum}; + 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_applicator_properties_with_options( +auto compiler_draft4_validation_minproperties( 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()) { + const DynamicContext &dynamic_context, const Instructions &) + -> Instructions { + if (!schema_context.schema.at(dynamic_context.keyword).is_integral()) { return {}; } - if (schema_context.schema.at(dynamic_context.keyword).empty()) { - return {}; - } + assert(schema_context.schema.at(dynamic_context.keyword).is_positive()); if (schema_context.schema.defines("type") && schema_context.schema.at("type").is_string() && @@ -916,1508 +269,24 @@ auto compiler_draft4_applicator_properties_with_options( 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)}; - + // We'll handle it at the type level as an optimization 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); - } - } + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == "object") { + return {}; + } - 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); - } - } - } - - if (fusion_entries.size() > 32) { - return children; - } - - 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).empty()) { - return {make(sourcemeta::blaze::InstructionIndex::AssertionFail, context, - schema_context, dynamic_context, ValueNone{})}; - } - - 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 {}; - } + 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/evaluator/evaluator_describe.cc b/vendor/blaze/src/evaluator/evaluator_describe.cc index 9f4102a34..70bfcc5e3 100644 --- a/vendor/blaze/src/evaluator/evaluator_describe.cc +++ b/vendor/blaze/src/evaluator/evaluator_describe.cc @@ -99,6 +99,27 @@ auto describe_type_check(const bool valid, } } +auto describe_not_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 NOT be of type "; + message << type_name(expected); + if (!valid) { + message << " but it was of type "; + if (current == sourcemeta::core::JSON::Type::Decimal && + expected == sourcemeta::core::JSON::Type::Integer) { + message << "integer"; + } else if ((current == sourcemeta::core::JSON::Type::Integer && + expected == sourcemeta::core::JSON::Type::Real) || + 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, @@ -177,6 +198,84 @@ auto describe_types_check(const bool valid, } } +auto describe_not_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_not_type_check( + valid, current, static_cast(type_index), + message); + return; + } + + message << "The value was expected to NOT 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()) @@ -266,7 +365,7 @@ auto describe(const bool valid, const Instruction &step, } if (step.type == sourcemeta::blaze::InstructionIndex::LogicalAnd) { - if (keyword == "allOf") { + if (keyword == "allOf" || keyword == "extends") { assert(!step.children.empty()); std::ostringstream message; message << "The " << type_name(target.type()) @@ -1333,6 +1432,14 @@ auto describe(const bool valid, const Instruction &step, return message.str(); } + if (step.type == + sourcemeta::blaze::InstructionIndex::AssertionNotTypeStrictAny) { + std::ostringstream message; + describe_not_types_check(valid, target.type(), + instruction_value(step), message); + return message.str(); + } + if (step.type == sourcemeta::blaze::InstructionIndex::AssertionTypeStringBounded) { std::ostringstream message; diff --git a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator.h b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator.h index 87822b065..b125cd70c 100644 --- a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator.h +++ b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator.h @@ -44,7 +44,7 @@ struct Template { }; /// @ingroup evaluator -constexpr std::size_t JSON_VERSION{4}; +constexpr std::size_t JSON_VERSION{5}; /// @ingroup evaluator /// Parse a template from JSON diff --git a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h index d77370225..7e69da570 100644 --- a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h +++ b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h @@ -523,6 +523,20 @@ INSTRUCTION_HANDLER(AssertionTypeStrictAny) { EVALUATE_END(AssertionTypeStrictAny); } +INSTRUCTION_DIRECT(AssertionNotTypeStrictAny, ValueTypes) { + return !value.test(std::to_underlying(effective_type_strict_real(target))); +} + +INSTRUCTION_HANDLER(AssertionNotTypeStrictAny) { + EVALUATE_BEGIN_NO_PRECONDITION(AssertionNotTypeStrictAny); + const auto value{assume_value_copy(instruction.value)}; + assert(value.any()); + const auto &target{ + resolve_instance(instance, instruction.relative_instance_location)}; + result = DIRECT(AssertionNotTypeStrictAny, target, value); + EVALUATE_END(AssertionNotTypeStrictAny); +} + INSTRUCTION_DIRECT(AssertionTypeStringBounded, ValueRange) { const auto &[minimum, maximum, exhaustive] = value; return target.type() == JSON::Type::String && @@ -2564,7 +2578,7 @@ using DispatchHandler = bool (*)( template // Must have same order as InstructionIndex // NOLINTNEXTLINE(modernize-avoid-c-arrays) -static constexpr DispatchHandler handlers[99] = { +static constexpr DispatchHandler handlers[100] = { AssertionFail, AssertionDefines, AssertionDefinesStrict, @@ -2578,6 +2592,7 @@ static constexpr DispatchHandler handlers[99] = { AssertionTypeAny, AssertionTypeStrict, AssertionTypeStrictAny, + AssertionNotTypeStrictAny, AssertionTypeStringBounded, AssertionTypeStringUpper, AssertionTypeArrayBounded, diff --git a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h index f9c351a15..e3d39c345 100644 --- a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h +++ b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_instruction.h @@ -33,6 +33,7 @@ enum class InstructionIndex : std::uint8_t { AssertionTypeAny, AssertionTypeStrict, AssertionTypeStrictAny, + AssertionNotTypeStrictAny, AssertionTypeStringBounded, AssertionTypeStringUpper, AssertionTypeArrayBounded, @@ -137,6 +138,7 @@ constexpr std::string_view InstructionNames[] = { "AssertionTypeAny", "AssertionTypeStrict", "AssertionTypeStrictAny", + "AssertionNotTypeStrictAny", "AssertionTypeStringBounded", "AssertionTypeStringUpper", "AssertionTypeArrayBounded", diff --git a/vendor/blaze/src/test/include/sourcemeta/blaze/test.h b/vendor/blaze/src/test/include/sourcemeta/blaze/test.h index 3662f9a20..32ae5f604 100644 --- a/vendor/blaze/src/test/include/sourcemeta/blaze/test.h +++ b/vendor/blaze/src/test/include/sourcemeta/blaze/test.h @@ -90,17 +90,17 @@ struct SOURCEMETA_BLAZE_TEST_EXPORT TestSuite { #if defined(_MSC_VER) #pragma warning(disable : 4251) #endif - /// The target schema URI or file path - sourcemeta::core::JSON::String target; + /// The target schema URIs or file paths + std::vector targets; /// The list of test cases in the suite std::vector tests; + /// The compiled schema templates for fast validation + std::vector