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.
- checksums.yaml +4 -4
- data/.yardopts +3 -2
- data/CHANGELOG.md +8 -3
- data/LICENSE.md +2 -3
- data/README.md +68 -31
- data/docs/Glossary.md +313 -0
- data/jsi.gemspec +1 -0
- data/lib/jsi/base/mutability.rb +4 -0
- data/lib/jsi/base/node.rb +63 -24
- data/lib/jsi/base.rb +556 -173
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +106 -56
- data/lib/jsi/metaschema_node.rb +227 -160
- data/lib/jsi/ptr.rb +32 -15
- 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 +103 -50
- data/lib/jsi/schema/vocabulary.rb +36 -0
- data/lib/jsi/schema.rb +519 -337
- data/lib/jsi/schema_classes.rb +168 -124
- data/lib/jsi/schema_set.rb +67 -126
- data/lib/jsi/set.rb +23 -0
- data/lib/jsi/simple_wrap.rb +13 -16
- 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 +57 -12
- data/lib/jsi/util/typelike.rb +19 -64
- data/lib/jsi/util.rb +52 -34
- data/lib/jsi/validation/error.rb +41 -2
- data/lib/jsi/validation/result.rb +118 -71
- data/lib/jsi/validation.rb +1 -6
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +158 -41
- data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +67 -0
- data/lib/schemas/json-schema.org/draft-04/schema.rb +63 -106
- data/lib/schemas/json-schema.org/draft-06/schema.rb +56 -105
- data/lib/schemas/json-schema.org/draft-07/schema.rb +67 -124
- data/readme.rb +3 -3
- 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 +69 -47
- 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 -186
- 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 -93
- 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 -200
- data/lib/jsi/util/private/attr_struct.rb +0 -141
data/lib/jsi/util/typelike.rb
CHANGED
|
@@ -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
|
|
90
|
+
q.group {
|
|
91
|
+
q.nest(2) {
|
|
114
92
|
q.breakable ' ' if !empty?
|
|
115
|
-
q.seplist(self
|
|
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
|
|
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
|
|
172
|
+
q.group {
|
|
173
|
+
q.nest(2) {
|
|
220
174
|
q.breakable ' ' if !empty?
|
|
221
|
-
q.seplist(self
|
|
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
|
|
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?(
|
|
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
|
|
128
|
+
(options_state || !options.empty?) ? object.to_json(options) : object.to_json # TODO remove eventually (keyword argument compatibility)
|
|
90
129
|
else
|
|
91
|
-
|
|
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?(
|
|
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.
|
|
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
|
data/lib/jsi/validation/error.rb
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module JSI
|
|
4
4
|
module Validation
|
|
5
|
-
Error =
|
|
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 =
|
|
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
|
-
#
|
|
18
|
-
class Builder
|
|
16
|
+
# context to build a Validation::Result
|
|
17
|
+
class Builder < Schema::Cxt
|
|
19
18
|
def instance
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
96
|
+
class Result::Full < Result
|
|
84
97
|
# @private
|
|
85
98
|
class Builder < Result::Builder
|
|
86
99
|
def validate(
|
|
87
100
|
valid,
|
|
88
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
|
122
|
+
class Result::Full
|
|
117
123
|
def initialize
|
|
118
|
-
@
|
|
119
|
-
@
|
|
124
|
+
@nested_validation_errors = Set.new
|
|
125
|
+
@evaluated_tokens = Set.new
|
|
120
126
|
end
|
|
121
127
|
|
|
122
|
-
|
|
123
|
-
attr_reader
|
|
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
|
-
|
|
150
|
+
nested_validation_errors.empty?
|
|
127
151
|
end
|
|
128
152
|
|
|
129
153
|
def freeze
|
|
130
|
-
@
|
|
131
|
-
@
|
|
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?(
|
|
138
|
-
|
|
139
|
-
|
|
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
|
-
|
|
151
|
-
|
|
178
|
+
nested_validation_errors: nested_validation_errors,
|
|
179
|
+
evaluated_tokens: evaluated_tokens,
|
|
152
180
|
}.freeze
|
|
153
181
|
end
|
|
154
182
|
end
|
|
155
183
|
|
|
156
|
-
#
|
|
157
|
-
class
|
|
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
|
-
|
|
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
|
|
178
|
-
def initialize
|
|
179
|
-
@
|
|
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
|
-
|
|
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
|
-
|
|
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
|