jsi-dev 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.yardopts +3 -4
- data/CHANGELOG.md +19 -0
- data/LICENSE.md +2 -3
- data/README.md +87 -43
- data/docs/{glossary.md → Glossary.md} +84 -52
- data/jsi.gemspec +1 -1
- data/lib/jsi/base/mutability.rb +48 -0
- data/lib/jsi/base/node.rb +66 -52
- data/lib/jsi/base.rb +592 -176
- data/lib/jsi/jsi_coder.rb +4 -2
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +118 -59
- data/lib/jsi/metaschema_node.rb +244 -154
- data/lib/jsi/ptr.rb +45 -17
- data/lib/jsi/ref.rb +197 -0
- data/lib/jsi/registry.rb +311 -0
- data/lib/jsi/schema/cxt/child_application.rb +35 -0
- data/lib/jsi/schema/cxt/inplace_application.rb +37 -0
- data/lib/jsi/schema/cxt.rb +80 -0
- data/lib/jsi/schema/dialect.rb +137 -0
- data/lib/jsi/schema/draft04.rb +113 -5
- data/lib/jsi/schema/draft06.rb +123 -5
- data/lib/jsi/schema/draft07.rb +157 -5
- data/lib/jsi/schema/draft202012.rb +303 -0
- data/lib/jsi/schema/dynamic_anchor_map.rb +63 -0
- data/lib/jsi/schema/element.rb +69 -0
- data/lib/jsi/schema/elements/anchor.rb +13 -0
- data/lib/jsi/schema/elements/array_validation.rb +82 -0
- data/lib/jsi/schema/elements/comment.rb +10 -0
- data/lib/jsi/schema/{validation → elements}/const.rb +11 -7
- data/lib/jsi/schema/elements/contains.rb +59 -0
- data/lib/jsi/schema/elements/contains_minmax.rb +91 -0
- data/lib/jsi/schema/elements/content_encoding.rb +10 -0
- data/lib/jsi/schema/elements/content_media_type.rb +10 -0
- data/lib/jsi/schema/elements/content_schema.rb +16 -0
- data/lib/jsi/schema/elements/default.rb +11 -0
- data/lib/jsi/schema/elements/definitions.rb +19 -0
- data/lib/jsi/schema/elements/dependencies.rb +99 -0
- data/lib/jsi/schema/elements/dependent_required.rb +49 -0
- data/lib/jsi/schema/elements/dependent_schemas.rb +69 -0
- data/lib/jsi/schema/elements/dynamic_ref.rb +69 -0
- data/lib/jsi/schema/elements/enum.rb +26 -0
- data/lib/jsi/schema/elements/examples.rb +10 -0
- data/lib/jsi/schema/elements/format.rb +10 -0
- data/lib/jsi/schema/elements/id.rb +30 -0
- data/lib/jsi/schema/elements/if_then_else.rb +82 -0
- data/lib/jsi/schema/elements/info_bool.rb +10 -0
- data/lib/jsi/schema/elements/info_string.rb +10 -0
- data/lib/jsi/schema/elements/items.rb +93 -0
- data/lib/jsi/schema/elements/items_prefixed.rb +96 -0
- data/lib/jsi/schema/elements/not.rb +31 -0
- data/lib/jsi/schema/elements/numeric.rb +137 -0
- data/lib/jsi/schema/elements/numeric_draft04.rb +77 -0
- data/lib/jsi/schema/elements/object_validation.rb +55 -0
- data/lib/jsi/schema/elements/pattern.rb +35 -0
- data/lib/jsi/schema/elements/properties.rb +145 -0
- data/lib/jsi/schema/elements/property_names.rb +48 -0
- data/lib/jsi/schema/elements/ref.rb +62 -0
- data/lib/jsi/schema/elements/required.rb +34 -0
- data/lib/jsi/schema/elements/self.rb +24 -0
- data/lib/jsi/schema/elements/some_of.rb +180 -0
- data/lib/jsi/schema/elements/string_validation.rb +57 -0
- data/lib/jsi/schema/elements/type.rb +43 -0
- data/lib/jsi/schema/elements/unevaluated_items.rb +54 -0
- data/lib/jsi/schema/elements/unevaluated_properties.rb +54 -0
- data/lib/jsi/schema/elements/xschema.rb +10 -0
- data/lib/jsi/schema/elements/xvocabulary.rb +10 -0
- data/lib/jsi/schema/elements.rb +101 -0
- data/lib/jsi/schema/issue.rb +3 -4
- data/lib/jsi/schema/schema_ancestor_node.rb +105 -52
- data/lib/jsi/schema/vocabulary.rb +36 -0
- data/lib/jsi/schema.rb +598 -383
- data/lib/jsi/schema_classes.rb +195 -141
- data/lib/jsi/schema_set.rb +85 -128
- data/lib/jsi/set.rb +23 -0
- data/lib/jsi/simple_wrap.rb +14 -17
- data/lib/jsi/struct.rb +57 -0
- data/lib/jsi/uri.rb +40 -0
- data/lib/jsi/util/private/memo_map.rb +9 -13
- data/lib/jsi/util/private.rb +59 -31
- data/lib/jsi/util/typelike.rb +19 -60
- data/lib/jsi/util.rb +53 -34
- data/lib/jsi/validation/error.rb +45 -2
- data/lib/jsi/validation/result.rb +121 -90
- data/lib/jsi/validation.rb +1 -6
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +170 -36
- data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +62 -0
- data/lib/schemas/json-schema.org/draft-04/schema.rb +60 -109
- data/lib/schemas/json-schema.org/draft-06/schema.rb +53 -108
- data/lib/schemas/json-schema.org/draft-07/schema.rb +63 -127
- data/readme.rb +4 -4
- data/{resources}/schemas/2020-12_strict.json +19 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/applicator.json +48 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/content.json +17 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/core.json +51 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-annotation.json +14 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-assertion.json +14 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/meta-data.json +37 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/unevaluated.json +15 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/meta/validation.json +98 -0
- data/{resources}/schemas/json-schema.org/draft/2020-12/schema.json +58 -0
- metadata +73 -52
- data/lib/jsi/metaschema.rb +0 -6
- data/lib/jsi/schema/application/child_application/contains.rb +0 -25
- data/lib/jsi/schema/application/child_application/draft04.rb +0 -21
- data/lib/jsi/schema/application/child_application/draft06.rb +0 -28
- data/lib/jsi/schema/application/child_application/draft07.rb +0 -28
- data/lib/jsi/schema/application/child_application/items.rb +0 -18
- data/lib/jsi/schema/application/child_application/properties.rb +0 -25
- data/lib/jsi/schema/application/child_application.rb +0 -13
- data/lib/jsi/schema/application/draft04.rb +0 -8
- data/lib/jsi/schema/application/draft06.rb +0 -8
- data/lib/jsi/schema/application/draft07.rb +0 -8
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +0 -28
- data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -25
- data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -26
- data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -32
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +0 -20
- data/lib/jsi/schema/application/inplace_application/ref.rb +0 -18
- data/lib/jsi/schema/application/inplace_application/someof.rb +0 -44
- data/lib/jsi/schema/application/inplace_application.rb +0 -14
- data/lib/jsi/schema/application.rb +0 -12
- data/lib/jsi/schema/ref.rb +0 -183
- data/lib/jsi/schema/validation/array.rb +0 -69
- data/lib/jsi/schema/validation/contains.rb +0 -25
- data/lib/jsi/schema/validation/dependencies.rb +0 -49
- data/lib/jsi/schema/validation/draft04/minmax.rb +0 -91
- data/lib/jsi/schema/validation/draft04.rb +0 -110
- data/lib/jsi/schema/validation/draft06.rb +0 -120
- data/lib/jsi/schema/validation/draft07.rb +0 -157
- data/lib/jsi/schema/validation/enum.rb +0 -25
- data/lib/jsi/schema/validation/ifthenelse.rb +0 -46
- data/lib/jsi/schema/validation/items.rb +0 -54
- data/lib/jsi/schema/validation/not.rb +0 -20
- data/lib/jsi/schema/validation/numeric.rb +0 -121
- data/lib/jsi/schema/validation/object.rb +0 -45
- data/lib/jsi/schema/validation/pattern.rb +0 -34
- data/lib/jsi/schema/validation/properties.rb +0 -101
- data/lib/jsi/schema/validation/property_names.rb +0 -32
- data/lib/jsi/schema/validation/ref.rb +0 -40
- data/lib/jsi/schema/validation/required.rb +0 -27
- data/lib/jsi/schema/validation/someof.rb +0 -90
- data/lib/jsi/schema/validation/string.rb +0 -47
- data/lib/jsi/schema/validation/type.rb +0 -49
- data/lib/jsi/schema/validation.rb +0 -49
- data/lib/jsi/schema_registry.rb +0 -190
- data/lib/jsi/util/private/attr_struct.rb +0 -130
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
PROPERTY_NAMES = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'propertyNames') do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
if keyword?('propertyNames')
|
|
9
|
+
#> The value of "propertyNames" MUST be a valid JSON Schema.
|
|
10
|
+
cxt_yield(['propertyNames'])
|
|
11
|
+
end
|
|
12
|
+
end # element.add_action(:subschema)
|
|
13
|
+
|
|
14
|
+
element.add_action(:propertyNames) do
|
|
15
|
+
cxt_yield(subschema(['propertyNames'])) if keyword?('propertyNames')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
element.add_action(:validate) do
|
|
19
|
+
if keyword?('propertyNames')
|
|
20
|
+
# The value of "propertyNames" MUST be a valid JSON Schema.
|
|
21
|
+
#
|
|
22
|
+
# If the instance is an object, this keyword validates if every property name in the instance
|
|
23
|
+
# validates against the provided schema. Note the property name that the schema is testing will
|
|
24
|
+
# always be a string.
|
|
25
|
+
if instance.respond_to?(:to_hash)
|
|
26
|
+
results = {}
|
|
27
|
+
instance.each_key do |property_name|
|
|
28
|
+
results[property_name] = subschema(['propertyNames']).internal_validate_instance(
|
|
29
|
+
Ptr[],
|
|
30
|
+
property_name,
|
|
31
|
+
validate_only: validate_only,
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
validate(
|
|
35
|
+
results.each_value.all?(&:valid?),
|
|
36
|
+
'validation.keyword.propertyNames.invalid',
|
|
37
|
+
"instance object property names are not all valid against `propertyNames` schema",
|
|
38
|
+
keyword: 'propertyNames',
|
|
39
|
+
results: results.each_value,
|
|
40
|
+
instance_property_names_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end # element.add_action(:validate)
|
|
45
|
+
end # Schema::Element.new
|
|
46
|
+
end # PROPERTY_NAMES = element_map
|
|
47
|
+
end # module Schema::Elements
|
|
48
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
# exclusive [Boolean]: whether to abort invocation of subsequent actions when a $ref is evaluated
|
|
6
|
+
REF = element_map do |exclusive: |
|
|
7
|
+
Schema::Element.new(keyword: '$ref') do |element|
|
|
8
|
+
if exclusive
|
|
9
|
+
# $ref must come before all other elements to abort evaluation
|
|
10
|
+
element.required_before_elements { |_| true }
|
|
11
|
+
|
|
12
|
+
actions_to_abort = [
|
|
13
|
+
:id,
|
|
14
|
+
:id_without_fragment,
|
|
15
|
+
:anchor,
|
|
16
|
+
]
|
|
17
|
+
actions_to_abort.each do |action|
|
|
18
|
+
element.add_action(action) { self.abort = true if keyword?('$ref') }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
resolve_ref = proc do
|
|
23
|
+
next if !keyword_value_str?('$ref')
|
|
24
|
+
ref = schema.schema_ref(schema_content['$ref'])
|
|
25
|
+
resolved_schema = ref.resolve.with_dynamic_scope_from(schema)
|
|
26
|
+
[resolved_schema, ref]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
element.add_action(:inplace_applicate) do
|
|
30
|
+
resolved_schema, ref = *instance_exec(&resolve_ref) || next
|
|
31
|
+
|
|
32
|
+
inplace_schema_applicate(resolved_schema, ref: ref)
|
|
33
|
+
|
|
34
|
+
if exclusive
|
|
35
|
+
self.abort = true
|
|
36
|
+
end
|
|
37
|
+
end # element.add_action(:inplace_applicate)
|
|
38
|
+
|
|
39
|
+
element.add_action(:validate) do
|
|
40
|
+
resolved_schema, schema_ref = *instance_exec(&resolve_ref) || next
|
|
41
|
+
|
|
42
|
+
ref_result = resolved_schema.internal_validate_instance(
|
|
43
|
+
instance_ptr,
|
|
44
|
+
instance_document,
|
|
45
|
+
validate_only: validate_only,
|
|
46
|
+
visited_refs: Util.add_visited_ref(visited_refs, schema_ref),
|
|
47
|
+
)
|
|
48
|
+
inplace_results_validate(
|
|
49
|
+
ref_result.valid?,
|
|
50
|
+
'validation.keyword.$ref.invalid',
|
|
51
|
+
"instance is not valid against the schema referenced by `$ref`",
|
|
52
|
+
keyword: '$ref',
|
|
53
|
+
results: [ref_result],
|
|
54
|
+
)
|
|
55
|
+
if exclusive
|
|
56
|
+
self.abort = true
|
|
57
|
+
end
|
|
58
|
+
end # element.add_action(:validate)
|
|
59
|
+
end # Schema::Element.new
|
|
60
|
+
end # REF = element_map
|
|
61
|
+
end # module Schema::Elements
|
|
62
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
REQUIRED = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'required') do |element|
|
|
7
|
+
element.add_action(:described_object_property_names) do
|
|
8
|
+
next if !keyword_value_ary?('required')
|
|
9
|
+
schema_content['required'].each(&block)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
element.add_action(:validate) do
|
|
13
|
+
if keyword?('required')
|
|
14
|
+
value = schema_content['required']
|
|
15
|
+
# The value of this keyword MUST be an array. Elements of this array, if any, MUST be strings, and MUST be unique.
|
|
16
|
+
if value.respond_to?(:to_ary)
|
|
17
|
+
if instance.respond_to?(:to_hash)
|
|
18
|
+
# An object instance is valid against this keyword if every item in the array is the name of a property in the instance.
|
|
19
|
+
missing_required = value.reject { |property_name| instance.key?(property_name) }.freeze
|
|
20
|
+
validate(
|
|
21
|
+
missing_required.empty?,
|
|
22
|
+
'validation.keyword.required.missing_property_names',
|
|
23
|
+
'instance object does not contain all property names specified by `required` value',
|
|
24
|
+
keyword: 'required',
|
|
25
|
+
missing_required_property_names: missing_required,
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end # element.add_action(:validate)
|
|
31
|
+
end # Schema::Element.new
|
|
32
|
+
end # REQUIRED = element_map
|
|
33
|
+
end # module Schema::Elements
|
|
34
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
SELF = element_map do
|
|
6
|
+
Schema::Element.new do |element|
|
|
7
|
+
element.add_action(:inplace_applicate) do
|
|
8
|
+
inplace_schema_applicate(schema)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
element.add_action(:validate) do
|
|
12
|
+
#> boolean schemas are equivalent to the following behaviors:
|
|
13
|
+
#>
|
|
14
|
+
#> true: Always passes validation, as if the empty schema {}
|
|
15
|
+
#>
|
|
16
|
+
#> false: Always fails validation, as if the schema { "not":{} }
|
|
17
|
+
if schema_content == false
|
|
18
|
+
validate(false, 'validation.false_schema', "instance is not valid against `false` schema")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
ALL_OF = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'allOf') do |element|
|
|
7
|
+
element.add_action(:subschema) do
|
|
8
|
+
#> This keyword's value MUST be a non-empty array.
|
|
9
|
+
if keyword_value_ary?('allOf')
|
|
10
|
+
schema_content['allOf'].each_index do |i|
|
|
11
|
+
#> Each item of the array MUST be a valid JSON Schema.
|
|
12
|
+
cxt_yield(['allOf', i])
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end # element.add_action(:subschema)
|
|
16
|
+
|
|
17
|
+
element.add_action(:inplace_applicate) do
|
|
18
|
+
if keyword?('allOf') && schema_content['allOf'].respond_to?(:to_ary)
|
|
19
|
+
schema_content['allOf'].each_index do |i|
|
|
20
|
+
inplace_subschema_applicate(['allOf', i])
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end # element.add_action(:inplace_applicate)
|
|
24
|
+
|
|
25
|
+
element.add_action(:validate) do
|
|
26
|
+
if keyword?('allOf')
|
|
27
|
+
value = schema_content['allOf']
|
|
28
|
+
# This keyword's value MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
|
|
29
|
+
if value.respond_to?(:to_ary)
|
|
30
|
+
# An instance validates successfully against this keyword if it validates successfully against all
|
|
31
|
+
# schemas defined by this keyword's value.
|
|
32
|
+
allOf_results = value.each_index.map do |i|
|
|
33
|
+
inplace_subschema_validate(['allOf', i])
|
|
34
|
+
end
|
|
35
|
+
inplace_results_validate(
|
|
36
|
+
allOf_results.all?(&:valid?),
|
|
37
|
+
'validation.keyword.allOf.not_all_valid',
|
|
38
|
+
"instance is not valid against all `allOf` schemas",
|
|
39
|
+
keyword: 'allOf',
|
|
40
|
+
results: allOf_results,
|
|
41
|
+
allOf_indexes_valid: allOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end # element.add_action(:validate)
|
|
46
|
+
end # Schema::Element.new
|
|
47
|
+
end # ALL_OF = element_map
|
|
48
|
+
end # module Schema::Elements
|
|
49
|
+
|
|
50
|
+
module Schema::Elements
|
|
51
|
+
ANY_OF = element_map do
|
|
52
|
+
Schema::Element.new(keyword: 'anyOf') do |element|
|
|
53
|
+
element.add_action(:subschema) do
|
|
54
|
+
#> This keyword's value MUST be a non-empty array.
|
|
55
|
+
if keyword_value_ary?('anyOf')
|
|
56
|
+
schema_content['anyOf'].each_index do |i|
|
|
57
|
+
#> Each item of the array MUST be a valid JSON Schema.
|
|
58
|
+
cxt_yield(['anyOf', i])
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end # element.add_action(:subschema)
|
|
62
|
+
|
|
63
|
+
element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('anyOf') }
|
|
64
|
+
|
|
65
|
+
element.add_action(:inplace_applicate) do
|
|
66
|
+
if keyword?('anyOf') && schema_content['anyOf'].respond_to?(:to_ary)
|
|
67
|
+
anyOf = schema_content['anyOf'].each_index.map { |i| subschema(['anyOf', i]) }
|
|
68
|
+
validOf = anyOf.select { |schema| schema.instance_valid?(instance) }
|
|
69
|
+
if !validOf.empty?
|
|
70
|
+
applicators = validOf
|
|
71
|
+
else
|
|
72
|
+
# invalid application: if none of the anyOf were valid, we apply them all
|
|
73
|
+
applicators = anyOf
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
applicators.each do |applicator|
|
|
77
|
+
inplace_schema_applicate(applicator)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end # element.add_action(:inplace_applicate)
|
|
81
|
+
|
|
82
|
+
element.add_action(:validate) do
|
|
83
|
+
if keyword?('anyOf')
|
|
84
|
+
value = schema_content['anyOf']
|
|
85
|
+
# This keyword's value MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
|
|
86
|
+
if value.respond_to?(:to_ary)
|
|
87
|
+
# An instance validates successfully against this keyword if it validates successfully against at
|
|
88
|
+
# least one schema defined by this keyword's value.
|
|
89
|
+
# Note that when annotations are being collected, all subschemas MUST be examined so that
|
|
90
|
+
# annotations are collected from each subschema that validates successfully.
|
|
91
|
+
anyOf_results = value.each_index.map do |i|
|
|
92
|
+
inplace_subschema_validate(['anyOf', i])
|
|
93
|
+
end
|
|
94
|
+
inplace_results_validate(
|
|
95
|
+
anyOf_results.any?(&:valid?),
|
|
96
|
+
'validation.keyword.anyOf.not_any_valid',
|
|
97
|
+
"instance is not valid against any `anyOf` schema",
|
|
98
|
+
keyword: 'anyOf',
|
|
99
|
+
results: anyOf_results,
|
|
100
|
+
# when invalid these are all false, but included for consistency with allOf/oneOf
|
|
101
|
+
anyOf_indexes_valid: anyOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end # element.add_action(:validate)
|
|
106
|
+
end # Schema::Element.new
|
|
107
|
+
end # ANY_OF = element_map
|
|
108
|
+
end # module Schema::Elements
|
|
109
|
+
|
|
110
|
+
module Schema::Elements
|
|
111
|
+
ONE_OF = element_map do
|
|
112
|
+
Schema::Element.new(keyword: 'oneOf') do |element|
|
|
113
|
+
element.add_action(:subschema) do
|
|
114
|
+
#> This keyword's value MUST be a non-empty array.
|
|
115
|
+
if keyword_value_ary?('oneOf')
|
|
116
|
+
schema_content['oneOf'].each_index do |i|
|
|
117
|
+
#> Each item of the array MUST be a valid JSON Schema.
|
|
118
|
+
cxt_yield(['oneOf', i])
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end # element.add_action(:subschema)
|
|
122
|
+
|
|
123
|
+
element.add_action(:inplace_application_requires_instance) { cxt_yield(true) if keyword?('oneOf') }
|
|
124
|
+
|
|
125
|
+
element.add_action(:inplace_applicate) do
|
|
126
|
+
if keyword?('oneOf') && schema_content['oneOf'].respond_to?(:to_ary)
|
|
127
|
+
oneOf_idxs = schema_content['oneOf'].each_index
|
|
128
|
+
subschema_idx_valid = Hash.new { |h, i| h[i] = subschema(['oneOf', i]).instance_valid?(instance) }
|
|
129
|
+
# count up to 2 `oneOf` subschemas which `instance` validates against
|
|
130
|
+
nvalid = oneOf_idxs.inject(0) { |n, i| n <= 1 && subschema_idx_valid[i] ? n + 1 : n }
|
|
131
|
+
if nvalid != 0
|
|
132
|
+
applicator_idxs = oneOf_idxs.select { |i| subschema_idx_valid[i] }
|
|
133
|
+
else
|
|
134
|
+
# invalid application: if none of the oneOf were valid, we apply them all.
|
|
135
|
+
# note: invalid application does not apply when multiple oneOfs validate.
|
|
136
|
+
applicator_idxs = oneOf_idxs
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
applicator_idxs.each do |i|
|
|
140
|
+
inplace_subschema_applicate(['oneOf', i])
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end # element.add_action(:inplace_applicate)
|
|
144
|
+
|
|
145
|
+
element.add_action(:validate) do
|
|
146
|
+
if keyword?('oneOf')
|
|
147
|
+
value = schema_content['oneOf']
|
|
148
|
+
# This keyword's value MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
|
|
149
|
+
if value.respond_to?(:to_ary)
|
|
150
|
+
# An instance validates successfully against this keyword if it validates successfully against
|
|
151
|
+
# exactly one schema defined by this keyword's value.
|
|
152
|
+
oneOf_results = value.each_index.map do |i|
|
|
153
|
+
inplace_subschema_validate(['oneOf', i])
|
|
154
|
+
end
|
|
155
|
+
if oneOf_results.none?(&:valid?)
|
|
156
|
+
inplace_results_validate(
|
|
157
|
+
false,
|
|
158
|
+
'validation.keyword.oneOf.not_any_valid',
|
|
159
|
+
"instance is not valid against any `oneOf` schema",
|
|
160
|
+
keyword: 'oneOf',
|
|
161
|
+
results: oneOf_results,
|
|
162
|
+
oneOf_indexes_valid: oneOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
|
|
163
|
+
)
|
|
164
|
+
else
|
|
165
|
+
inplace_results_validate(
|
|
166
|
+
oneOf_results.select(&:valid?).size == 1,
|
|
167
|
+
'validation.keyword.oneOf.multiple_valid',
|
|
168
|
+
"instance is valid against multiple `oneOf` schemas",
|
|
169
|
+
keyword: 'oneOf',
|
|
170
|
+
results: oneOf_results,
|
|
171
|
+
oneOf_indexes_valid: oneOf_results.each_with_index.inject({}) { |h, (r, i)| h.update({i => r.valid?}) }.freeze,
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end # element.add_action(:validate)
|
|
177
|
+
end # Schema::Element.new
|
|
178
|
+
end # ONE_OF = element_map
|
|
179
|
+
end # module Schema::Elements
|
|
180
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
MAX_LENGTH = element_map do
|
|
6
|
+
Schema::Element.new(keyword: 'maxLength') do |element|
|
|
7
|
+
element.add_action(:validate) do
|
|
8
|
+
if keyword?('maxLength')
|
|
9
|
+
value = schema_content['maxLength']
|
|
10
|
+
# The value of this keyword MUST be a non-negative integer.
|
|
11
|
+
if internal_integer?(value) && value >= 0
|
|
12
|
+
if instance.respond_to?(:to_str)
|
|
13
|
+
# A string instance is valid against this keyword if its length is less than, or equal to, the
|
|
14
|
+
# value of this keyword.
|
|
15
|
+
length = instance.to_str.length
|
|
16
|
+
validate(
|
|
17
|
+
length <= value,
|
|
18
|
+
'validation.keyword.maxLength.length_greater',
|
|
19
|
+
"instance string length is greater than `maxLength` value",
|
|
20
|
+
keyword: 'maxLength',
|
|
21
|
+
instance_length: length,
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end # element.add_action(:validate)
|
|
27
|
+
end # Schema::Element.new
|
|
28
|
+
end # MAX_LENGTH = element_map
|
|
29
|
+
end # module Schema::Elements
|
|
30
|
+
|
|
31
|
+
module Schema::Elements
|
|
32
|
+
MIN_LENGTH = element_map do
|
|
33
|
+
Schema::Element.new(keyword: 'minLength') do |element|
|
|
34
|
+
element.add_action(:validate) do
|
|
35
|
+
if keyword?('minLength')
|
|
36
|
+
value = schema_content['minLength']
|
|
37
|
+
# The value of this keyword MUST be a non-negative integer.
|
|
38
|
+
if internal_integer?(value) && value >= 0
|
|
39
|
+
if instance.respond_to?(:to_str)
|
|
40
|
+
# A string instance is valid against this keyword if its length is greater than, or equal to, the
|
|
41
|
+
# value of this keyword.
|
|
42
|
+
length = instance.to_str.length
|
|
43
|
+
validate(
|
|
44
|
+
length >= value,
|
|
45
|
+
'validation.keyword.minLength.length_less',
|
|
46
|
+
"instance string length is less than `minLength` value",
|
|
47
|
+
keyword: 'minLength',
|
|
48
|
+
instance_length: length,
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end # element.add_action(:validate)
|
|
54
|
+
end # Schema::Element.new
|
|
55
|
+
end # MIN_LENGTH = element_map
|
|
56
|
+
end # module Schema::Elements
|
|
57
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
#> String values MUST be one of the six primitive types
|
|
6
|
+
#> ("null", "boolean", "object", "array", "number", or "string"),
|
|
7
|
+
#> or "integer"
|
|
8
|
+
instance_types = {
|
|
9
|
+
'null' => proc { instance == nil },
|
|
10
|
+
'boolean' => proc { instance == true || instance == false },
|
|
11
|
+
'object' => proc { instance.respond_to?(:to_hash) },
|
|
12
|
+
'array' => proc { instance.respond_to?(:to_ary) },
|
|
13
|
+
'string' => proc { instance.respond_to?(:to_str) },
|
|
14
|
+
'number' => proc { instance.is_a?(Numeric) },
|
|
15
|
+
'integer' => proc { internal_integer?(instance) },
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
TYPE = element_map do
|
|
19
|
+
Schema::Element.new(keyword: 'type') do |element|
|
|
20
|
+
element.add_action(:validate) do
|
|
21
|
+
next if !keyword?('type')
|
|
22
|
+
value = schema_content['type']
|
|
23
|
+
#> The value of this keyword MUST be either a string or an array. If it is an array, elements of
|
|
24
|
+
#> the array MUST be strings and MUST be unique.
|
|
25
|
+
types = value.respond_to?(:to_ary) ? value : [value]
|
|
26
|
+
matched_type = types.any? do |type|
|
|
27
|
+
if instance_types.key?(type)
|
|
28
|
+
instance_exec(&instance_types[type])
|
|
29
|
+
else
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
validate(
|
|
34
|
+
matched_type,
|
|
35
|
+
'validation.keyword.type.not_match',
|
|
36
|
+
'instance type does not match `type` value',
|
|
37
|
+
keyword: 'type',
|
|
38
|
+
)
|
|
39
|
+
end # element.add_action(:validate)
|
|
40
|
+
end # Schema::Element.new
|
|
41
|
+
end # TYPE = element_map
|
|
42
|
+
end # module Schema::Elements
|
|
43
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
# https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-unevaluateditems
|
|
6
|
+
UNEVALUATED_ITEMS = element_map do
|
|
7
|
+
Schema::Element.new(keyword: 'unevaluatedItems') do |element|
|
|
8
|
+
element.depends_on_elements do |other_element|
|
|
9
|
+
other_element.invokes?(:inplace_applicate) ||
|
|
10
|
+
(other_element.invokes?(:child_applicate) && !other_element.invokes?(:application_requires_evaluated))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
element.add_action(:application_requires_evaluated) { cxt_yield(true) if keyword?('unevaluatedItems') }
|
|
14
|
+
|
|
15
|
+
element.add_action(:subschema) do
|
|
16
|
+
#> The value of "unevaluatedItems" MUST be a valid JSON Schema.
|
|
17
|
+
if keyword?('unevaluatedItems')
|
|
18
|
+
cxt_yield(['unevaluatedItems'])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
element.add_action(:child_applicate) do
|
|
23
|
+
if instance.respond_to?(:to_ary)
|
|
24
|
+
if keyword?('unevaluatedItems')
|
|
25
|
+
if !evaluated
|
|
26
|
+
child_subschema_applicate(['unevaluatedItems'])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
element.add_action(:validate) do
|
|
33
|
+
next if !keyword?('unevaluatedItems')
|
|
34
|
+
next if !instance.respond_to?(:to_ary)
|
|
35
|
+
results = {}
|
|
36
|
+
instance.each_index do |i|
|
|
37
|
+
if !result.evaluated_tokens.include?(i)
|
|
38
|
+
results[i] = child_subschema_validate(i, ['unevaluatedItems'])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
child_results_validate(
|
|
43
|
+
results.each_value.all?(&:valid?),
|
|
44
|
+
'validation.keyword.unevaluatedItems.invalid',
|
|
45
|
+
"instance array unevaluated items are not all valid against `unevaluatedItems` schema",
|
|
46
|
+
keyword: 'unevaluatedItems',
|
|
47
|
+
child_results: results,
|
|
48
|
+
instance_indexes_valid: results.inject({}) { |h, (i, r)| h.update({i => r.valid?}) }.freeze,
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Elements
|
|
5
|
+
# https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-01#name-unevaluatedproperties
|
|
6
|
+
UNEVALUATED_PROPERTIES = element_map do
|
|
7
|
+
Schema::Element.new(keyword: 'unevaluatedProperties') do |element|
|
|
8
|
+
element.depends_on_elements do |other_element|
|
|
9
|
+
other_element.invokes?(:inplace_applicate) ||
|
|
10
|
+
(other_element.invokes?(:child_applicate) && !other_element.invokes?(:application_requires_evaluated))
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
element.add_action(:application_requires_evaluated) { cxt_yield(true) if keyword?('unevaluatedProperties') }
|
|
14
|
+
|
|
15
|
+
element.add_action(:subschema) do
|
|
16
|
+
#> The value of "unevaluatedProperties" MUST be a valid JSON Schema.
|
|
17
|
+
if keyword?('unevaluatedProperties')
|
|
18
|
+
cxt_yield(['unevaluatedProperties'])
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
element.add_action(:child_applicate) do
|
|
23
|
+
if instance.respond_to?(:to_hash)
|
|
24
|
+
if keyword?('unevaluatedProperties')
|
|
25
|
+
if !evaluated
|
|
26
|
+
child_subschema_applicate(['unevaluatedProperties'])
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
element.add_action(:validate) do
|
|
33
|
+
next if !keyword?('unevaluatedProperties')
|
|
34
|
+
next if !instance.respond_to?(:to_hash)
|
|
35
|
+
results = {}
|
|
36
|
+
instance.each_key do |property_name|
|
|
37
|
+
if !result.evaluated_tokens.include?(property_name)
|
|
38
|
+
results[property_name] = child_subschema_validate(property_name, ['unevaluatedProperties'])
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
child_results_validate(
|
|
43
|
+
results.each_value.all?(&:valid?),
|
|
44
|
+
'validation.keyword.unevaluatedProperties.invalid',
|
|
45
|
+
"instance object unevaluated properties are not all valid against `unevaluatedProperties` schema",
|
|
46
|
+
keyword: 'unevaluatedProperties',
|
|
47
|
+
child_results: results,
|
|
48
|
+
instance_properties_valid: results.inject({}) { |h, (k, r)| h.update({k => r.valid?}) }.freeze,
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|