jsi 0.8.2 → 0.9.0

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 (146) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -2
  3. data/CHANGELOG.md +8 -3
  4. data/LICENSE.md +2 -3
  5. data/README.md +68 -31
  6. data/docs/Glossary.md +313 -0
  7. data/jsi.gemspec +1 -0
  8. data/lib/jsi/base/mutability.rb +4 -0
  9. data/lib/jsi/base/node.rb +63 -24
  10. data/lib/jsi/base.rb +556 -173
  11. data/lib/jsi/metaschema_node/bootstrap_schema.rb +106 -56
  12. data/lib/jsi/metaschema_node.rb +227 -160
  13. data/lib/jsi/ptr.rb +32 -15
  14. data/lib/jsi/ref.rb +197 -0
  15. data/lib/jsi/registry.rb +311 -0
  16. data/lib/jsi/schema/cxt/child_application.rb +35 -0
  17. data/lib/jsi/schema/cxt/inplace_application.rb +37 -0
  18. data/lib/jsi/schema/cxt.rb +80 -0
  19. data/lib/jsi/schema/dialect.rb +137 -0
  20. data/lib/jsi/schema/draft04.rb +113 -5
  21. data/lib/jsi/schema/draft06.rb +123 -5
  22. data/lib/jsi/schema/draft07.rb +157 -5
  23. data/lib/jsi/schema/draft202012.rb +303 -0
  24. data/lib/jsi/schema/dynamic_anchor_map.rb +63 -0
  25. data/lib/jsi/schema/element.rb +69 -0
  26. data/lib/jsi/schema/elements/anchor.rb +13 -0
  27. data/lib/jsi/schema/elements/array_validation.rb +82 -0
  28. data/lib/jsi/schema/elements/comment.rb +10 -0
  29. data/lib/jsi/schema/{validation → elements}/const.rb +11 -7
  30. data/lib/jsi/schema/elements/contains.rb +59 -0
  31. data/lib/jsi/schema/elements/contains_minmax.rb +91 -0
  32. data/lib/jsi/schema/elements/content_encoding.rb +10 -0
  33. data/lib/jsi/schema/elements/content_media_type.rb +10 -0
  34. data/lib/jsi/schema/elements/content_schema.rb +16 -0
  35. data/lib/jsi/schema/elements/default.rb +11 -0
  36. data/lib/jsi/schema/elements/definitions.rb +19 -0
  37. data/lib/jsi/schema/elements/dependencies.rb +99 -0
  38. data/lib/jsi/schema/elements/dependent_required.rb +49 -0
  39. data/lib/jsi/schema/elements/dependent_schemas.rb +69 -0
  40. data/lib/jsi/schema/elements/dynamic_ref.rb +69 -0
  41. data/lib/jsi/schema/elements/enum.rb +26 -0
  42. data/lib/jsi/schema/elements/examples.rb +10 -0
  43. data/lib/jsi/schema/elements/format.rb +10 -0
  44. data/lib/jsi/schema/elements/id.rb +30 -0
  45. data/lib/jsi/schema/elements/if_then_else.rb +82 -0
  46. data/lib/jsi/schema/elements/info_bool.rb +10 -0
  47. data/lib/jsi/schema/elements/info_string.rb +10 -0
  48. data/lib/jsi/schema/elements/items.rb +93 -0
  49. data/lib/jsi/schema/elements/items_prefixed.rb +96 -0
  50. data/lib/jsi/schema/elements/not.rb +31 -0
  51. data/lib/jsi/schema/elements/numeric.rb +137 -0
  52. data/lib/jsi/schema/elements/numeric_draft04.rb +77 -0
  53. data/lib/jsi/schema/elements/object_validation.rb +55 -0
  54. data/lib/jsi/schema/elements/pattern.rb +35 -0
  55. data/lib/jsi/schema/elements/properties.rb +145 -0
  56. data/lib/jsi/schema/elements/property_names.rb +48 -0
  57. data/lib/jsi/schema/elements/ref.rb +62 -0
  58. data/lib/jsi/schema/elements/required.rb +34 -0
  59. data/lib/jsi/schema/elements/self.rb +24 -0
  60. data/lib/jsi/schema/elements/some_of.rb +180 -0
  61. data/lib/jsi/schema/elements/string_validation.rb +57 -0
  62. data/lib/jsi/schema/elements/type.rb +43 -0
  63. data/lib/jsi/schema/elements/unevaluated_items.rb +54 -0
  64. data/lib/jsi/schema/elements/unevaluated_properties.rb +54 -0
  65. data/lib/jsi/schema/elements/xschema.rb +10 -0
  66. data/lib/jsi/schema/elements/xvocabulary.rb +10 -0
  67. data/lib/jsi/schema/elements.rb +101 -0
  68. data/lib/jsi/schema/issue.rb +3 -4
  69. data/lib/jsi/schema/schema_ancestor_node.rb +103 -50
  70. data/lib/jsi/schema/vocabulary.rb +36 -0
  71. data/lib/jsi/schema.rb +519 -337
  72. data/lib/jsi/schema_classes.rb +168 -124
  73. data/lib/jsi/schema_set.rb +67 -126
  74. data/lib/jsi/set.rb +23 -0
  75. data/lib/jsi/simple_wrap.rb +13 -16
  76. data/lib/jsi/struct.rb +57 -0
  77. data/lib/jsi/uri.rb +40 -0
  78. data/lib/jsi/util/private/memo_map.rb +9 -13
  79. data/lib/jsi/util/private.rb +57 -12
  80. data/lib/jsi/util/typelike.rb +19 -64
  81. data/lib/jsi/util.rb +52 -34
  82. data/lib/jsi/validation/error.rb +41 -2
  83. data/lib/jsi/validation/result.rb +118 -71
  84. data/lib/jsi/validation.rb +1 -6
  85. data/lib/jsi/version.rb +1 -1
  86. data/lib/jsi.rb +158 -41
  87. data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +67 -0
  88. data/lib/schemas/json-schema.org/draft-04/schema.rb +63 -106
  89. data/lib/schemas/json-schema.org/draft-06/schema.rb +56 -105
  90. data/lib/schemas/json-schema.org/draft-07/schema.rb +67 -124
  91. data/readme.rb +3 -3
  92. data/{resources}/schemas/2020-12_strict.json +19 -0
  93. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/applicator.json +48 -0
  94. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/content.json +17 -0
  95. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/core.json +51 -0
  96. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-annotation.json +14 -0
  97. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-assertion.json +14 -0
  98. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/meta-data.json +37 -0
  99. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/unevaluated.json +15 -0
  100. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/validation.json +98 -0
  101. data/{resources}/schemas/json-schema.org/draft/2020-12/schema.json +58 -0
  102. metadata +69 -47
  103. data/lib/jsi/schema/application/child_application/contains.rb +0 -25
  104. data/lib/jsi/schema/application/child_application/draft04.rb +0 -21
  105. data/lib/jsi/schema/application/child_application/draft06.rb +0 -28
  106. data/lib/jsi/schema/application/child_application/draft07.rb +0 -28
  107. data/lib/jsi/schema/application/child_application/items.rb +0 -18
  108. data/lib/jsi/schema/application/child_application/properties.rb +0 -25
  109. data/lib/jsi/schema/application/child_application.rb +0 -13
  110. data/lib/jsi/schema/application/draft04.rb +0 -8
  111. data/lib/jsi/schema/application/draft06.rb +0 -8
  112. data/lib/jsi/schema/application/draft07.rb +0 -8
  113. data/lib/jsi/schema/application/inplace_application/dependencies.rb +0 -28
  114. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -25
  115. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -26
  116. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -32
  117. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +0 -20
  118. data/lib/jsi/schema/application/inplace_application/ref.rb +0 -18
  119. data/lib/jsi/schema/application/inplace_application/someof.rb +0 -44
  120. data/lib/jsi/schema/application/inplace_application.rb +0 -14
  121. data/lib/jsi/schema/application.rb +0 -12
  122. data/lib/jsi/schema/ref.rb +0 -186
  123. data/lib/jsi/schema/validation/array.rb +0 -69
  124. data/lib/jsi/schema/validation/contains.rb +0 -25
  125. data/lib/jsi/schema/validation/dependencies.rb +0 -49
  126. data/lib/jsi/schema/validation/draft04/minmax.rb +0 -93
  127. data/lib/jsi/schema/validation/draft04.rb +0 -110
  128. data/lib/jsi/schema/validation/draft06.rb +0 -120
  129. data/lib/jsi/schema/validation/draft07.rb +0 -157
  130. data/lib/jsi/schema/validation/enum.rb +0 -25
  131. data/lib/jsi/schema/validation/ifthenelse.rb +0 -46
  132. data/lib/jsi/schema/validation/items.rb +0 -54
  133. data/lib/jsi/schema/validation/not.rb +0 -20
  134. data/lib/jsi/schema/validation/numeric.rb +0 -121
  135. data/lib/jsi/schema/validation/object.rb +0 -45
  136. data/lib/jsi/schema/validation/pattern.rb +0 -34
  137. data/lib/jsi/schema/validation/properties.rb +0 -101
  138. data/lib/jsi/schema/validation/property_names.rb +0 -32
  139. data/lib/jsi/schema/validation/ref.rb +0 -40
  140. data/lib/jsi/schema/validation/required.rb +0 -27
  141. data/lib/jsi/schema/validation/someof.rb +0 -90
  142. data/lib/jsi/schema/validation/string.rb +0 -47
  143. data/lib/jsi/schema/validation/type.rb +0 -49
  144. data/lib/jsi/schema/validation.rb +0 -49
  145. data/lib/jsi/schema_registry.rb +0 -200
  146. data/lib/jsi/util/private/attr_struct.rb +0 -141
@@ -5,6 +5,8 @@ module JSI
5
5
  #
6
6
  # this module is intended to be internal to JSI. no guarantees or API promises
7
7
  # are made for non-JSI classes including this module.
8
+ #
9
+ # @api private
8
10
  module Util::Hashlike
9
11
  include(Enumerable)
10
12
 
@@ -13,17 +15,14 @@ module JSI
13
15
 
14
16
  # methods which do not need to access the value.
15
17
  SAFE_KEY_ONLY_METHODS = %w(each_key empty? has_key? include? key? keys length member? size).map(&:freeze).freeze
16
- SAFE_KEY_VALUE_METHODS = %w(< <= > >= any? assoc compact dig each_pair each_value fetch fetch_values has_value? invert key merge rassoc reject select to_h to_proc transform_values value? values values_at).map(&:freeze).freeze
17
- DESTRUCTIVE_METHODS = %w(clear delete delete_if keep_if reject! replace select! shift).map(&:freeze).freeze
18
+ SAFE_KEY_VALUE_METHODS = %w(< <= > >= any? assoc compact dig each_pair each_value fetch fetch_values flatten has_value? invert key merge rassoc reject select filter to_h to_proc transform_values value? values values_at).map(&:freeze).freeze
19
+ DESTRUCTIVE_METHODS = %w(clear delete delete_if filter! flatten! keep_if reject! replace select! shift).map(&:freeze).freeze
18
20
  # these return a modified copy
19
- safe_modified_copy_methods = %w(compact)
20
- # select and reject will return a modified copy but need the yielded block variable value from #[]
21
- safe_kv_block_modified_copy_methods = %w(select reject)
21
+ safe_modified_copy_methods = %w(compact slice except)
22
22
  SAFE_METHODS = SAFE_KEY_ONLY_METHODS | SAFE_KEY_VALUE_METHODS
23
23
  custom_methods = %w(merge) # defined below
24
24
  safe_to_hash_methods = SAFE_METHODS -
25
25
  safe_modified_copy_methods -
26
- safe_kv_block_modified_copy_methods -
27
26
  custom_methods
28
27
  safe_to_hash_methods.each do |method_name|
29
28
  if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
@@ -49,16 +48,6 @@ module JSI
49
48
  end
50
49
  end
51
50
  end
52
- safe_kv_block_modified_copy_methods.each do |method_name|
53
- define_method(method_name) do |**kw, &b|
54
- jsi_modified_copy do |object_to_modify|
55
- responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
56
- responsive_object.public_send(method_name) do |k, _v|
57
- b.call(k, self[k, **kw])
58
- end
59
- end
60
- end
61
- end
62
51
 
63
52
  # like [Hash#update](https://ruby-doc.org/core/Hash.html#method-i-update)
64
53
  # @param other [#to_hash] the other hash to update this hash from
@@ -93,34 +82,22 @@ module JSI
93
82
  end
94
83
  end
95
84
 
96
- # basically the same #inspect as Hash, but has the class name and, if responsive,
97
- # self's #jsi_object_group_text
98
- # @return [String]
99
- def inspect
100
- object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
101
- -"\#{<#{object_group_str}>#{map { |k, v| " #{k.inspect} => #{v.inspect}" }.join(',')}}"
102
- end
103
-
104
- def to_s
105
- inspect
106
- end
107
-
108
85
  # pretty-prints a representation of this hashlike to the given printer
109
86
  # @return [void]
110
87
  def pretty_print(q)
111
88
  object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
112
89
  q.text "\#{<#{object_group_str}>"
113
- q.group(2) {
90
+ q.group {
91
+ q.nest(2) {
114
92
  q.breakable ' ' if !empty?
115
- q.seplist(self, nil, :each_pair) { |k, v|
116
- q.group {
93
+ q.seplist(self) { |k, v|
117
94
  q.pp k
118
95
  q.text ' => '
119
96
  q.pp v
120
- }
121
97
  }
98
+ }
99
+ q.breakable('') if !empty?
122
100
  }
123
- q.breakable '' if !empty?
124
101
  q.text '}'
125
102
  end
126
103
  end
@@ -129,6 +106,8 @@ module JSI
129
106
  #
130
107
  # this module is intended to be internal to JSI. no guarantees or API promises
131
108
  # are made for non-JSI classes including this module.
109
+ #
110
+ # @api private
132
111
  module Util::Arraylike
133
112
  include(Enumerable)
134
113
 
@@ -138,17 +117,14 @@ module JSI
138
117
  # methods which do not need to access the element.
139
118
  SAFE_INDEX_ONLY_METHODS = %w(each_index empty? length size).map(&:freeze).freeze
140
119
  # there are some ambiguous ones that are omitted, like #sort, #map / #collect.
141
- SAFE_INDEX_ELEMENT_METHODS = %w(| & * + - <=> abbrev at bsearch bsearch_index combination compact count cycle dig drop drop_while fetch find_index first include? index join last pack permutation product reject repeated_combination repeated_permutation reverse reverse_each rindex rotate sample select shelljoin shuffle slice sort take take_while transpose uniq values_at zip).map(&:freeze).freeze
120
+ SAFE_INDEX_ELEMENT_METHODS = %w(| & * + - <=> abbrev at bsearch bsearch_index combination compact count cycle difference dig drop drop_while fetch find_index first include? index intersection intersect? join last pack permutation product reject repeated_combination repeated_permutation reverse reverse_each rindex rotate sample select shelljoin shuffle slice sort take take_while transpose union uniq values_at zip).map(&:freeze).freeze
142
121
  DESTRUCTIVE_METHODS = %w(<< clear collect! compact! concat delete delete_at delete_if fill flatten! insert keep_if map! pop push reject! replace reverse! rotate! select! shift shuffle! slice! sort! sort_by! uniq! unshift).map(&:freeze).freeze
143
122
 
144
123
  # methods (well, method) that returns a modified copy and doesn't need any handling of block variable(s)
145
124
  safe_modified_copy_methods = %w(compact)
146
125
 
147
- # methods that return a modified copy and do need handling of block variables
148
- safe_el_block_methods = %w(reject select)
149
-
150
126
  SAFE_METHODS = SAFE_INDEX_ONLY_METHODS | SAFE_INDEX_ELEMENT_METHODS
151
- safe_to_ary_methods = SAFE_METHODS - safe_modified_copy_methods - safe_el_block_methods
127
+ safe_to_ary_methods = SAFE_METHODS - safe_modified_copy_methods
152
128
  safe_to_ary_methods.each do |method_name|
153
129
  if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
154
130
  define_method(method_name) { |*a, &b| to_ary.public_send(method_name, *a, &b) }
@@ -173,17 +149,6 @@ module JSI
173
149
  end
174
150
  end
175
151
  end
176
- safe_el_block_methods.each do |method_name|
177
- define_method(method_name) do |**kw, &b|
178
- jsi_modified_copy do |object_to_modify|
179
- i = 0
180
- responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
181
- responsive_object.public_send(method_name) do |_e|
182
- b.call(self[i, **kw]).tap { i += 1 }
183
- end
184
- end
185
- end
186
- end
187
152
 
188
153
  # see [Array#assoc](https://ruby-doc.org/core/Array.html#method-i-assoc)
189
154
  def assoc(obj)
@@ -199,30 +164,20 @@ module JSI
199
164
  detect { |e| e.respond_to?(:to_ary) and e[1] == obj }
200
165
  end
201
166
 
202
- # basically the same #inspect as Array, but has the class name and, if responsive,
203
- # self's #jsi_object_group_text
204
- # @return [String]
205
- def inspect
206
- object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
207
- -"\#[<#{object_group_str}>#{map { |e| ' ' + e.inspect }.join(',')}]"
208
- end
209
-
210
- def to_s
211
- inspect
212
- end
213
-
214
167
  # pretty-prints a representation of this arraylike to the given printer
215
168
  # @return [void]
216
169
  def pretty_print(q)
217
170
  object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
218
171
  q.text "\#[<#{object_group_str}>"
219
- q.group(2) {
172
+ q.group {
173
+ q.nest(2) {
220
174
  q.breakable ' ' if !empty?
221
- q.seplist(self, nil, :each) { |e|
175
+ q.seplist(self) { |e|
222
176
  q.pp e
223
177
  }
178
+ }
179
+ q.breakable('') if !empty?
224
180
  }
225
- q.breakable '' if !empty?
226
181
  q.text ']'
227
182
  end
228
183
  end
data/lib/jsi/util.rb CHANGED
@@ -1,10 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require("delegate")
4
+
3
5
  module JSI
4
6
  # JSI::Util contains public utilities
5
7
  module Util
6
8
  autoload :Private, 'jsi/util/private'
7
9
 
10
+ # common methods of inspecting / pretty-printing
11
+ # @private (not in Util::Private due to dependency order)
12
+ module Pretty
13
+ # @return [String]
14
+ def inspect
15
+ out = String.new
16
+ PP.singleline_pp(self, out)
17
+ out.freeze
18
+ end
19
+
20
+ # @return [String]
21
+ def to_s
22
+ inspect
23
+ end
24
+
25
+ private
26
+
27
+ def jsi_pp_object_group(q, pres = [self.class.name].freeze, empty: false)
28
+ q.text('#<')
29
+ pres.each_with_index do |pre, i|
30
+ q.text(' ') if i != 0
31
+ q.text(pre.to_s)
32
+ end
33
+ if block_given? && !empty
34
+ q.group do
35
+ q.nest(2) do
36
+ q.breakable(' ')
37
+ yield
38
+ end
39
+ q.breakable('')
40
+ end
41
+ end
42
+ q.text('>')
43
+ end
44
+ end
45
+
8
46
  include Private
9
47
 
10
48
  extend self
@@ -32,7 +70,7 @@ module JSI
32
70
 
33
71
  # A structure like the given `object`, recursively coerced to JSON-compatible types.
34
72
  #
35
- # - Structures of Hash, Array, and basic types of String/number/boolean/nil are returned as-is.
73
+ # - Structures of Hash, Array, and simple types of String/number/boolean/nil are returned as-is.
36
74
  # - If the object responds to `#as_json`, that method is used, passing any given options.
37
75
  # - If the object supports [implicit conversion](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html)
38
76
  # with `#to_hash`, `#to_ary`, `#to_str`, or `#to_int`, that is used.
@@ -46,7 +84,7 @@ module JSI
46
84
  type_err = proc { raise(TypeError, "cannot express object as json: #{object.pretty_inspect.chomp}") }
47
85
  if object.respond_to?(:as_json)
48
86
  options.empty? ? object.as_json : object.as_json(**options) # TODO remove eventually (keyword argument compatibility)
49
- elsif object.is_a?(Addressable::URI)
87
+ elsif object.is_a?(URI)
50
88
  object.to_s
51
89
  elsif object.respond_to?(:to_hash) && (object_to_hash = object.to_hash).is_a?(Hash)
52
90
  result = {}
@@ -67,7 +105,7 @@ module JSI
67
105
  object
68
106
  elsif object.is_a?(Symbol)
69
107
  object.to_s
70
- elsif object.is_a?(Set)
108
+ elsif object.is_a?(::Set)
71
109
  as_json(object.to_a, **options)
72
110
  elsif object.respond_to?(:to_str) && (object_to_str = object.to_str).is_a?(String)
73
111
  object_to_str
@@ -85,10 +123,15 @@ module JSI
85
123
  # - Otherwise, JSON is generated using {as_json} to coerce to compatible types.
86
124
  # @return [String]
87
125
  def to_json(object, options = {})
126
+ options_state = options.class.name =~ /\AJSON:.*:Generator::State\z/
88
127
  if USE_TO_JSON_METHOD[object.class]
89
- options.empty? ? object.to_json : object.to_json(**options) # TODO remove eventually (keyword argument compatibility)
128
+ (options_state || !options.empty?) ? object.to_json(options) : object.to_json # TODO remove eventually (keyword argument compatibility)
90
129
  else
91
- JSON.generate(as_json(object, **options))
130
+ if options_state
131
+ JSON.generate(as_json(object), options)
132
+ else
133
+ JSON.generate(as_json(object, **options))
134
+ end
92
135
  end
93
136
  end
94
137
 
@@ -120,7 +163,7 @@ module JSI
120
163
  end
121
164
 
122
165
  def deep_stringify_symbol_keys(object)
123
- if object.respond_to?(:to_hash) && !object.is_a?(Addressable::URI)
166
+ if object.respond_to?(:to_hash) && !object.is_a?(URI)
124
167
  JSI::Util.modified_copy(object) do |hash|
125
168
  out = {}
126
169
  (hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v|
@@ -143,7 +186,9 @@ module JSI
143
186
  # the given object is not modified.
144
187
  def deep_to_frozen(object, not_implemented: nil)
145
188
  dtf = proc { |o| deep_to_frozen(o, not_implemented: not_implemented) }
146
- if object.instance_of?(Hash)
189
+ if object.is_a?(Delegator)
190
+ object.class.new(dtf[object.__getobj__]).freeze
191
+ elsif object.instance_of?(Hash)
147
192
  out = {}
148
193
  identical = object.frozen?
149
194
  object.each do |k, v|
@@ -197,32 +242,5 @@ module JSI
197
242
  end
198
243
  end
199
244
  end
200
-
201
- # ensures the given param becomes a frozen Set of Modules.
202
- # returns the param if it is already that, otherwise initializes and freezes such a Set.
203
- #
204
- # @api private
205
- # @param modules [Set, Enumerable] the object to ensure becomes a frozen Set of Modules
206
- # @return [Set] frozen Set containing the given modules
207
- # @raise [ArgumentError] when the modules param is not an Enumerable
208
- # @raise [Schema::NotASchemaError] when the modules param contains objects which are not Schemas
209
- def ensure_module_set(modules)
210
- if modules.is_a?(Set) && modules.frozen?
211
- set = modules
212
- elsif modules.is_a?(Enumerable)
213
- set = Set.new(modules).freeze
214
- else
215
- raise(TypeError, "not given an Enumerable of Modules")
216
- end
217
- not_modules = set.reject { |s| s.is_a?(Module) }
218
- if !not_modules.empty?
219
- raise(TypeError, [
220
- "ensure_module_set given non-Module objects:",
221
- *not_modules.map { |ns| ns.pretty_inspect.chomp },
222
- ].join("\n"))
223
- end
224
-
225
- set
226
- end
227
245
  end
228
246
  end
@@ -2,13 +2,15 @@
2
2
 
3
3
  module JSI
4
4
  module Validation
5
- Error = Util::AttrStruct[*%w(
5
+ Error = Struct.subclass(*%i(
6
6
  message
7
7
  keyword
8
+ additional
8
9
  schema
9
10
  instance_ptr
10
11
  instance_document
11
- )]
12
+ nested_errors
13
+ ))
12
14
 
13
15
  # a validation error of a schema instance against a schema
14
16
  #
@@ -19,6 +21,9 @@ module JSI
19
21
  # the keyword of the schema which failed to validate.
20
22
  # this may be absent if the error is not from a schema keyword (i.e, `false` schema).
21
23
  # @return [String]
24
+ # @!attribute additional
25
+ # additional contextual information about the error
26
+ # @return [Hash]
22
27
  # @!attribute schema
23
28
  # the schema against which the instance failed to validate
24
29
  # @return [JSI::Schema]
@@ -28,11 +33,45 @@ module JSI
28
33
  # @!attribute instance_document
29
34
  # document containing the instance at instance_ptr
30
35
  # @return [Object]
36
+ # @!attribute nested_errors
37
+ # @return [Set<Validation::Error>]
31
38
  class Error
32
39
  def initialize(attributes = {})
33
40
  super
34
41
  freeze
35
42
  end
43
+
44
+ # @yield [Validation::Error]
45
+ def each_validation_error(&block)
46
+ return(to_enum(__method__)) if !block_given?
47
+ nested_errors.each { |nested_error| nested_error.each_validation_error(&block) }
48
+ yield(self)
49
+ nil
50
+ end
51
+
52
+ # @return [Object]
53
+ def instance
54
+ instance_ptr.evaluate(instance_document)
55
+ end
56
+
57
+ def pretty_print(q)
58
+ info = {
59
+ message: message,
60
+ instance: instance,
61
+ instance_ptr: instance_ptr,
62
+ keyword: keyword,
63
+ additional: additional,
64
+ 'schema uri': schema.schema_uri || schema.jsi_ptr.uri,
65
+ nested_errors: nested_errors,
66
+ }
67
+ jsi_pp_object_group(q) do
68
+ q.seplist(info) do |k, v|
69
+ q.text(k.to_s)
70
+ q.text(': ')
71
+ q.pp(v)
72
+ end
73
+ end
74
+ end
36
75
  end
37
76
  end
38
77
  end
@@ -4,31 +4,20 @@ module JSI
4
4
  module Validation
5
5
  # a result of validating an instance against schemas which describe it.
6
6
  class Result
7
- Builder = Util::AttrStruct[*%w(
7
+ Builder = Schema::Cxt.subclass(*%i(
8
8
  result
9
- schema
10
9
  instance_ptr
11
10
  instance_document
12
11
  validate_only
13
12
  visited_refs
14
- )]
13
+ ))
15
14
 
16
15
  # @private
17
- # a structure used to build a Result. virtual base class.
18
- class Builder
16
+ # context to build a Validation::Result
17
+ class Builder < Schema::Cxt
19
18
  def instance
20
- instance_ptr.evaluate(instance_document)
21
- end
22
-
23
- def schema_issue(*_)
24
- end
25
-
26
- def schema_error(message, keyword = nil)
27
- schema_issue(:error, message, keyword)
28
- end
29
-
30
- def schema_warning(message, keyword = nil)
31
- schema_issue(:warning, message, keyword)
19
+ return @instance if instance_variable_defined?(:@instance)
20
+ @instance = instance_ptr.evaluate(instance_document)
32
21
  end
33
22
 
34
23
  # @param subschema_ptr [JSI::Ptr, #to_ary]
@@ -40,7 +29,6 @@ module JSI
40
29
  validate_only: validate_only,
41
30
  visited_refs: visited_refs,
42
31
  )
43
- merge_schema_issues(subresult)
44
32
  subresult
45
33
  end
46
34
 
@@ -53,134 +41,180 @@ module JSI
53
41
  instance_document,
54
42
  validate_only: validate_only,
55
43
  )
56
- merge_schema_issues(subresult)
57
44
  subresult
58
45
  end
59
46
 
60
- # @param other_result [JSI::Validation::Result]
61
- # @return [void]
62
- def merge_schema_issues(other_result)
63
- unless validate_only
64
- # schema_issues are always merged from subschema results (not depending on validation results)
65
- result.schema_issues.merge(other_result.schema_issues)
47
+ # @param results [Enumerable<Validation::Result>]
48
+ def inplace_results_validate(*a, results: , **kw)
49
+ results.select(&:valid?).each do |inplace_result|
50
+ result.evaluated_tokens.merge(inplace_result.evaluated_tokens)
66
51
  end
67
- nil
52
+ validate(*a, **kw,
53
+ results: results,
54
+ )
55
+ end
56
+
57
+ # @param child_results [Hash<Object, Validation::Result>] token => child result
58
+ def child_results_validate(*a, child_results: , **kw)
59
+ result.evaluated_tokens.merge(child_results.each_key.select { |t| child_results[t].valid? })
60
+ validate(*a, **kw,
61
+ results: child_results.each_value,
62
+ )
68
63
  end
69
64
  end
70
65
  end
71
66
 
72
67
  class Result
68
+ include(Util::Pretty)
69
+
73
70
  # is the instance valid against its schemas?
74
71
  # @return [Boolean]
75
72
  def valid?
76
73
  #chkbug raise(NotImplementedError)
77
74
  end
78
75
 
76
+ # @raise [JSI::Invalid]
77
+ # @return [nil]
78
+ def valid!
79
+ raise(JSI::Invalid, self) if !valid?
80
+ end
81
+
82
+ def pretty_print(q)
83
+ pretty_print_valid(q)
84
+ end
85
+
79
86
  include Util::FingerprintHash
87
+
88
+ private
89
+
90
+ def pretty_print_valid(q, &block)
91
+ jsi_pp_object_group(q, [self.class.name, valid? ? "(VALID)" : "(INVALID)"].freeze, &block)
92
+ end
80
93
  end
81
94
 
82
95
  # a full result of validating an instance against its schemas, with each validation error
83
- class FullResult < Result
96
+ class Result::Full < Result
84
97
  # @private
85
98
  class Builder < Result::Builder
86
99
  def validate(
87
100
  valid,
88
- message,
101
+ message_key,
102
+ message_default,
89
103
  keyword: nil,
90
- results: Util::EMPTY_ARY
104
+ results: Util::EMPTY_ARY,
105
+ **additional
91
106
  )
92
- results.each { |res| result.schema_issues.merge(res.schema_issues) }
93
107
  if !valid
94
- results.each { |res| result.validation_errors.merge(res.validation_errors) }
95
- result.validation_errors << Validation::Error.new({
96
- message: message,
108
+ result.nested_validation_errors << Validation::Error.new({
109
+ message: JSI.t(message_key, default: message_default, **additional),
97
110
  keyword: keyword,
111
+ additional: additional,
98
112
  schema: schema,
99
113
  instance_ptr: instance_ptr,
100
114
  instance_document: instance_document,
115
+ nested_errors: results.map(&:nested_validation_errors).inject(Set[], &:merge).freeze,
101
116
  })
102
117
  end
103
118
  end
104
-
105
- def schema_issue(level, message, keyword = nil)
106
- result.schema_issues << Schema::Issue.new({
107
- level: level,
108
- message: message,
109
- keyword: keyword,
110
- schema: schema,
111
- })
112
- end
113
119
  end
114
120
  end
115
121
 
116
- class FullResult
122
+ class Result::Full
117
123
  def initialize
118
- @validation_errors = Set.new
119
- @schema_issues = Set.new
124
+ @nested_validation_errors = Set.new
125
+ @evaluated_tokens = Set.new
120
126
  end
121
127
 
122
- attr_reader :validation_errors
123
- attr_reader :schema_issues
128
+ # @return [Set<Validation::Error>]
129
+ attr_reader(:nested_validation_errors)
130
+
131
+ # @yield [Validation::Error]
132
+ def each_validation_error(&block)
133
+ return(to_enum(__method__)) if !block_given?
134
+ nested_validation_errors.each do |validation_error|
135
+ validation_error.each_validation_error(&block)
136
+ end
137
+ nil
138
+ end
139
+
140
+ # @deprecated after v0.8
141
+ # iterating (recursively) is better done with #each_validation_error
142
+ def validation_errors
143
+ each_validation_error.to_set
144
+ end
145
+
146
+ # @return [Set]
147
+ attr_reader(:evaluated_tokens)
124
148
 
125
149
  def valid?
126
- validation_errors.empty?
150
+ nested_validation_errors.empty?
127
151
  end
128
152
 
129
153
  def freeze
130
- @schema_issues.each(&:freeze)
131
- @validation_errors.freeze
132
- @schema_issues.freeze
154
+ @nested_validation_errors.freeze
155
+ @evaluated_tokens.freeze
133
156
  super
134
157
  end
135
158
 
136
159
  def merge(result)
137
- unless result.is_a?(FullResult)
138
- raise(TypeError, "not a #{FullResult.name}: #{result.pretty_inspect.chomp}")
139
- end
140
- validation_errors.merge(result.validation_errors)
141
- schema_issues.merge(result.schema_issues)
160
+ raise(TypeError, "not a #{Result::Full}: #{result.pretty_inspect.chomp}") unless result.is_a?(Result::Full)
161
+ nested_validation_errors.merge(result.nested_validation_errors)
162
+ evaluated_tokens.merge(result.evaluated_tokens)
142
163
  self
143
164
  end
144
165
 
166
+ def pretty_print(q)
167
+ pretty_print_valid(q) do
168
+ q.text('validation errors: ')
169
+ q.pp(nested_validation_errors)
170
+ end
171
+ end
172
+
145
173
  # see {Util::Private::FingerprintHash}
146
174
  # @api private
147
175
  def jsi_fingerprint
148
176
  {
149
177
  class: self.class,
150
- validation_errors: validation_errors,
151
- schema_issues: schema_issues,
178
+ nested_validation_errors: nested_validation_errors,
179
+ evaluated_tokens: evaluated_tokens,
152
180
  }.freeze
153
181
  end
154
182
  end
155
183
 
156
- # a result indicating only whether an instance is valid against its schemas
157
- class ValidityResult < Result
184
+ # A result indicating validation success of an instance against a schema
185
+ class Result::Valid < Result
158
186
  # @private
159
187
  class Builder < Result::Builder
160
188
  def validate(
161
189
  valid,
162
- message,
190
+ message_key,
191
+ message_default,
163
192
  keyword: nil,
164
- results: Util::EMPTY_ARY
193
+ results: Util::EMPTY_ARY,
194
+ **additional
165
195
  )
166
196
  if !valid
167
197
  throw(:jsi_validation_result, INVALID)
168
198
  end
169
199
  end
170
-
171
- def schema_issue(*_)
172
- # noop
173
- end
174
200
  end
175
201
  end
176
202
 
177
- class ValidityResult
178
- def initialize(valid)
179
- @valid = valid
203
+ class Result::Valid
204
+ def initialize
205
+ @evaluated_tokens = Set.new
180
206
  end
181
207
 
208
+ # @return [Set]
209
+ attr_reader(:evaluated_tokens)
210
+
182
211
  def valid?
183
- @valid
212
+ true
213
+ end
214
+
215
+ def freeze
216
+ @evaluated_tokens.freeze
217
+ super
184
218
  end
185
219
 
186
220
  # see {Util::Private::FingerprintHash}
@@ -188,9 +222,22 @@ module JSI
188
222
  def jsi_fingerprint
189
223
  {
190
224
  class: self.class,
191
- valid: valid?,
225
+ evaluated_tokens: evaluated_tokens,
192
226
  }.freeze
193
227
  end
194
228
  end
229
+
230
+ # A result indicating validation failure of an instance against a schema
231
+ class Result::Invalid < Result
232
+ def valid?
233
+ false
234
+ end
235
+
236
+ # see {Util::Private::FingerprintHash}
237
+ # @api private
238
+ def jsi_fingerprint
239
+ self.class
240
+ end
241
+ end
195
242
  end
196
243
  end