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.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -4
  3. data/CHANGELOG.md +19 -0
  4. data/LICENSE.md +2 -3
  5. data/README.md +87 -43
  6. data/docs/{glossary.md → Glossary.md} +84 -52
  7. data/jsi.gemspec +1 -1
  8. data/lib/jsi/base/mutability.rb +48 -0
  9. data/lib/jsi/base/node.rb +66 -52
  10. data/lib/jsi/base.rb +592 -176
  11. data/lib/jsi/jsi_coder.rb +4 -2
  12. data/lib/jsi/metaschema_node/bootstrap_schema.rb +118 -59
  13. data/lib/jsi/metaschema_node.rb +244 -154
  14. data/lib/jsi/ptr.rb +45 -17
  15. data/lib/jsi/ref.rb +197 -0
  16. data/lib/jsi/registry.rb +311 -0
  17. data/lib/jsi/schema/cxt/child_application.rb +35 -0
  18. data/lib/jsi/schema/cxt/inplace_application.rb +37 -0
  19. data/lib/jsi/schema/cxt.rb +80 -0
  20. data/lib/jsi/schema/dialect.rb +137 -0
  21. data/lib/jsi/schema/draft04.rb +113 -5
  22. data/lib/jsi/schema/draft06.rb +123 -5
  23. data/lib/jsi/schema/draft07.rb +157 -5
  24. data/lib/jsi/schema/draft202012.rb +303 -0
  25. data/lib/jsi/schema/dynamic_anchor_map.rb +63 -0
  26. data/lib/jsi/schema/element.rb +69 -0
  27. data/lib/jsi/schema/elements/anchor.rb +13 -0
  28. data/lib/jsi/schema/elements/array_validation.rb +82 -0
  29. data/lib/jsi/schema/elements/comment.rb +10 -0
  30. data/lib/jsi/schema/{validation → elements}/const.rb +11 -7
  31. data/lib/jsi/schema/elements/contains.rb +59 -0
  32. data/lib/jsi/schema/elements/contains_minmax.rb +91 -0
  33. data/lib/jsi/schema/elements/content_encoding.rb +10 -0
  34. data/lib/jsi/schema/elements/content_media_type.rb +10 -0
  35. data/lib/jsi/schema/elements/content_schema.rb +16 -0
  36. data/lib/jsi/schema/elements/default.rb +11 -0
  37. data/lib/jsi/schema/elements/definitions.rb +19 -0
  38. data/lib/jsi/schema/elements/dependencies.rb +99 -0
  39. data/lib/jsi/schema/elements/dependent_required.rb +49 -0
  40. data/lib/jsi/schema/elements/dependent_schemas.rb +69 -0
  41. data/lib/jsi/schema/elements/dynamic_ref.rb +69 -0
  42. data/lib/jsi/schema/elements/enum.rb +26 -0
  43. data/lib/jsi/schema/elements/examples.rb +10 -0
  44. data/lib/jsi/schema/elements/format.rb +10 -0
  45. data/lib/jsi/schema/elements/id.rb +30 -0
  46. data/lib/jsi/schema/elements/if_then_else.rb +82 -0
  47. data/lib/jsi/schema/elements/info_bool.rb +10 -0
  48. data/lib/jsi/schema/elements/info_string.rb +10 -0
  49. data/lib/jsi/schema/elements/items.rb +93 -0
  50. data/lib/jsi/schema/elements/items_prefixed.rb +96 -0
  51. data/lib/jsi/schema/elements/not.rb +31 -0
  52. data/lib/jsi/schema/elements/numeric.rb +137 -0
  53. data/lib/jsi/schema/elements/numeric_draft04.rb +77 -0
  54. data/lib/jsi/schema/elements/object_validation.rb +55 -0
  55. data/lib/jsi/schema/elements/pattern.rb +35 -0
  56. data/lib/jsi/schema/elements/properties.rb +145 -0
  57. data/lib/jsi/schema/elements/property_names.rb +48 -0
  58. data/lib/jsi/schema/elements/ref.rb +62 -0
  59. data/lib/jsi/schema/elements/required.rb +34 -0
  60. data/lib/jsi/schema/elements/self.rb +24 -0
  61. data/lib/jsi/schema/elements/some_of.rb +180 -0
  62. data/lib/jsi/schema/elements/string_validation.rb +57 -0
  63. data/lib/jsi/schema/elements/type.rb +43 -0
  64. data/lib/jsi/schema/elements/unevaluated_items.rb +54 -0
  65. data/lib/jsi/schema/elements/unevaluated_properties.rb +54 -0
  66. data/lib/jsi/schema/elements/xschema.rb +10 -0
  67. data/lib/jsi/schema/elements/xvocabulary.rb +10 -0
  68. data/lib/jsi/schema/elements.rb +101 -0
  69. data/lib/jsi/schema/issue.rb +3 -4
  70. data/lib/jsi/schema/schema_ancestor_node.rb +105 -52
  71. data/lib/jsi/schema/vocabulary.rb +36 -0
  72. data/lib/jsi/schema.rb +598 -383
  73. data/lib/jsi/schema_classes.rb +195 -141
  74. data/lib/jsi/schema_set.rb +85 -128
  75. data/lib/jsi/set.rb +23 -0
  76. data/lib/jsi/simple_wrap.rb +14 -17
  77. data/lib/jsi/struct.rb +57 -0
  78. data/lib/jsi/uri.rb +40 -0
  79. data/lib/jsi/util/private/memo_map.rb +9 -13
  80. data/lib/jsi/util/private.rb +59 -31
  81. data/lib/jsi/util/typelike.rb +19 -60
  82. data/lib/jsi/util.rb +53 -34
  83. data/lib/jsi/validation/error.rb +45 -2
  84. data/lib/jsi/validation/result.rb +121 -90
  85. data/lib/jsi/validation.rb +1 -6
  86. data/lib/jsi/version.rb +1 -1
  87. data/lib/jsi.rb +170 -36
  88. data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +62 -0
  89. data/lib/schemas/json-schema.org/draft-04/schema.rb +60 -109
  90. data/lib/schemas/json-schema.org/draft-06/schema.rb +53 -108
  91. data/lib/schemas/json-schema.org/draft-07/schema.rb +63 -127
  92. data/readme.rb +4 -4
  93. data/{resources}/schemas/2020-12_strict.json +19 -0
  94. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/applicator.json +48 -0
  95. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/content.json +17 -0
  96. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/core.json +51 -0
  97. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-annotation.json +14 -0
  98. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-assertion.json +14 -0
  99. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/meta-data.json +37 -0
  100. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/unevaluated.json +15 -0
  101. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/validation.json +98 -0
  102. data/{resources}/schemas/json-schema.org/draft/2020-12/schema.json +58 -0
  103. metadata +73 -52
  104. data/lib/jsi/metaschema.rb +0 -6
  105. data/lib/jsi/schema/application/child_application/contains.rb +0 -25
  106. data/lib/jsi/schema/application/child_application/draft04.rb +0 -21
  107. data/lib/jsi/schema/application/child_application/draft06.rb +0 -28
  108. data/lib/jsi/schema/application/child_application/draft07.rb +0 -28
  109. data/lib/jsi/schema/application/child_application/items.rb +0 -18
  110. data/lib/jsi/schema/application/child_application/properties.rb +0 -25
  111. data/lib/jsi/schema/application/child_application.rb +0 -13
  112. data/lib/jsi/schema/application/draft04.rb +0 -8
  113. data/lib/jsi/schema/application/draft06.rb +0 -8
  114. data/lib/jsi/schema/application/draft07.rb +0 -8
  115. data/lib/jsi/schema/application/inplace_application/dependencies.rb +0 -28
  116. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -25
  117. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -26
  118. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -32
  119. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +0 -20
  120. data/lib/jsi/schema/application/inplace_application/ref.rb +0 -18
  121. data/lib/jsi/schema/application/inplace_application/someof.rb +0 -44
  122. data/lib/jsi/schema/application/inplace_application.rb +0 -14
  123. data/lib/jsi/schema/application.rb +0 -12
  124. data/lib/jsi/schema/ref.rb +0 -183
  125. data/lib/jsi/schema/validation/array.rb +0 -69
  126. data/lib/jsi/schema/validation/contains.rb +0 -25
  127. data/lib/jsi/schema/validation/dependencies.rb +0 -49
  128. data/lib/jsi/schema/validation/draft04/minmax.rb +0 -91
  129. data/lib/jsi/schema/validation/draft04.rb +0 -110
  130. data/lib/jsi/schema/validation/draft06.rb +0 -120
  131. data/lib/jsi/schema/validation/draft07.rb +0 -157
  132. data/lib/jsi/schema/validation/enum.rb +0 -25
  133. data/lib/jsi/schema/validation/ifthenelse.rb +0 -46
  134. data/lib/jsi/schema/validation/items.rb +0 -54
  135. data/lib/jsi/schema/validation/not.rb +0 -20
  136. data/lib/jsi/schema/validation/numeric.rb +0 -121
  137. data/lib/jsi/schema/validation/object.rb +0 -45
  138. data/lib/jsi/schema/validation/pattern.rb +0 -34
  139. data/lib/jsi/schema/validation/properties.rb +0 -101
  140. data/lib/jsi/schema/validation/property_names.rb +0 -32
  141. data/lib/jsi/schema/validation/ref.rb +0 -40
  142. data/lib/jsi/schema/validation/required.rb +0 -27
  143. data/lib/jsi/schema/validation/someof.rb +0 -90
  144. data/lib/jsi/schema/validation/string.rb +0 -47
  145. data/lib/jsi/schema/validation/type.rb +0 -49
  146. data/lib/jsi/schema/validation.rb +0 -49
  147. data/lib/jsi/schema_registry.rb +0 -190
  148. 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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Schema::Elements
5
+ XSCHEMA = element_map do
6
+ Schema::Element.new(keyword: '$schema') do |element|
7
+ end # Schema::Element.new
8
+ end # XSCHEMA = element_map
9
+ end # module Schema::Elements
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ module Schema::Elements
5
+ XVOCABULARY = element_map do
6
+ Schema::Element.new(keyword: '$vocabulary') do |element|
7
+ end
8
+ end
9
+ end
10
+ end