jsi 0.7.0 → 0.8.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 +6 -1
- data/CHANGELOG.md +15 -0
- data/README.md +19 -18
- data/jsi.gemspec +2 -3
- data/lib/jsi/base/mutability.rb +44 -0
- data/lib/jsi/base/node.rb +199 -34
- data/lib/jsi/base.rb +412 -228
- data/lib/jsi/jsi_coder.rb +18 -16
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +57 -23
- data/lib/jsi/metaschema_node.rb +138 -107
- data/lib/jsi/ptr.rb +59 -37
- data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/child_application.rb +0 -25
- data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
- data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/someof.rb +1 -1
- data/lib/jsi/schema/application/inplace_application.rb +0 -27
- data/lib/jsi/schema/draft04.rb +0 -1
- data/lib/jsi/schema/draft06.rb +0 -1
- data/lib/jsi/schema/draft07.rb +0 -1
- data/lib/jsi/schema/ref.rb +44 -18
- data/lib/jsi/schema/schema_ancestor_node.rb +65 -56
- data/lib/jsi/schema/validation/contains.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +2 -0
- data/lib/jsi/schema/validation/draft04.rb +0 -2
- data/lib/jsi/schema/validation/draft06.rb +0 -2
- data/lib/jsi/schema/validation/draft07.rb +0 -2
- data/lib/jsi/schema/validation/items.rb +3 -3
- data/lib/jsi/schema/validation/pattern.rb +1 -1
- data/lib/jsi/schema/validation/properties.rb +4 -4
- data/lib/jsi/schema/validation/ref.rb +1 -1
- data/lib/jsi/schema/validation.rb +0 -2
- data/lib/jsi/schema.rb +405 -194
- data/lib/jsi/schema_classes.rb +196 -127
- data/lib/jsi/schema_registry.rb +66 -17
- data/lib/jsi/schema_set.rb +76 -30
- data/lib/jsi/simple_wrap.rb +2 -7
- data/lib/jsi/util/private/attr_struct.rb +28 -14
- data/lib/jsi/util/private/memo_map.rb +75 -0
- data/lib/jsi/util/private.rb +73 -92
- data/lib/jsi/util/typelike.rb +28 -28
- data/lib/jsi/util.rb +120 -36
- data/lib/jsi/validation/error.rb +4 -0
- data/lib/jsi/validation/result.rb +18 -32
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +67 -25
- data/lib/schemas/json-schema.org/draft-04/schema.rb +159 -4
- data/lib/schemas/json-schema.org/draft-06/schema.rb +161 -4
- data/lib/schemas/json-schema.org/draft-07/schema.rb +188 -4
- metadata +19 -5
- data/lib/jsi/metaschema.rb +0 -6
- data/lib/jsi/schema/validation/core.rb +0 -39
data/lib/jsi/util/typelike.rb
CHANGED
@@ -1,23 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# @deprecated after v0.6
|
5
|
-
module Typelike
|
6
|
-
extend Util
|
7
|
-
end
|
8
|
-
|
9
4
|
# a module of methods for objects which behave like Hash but are not Hash.
|
10
5
|
#
|
11
6
|
# this module is intended to be internal to JSI. no guarantees or API promises
|
12
7
|
# are made for non-JSI classes including this module.
|
13
8
|
module Util::Hashlike
|
9
|
+
include(Enumerable)
|
10
|
+
|
14
11
|
# safe methods which can be delegated to #to_hash (which the includer is assumed to have defined).
|
15
12
|
# 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
|
16
13
|
|
17
14
|
# methods which do not need to access the value.
|
18
|
-
SAFE_KEY_ONLY_METHODS = %w(each_key empty? has_key? include? key? keys length member? size)
|
19
|
-
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)
|
20
|
-
DESTRUCTIVE_METHODS = %w(clear delete delete_if keep_if reject! replace select! shift)
|
15
|
+
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
|
21
18
|
# these return a modified copy
|
22
19
|
safe_modified_copy_methods = %w(compact)
|
23
20
|
# select and reject will return a modified copy but need the yielded block variable value from #[]
|
@@ -73,9 +70,8 @@ module JSI
|
|
73
70
|
unless other.respond_to?(:to_hash)
|
74
71
|
raise(TypeError, "cannot update with argument that does not respond to #to_hash: #{other.pretty_inspect.chomp}")
|
75
72
|
end
|
76
|
-
self_respondingto_key = respond_to?(:key?) ? self : to_hash
|
77
73
|
other.to_hash.each_pair do |key, value|
|
78
|
-
if block &&
|
74
|
+
if block && key?(key)
|
79
75
|
value = yield(key, self[key], value)
|
80
76
|
end
|
81
77
|
self[key] = value
|
@@ -92,7 +88,9 @@ module JSI
|
|
92
88
|
# @return duplicate of this hash with the other hash merged in
|
93
89
|
# @raise [TypeError] when `other` does not respond to #to_hash
|
94
90
|
def merge(other, &block)
|
95
|
-
|
91
|
+
jsi_modified_copy do |instance|
|
92
|
+
instance.merge(other.is_a?(Base) ? other.jsi_node_content : other, &block)
|
93
|
+
end
|
96
94
|
end
|
97
95
|
|
98
96
|
# basically the same #inspect as Hash, but has the class name and, if responsive,
|
@@ -100,19 +98,20 @@ module JSI
|
|
100
98
|
# @return [String]
|
101
99
|
def inspect
|
102
100
|
object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
|
103
|
-
"\#{<#{object_group_str}>#{map { |k, v| " #{k.inspect} => #{v.inspect}" }.join(',')}}"
|
101
|
+
-"\#{<#{object_group_str}>#{map { |k, v| " #{k.inspect} => #{v.inspect}" }.join(',')}}"
|
104
102
|
end
|
105
103
|
|
106
|
-
|
104
|
+
def to_s
|
105
|
+
inspect
|
106
|
+
end
|
107
107
|
|
108
108
|
# pretty-prints a representation of this hashlike to the given printer
|
109
109
|
# @return [void]
|
110
110
|
def pretty_print(q)
|
111
111
|
object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
|
112
112
|
q.text "\#{<#{object_group_str}>"
|
113
|
-
q.
|
114
|
-
|
115
|
-
q.breakable(empty? ? '' : ' ')
|
113
|
+
q.group(2) {
|
114
|
+
q.breakable ' ' if !empty?
|
116
115
|
q.seplist(self, nil, :each_pair) { |k, v|
|
117
116
|
q.group {
|
118
117
|
q.pp k
|
@@ -120,9 +119,8 @@ module JSI
|
|
120
119
|
q.pp v
|
121
120
|
}
|
122
121
|
}
|
123
|
-
}
|
124
122
|
}
|
125
|
-
q.breakable ''
|
123
|
+
q.breakable '' if !empty?
|
126
124
|
q.text '}'
|
127
125
|
end
|
128
126
|
end
|
@@ -132,14 +130,16 @@ module JSI
|
|
132
130
|
# this module is intended to be internal to JSI. no guarantees or API promises
|
133
131
|
# are made for non-JSI classes including this module.
|
134
132
|
module Util::Arraylike
|
133
|
+
include(Enumerable)
|
134
|
+
|
135
135
|
# safe methods which can be delegated to #to_ary (which the includer is assumed to have defined).
|
136
136
|
# 'safe' means, in this context, nondestructive - methods which do not modify the receiver.
|
137
137
|
|
138
138
|
# methods which do not need to access the element.
|
139
|
-
SAFE_INDEX_ONLY_METHODS = %w(each_index empty? length size)
|
139
|
+
SAFE_INDEX_ONLY_METHODS = %w(each_index empty? length size).map(&:freeze).freeze
|
140
140
|
# 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)
|
142
|
-
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)
|
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
|
142
|
+
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
143
|
|
144
144
|
# methods (well, method) that returns a modified copy and doesn't need any handling of block variable(s)
|
145
145
|
safe_modified_copy_methods = %w(compact)
|
@@ -204,25 +204,25 @@ module JSI
|
|
204
204
|
# @return [String]
|
205
205
|
def inspect
|
206
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(',')}]"
|
207
|
+
-"\#[<#{object_group_str}>#{map { |e| ' ' + e.inspect }.join(',')}]"
|
208
208
|
end
|
209
209
|
|
210
|
-
|
210
|
+
def to_s
|
211
|
+
inspect
|
212
|
+
end
|
211
213
|
|
212
214
|
# pretty-prints a representation of this arraylike to the given printer
|
213
215
|
# @return [void]
|
214
216
|
def pretty_print(q)
|
215
217
|
object_group_str = (respond_to?(:jsi_object_group_text, true) ? jsi_object_group_text : [self.class]).join(' ')
|
216
218
|
q.text "\#[<#{object_group_str}>"
|
217
|
-
q.
|
218
|
-
|
219
|
-
q.breakable(empty? ? '' : ' ')
|
219
|
+
q.group(2) {
|
220
|
+
q.breakable ' ' if !empty?
|
220
221
|
q.seplist(self, nil, :each) { |e|
|
221
222
|
q.pp e
|
222
223
|
}
|
223
|
-
}
|
224
224
|
}
|
225
|
-
q.breakable ''
|
225
|
+
q.breakable '' if !empty?
|
226
226
|
q.text ']'
|
227
227
|
end
|
228
228
|
end
|
data/lib/jsi/util.rb
CHANGED
@@ -7,11 +7,13 @@ module JSI
|
|
7
7
|
|
8
8
|
include Private
|
9
9
|
|
10
|
+
extend self
|
11
|
+
|
10
12
|
autoload :Arraylike, 'jsi/util/typelike'
|
11
13
|
autoload :Hashlike, 'jsi/util/typelike'
|
12
14
|
|
13
15
|
# yields the content of the given param `object`. for objects which have a #jsi_modified_copy
|
14
|
-
# method of their own (JSI::Base, JSI::
|
16
|
+
# method of their own (JSI::Base, JSI::MetaSchemaNode) that method is invoked with the given
|
15
17
|
# block. otherwise the given object itself is yielded.
|
16
18
|
#
|
17
19
|
# the given block must result in a modified copy of its block parameter
|
@@ -24,47 +26,69 @@ module JSI
|
|
24
26
|
if object.respond_to?(:jsi_modified_copy)
|
25
27
|
object.jsi_modified_copy(&block)
|
26
28
|
else
|
27
|
-
|
29
|
+
yield(object)
|
28
30
|
end
|
29
31
|
end
|
30
32
|
|
31
|
-
#
|
32
|
-
# types of Hash, Array, and basic types of String/boolean/numeric/nil. this
|
33
|
-
# will raise TypeError if an object is given that is not a type that seems
|
34
|
-
# to be expressable as json.
|
33
|
+
# A structure like the given `object`, recursively coerced to JSON-compatible types.
|
35
34
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
35
|
+
# - Structures of Hash, Array, and basic types of String/number/boolean/nil are returned as-is.
|
36
|
+
# - If the object responds to `#as_json`, that method is used, passing any given options.
|
37
|
+
# - If the object supports [implicit conversion](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html)
|
38
|
+
# with `#to_hash`, `#to_ary`, `#to_str`, or `#to_int`, that is used.
|
39
|
+
# - Set becomes Array; Symbol becomes String.
|
40
|
+
# - Types with no known coersion to JSON-compatible raise TypeError.
|
40
41
|
#
|
41
|
-
# @param object [Object]
|
42
|
-
# @return [Array, Hash, String, Boolean, NilClass
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
42
|
+
# @param object [Object]
|
43
|
+
# @return [Array, Hash, String, Integer, Float, Boolean, NilClass] a JSON-compatible structure like the given `object`
|
44
|
+
# @raise [TypeError] If the object cannot be coerced to a JSON-compatible structure
|
45
|
+
def as_json(object, options = {})
|
46
|
+
type_err = proc { raise(TypeError, "cannot express object as json: #{object.pretty_inspect.chomp}") }
|
47
|
+
if object.respond_to?(:as_json)
|
48
|
+
options.empty? ? object.as_json : object.as_json(**options) # TODO remove eventually (keyword argument compatibility)
|
49
|
+
elsif object.is_a?(Addressable::URI)
|
50
|
+
object.to_s
|
51
|
+
elsif object.respond_to?(:to_hash) && (object_to_hash = object.to_hash).is_a?(Hash)
|
52
|
+
result = {}
|
53
|
+
object_to_hash.each_pair do |k, v|
|
54
|
+
ks = k.is_a?(String) ? k :
|
55
|
+
k.is_a?(Symbol) ? k.to_s :
|
56
|
+
k.respond_to?(:to_str) && (kstr = k.to_str).is_a?(String) ? kstr :
|
52
57
|
raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
elsif object.respond_to?(:to_ary)
|
57
|
-
|
58
|
-
elsif [String, TrueClass, FalseClass, NilClass
|
58
|
+
result[ks] = as_json(v, **options)
|
59
|
+
end
|
60
|
+
result
|
61
|
+
elsif object.respond_to?(:to_ary) && (object_to_ary = object.to_ary).is_a?(Array)
|
62
|
+
object_to_ary.map { |e| as_json(e, **options) }
|
63
|
+
elsif [String, Integer, TrueClass, FalseClass, NilClass].any? { |c| object.is_a?(c) }
|
64
|
+
object
|
65
|
+
elsif object.is_a?(Float)
|
66
|
+
type_err.call unless object.finite?
|
59
67
|
object
|
60
68
|
elsif object.is_a?(Symbol)
|
61
69
|
object.to_s
|
62
70
|
elsif object.is_a?(Set)
|
63
|
-
as_json(object.to_a,
|
64
|
-
elsif object.respond_to?(:
|
65
|
-
|
71
|
+
as_json(object.to_a, **options)
|
72
|
+
elsif object.respond_to?(:to_str) && (object_to_str = object.to_str).is_a?(String)
|
73
|
+
object_to_str
|
74
|
+
elsif object.respond_to?(:to_int) && (object_to_int = object.to_int).is_a?(Integer)
|
75
|
+
object_to_int
|
66
76
|
else
|
67
|
-
|
77
|
+
type_err.call
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# A JSON encoded string of the given object.
|
82
|
+
#
|
83
|
+
# - If the object has a `#to_json` method that isn't defined by the stdlib `json` gem,
|
84
|
+
# that method is used, passing any given options.
|
85
|
+
# - Otherwise, JSON is generated using {as_json} to coerce to compatible types.
|
86
|
+
# @return [String]
|
87
|
+
def to_json(object, options = {})
|
88
|
+
if USE_TO_JSON_METHOD[object.class]
|
89
|
+
options.empty? ? object.to_json : object.to_json(**options) # TODO remove eventually (keyword argument compatibility)
|
90
|
+
else
|
91
|
+
JSON.generate(as_json(object, **options))
|
68
92
|
end
|
69
93
|
end
|
70
94
|
|
@@ -96,11 +120,11 @@ module JSI
|
|
96
120
|
end
|
97
121
|
|
98
122
|
def deep_stringify_symbol_keys(object)
|
99
|
-
if object.respond_to?(:to_hash)
|
123
|
+
if object.respond_to?(:to_hash) && !object.is_a?(Addressable::URI)
|
100
124
|
JSI::Util.modified_copy(object) do |hash|
|
101
125
|
out = {}
|
102
126
|
(hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v|
|
103
|
-
out[k.is_a?(Symbol) ? k.to_s : deep_stringify_symbol_keys(k)] = deep_stringify_symbol_keys(v)
|
127
|
+
out[k.is_a?(Symbol) ? k.to_s.freeze : deep_stringify_symbol_keys(k)] = deep_stringify_symbol_keys(v)
|
104
128
|
end
|
105
129
|
out
|
106
130
|
end
|
@@ -115,9 +139,69 @@ module JSI
|
|
115
139
|
end
|
116
140
|
end
|
117
141
|
|
142
|
+
# returns an object which is equal to the param object, and is recursively frozen.
|
143
|
+
# the given object is not modified.
|
144
|
+
def deep_to_frozen(object, not_implemented: nil)
|
145
|
+
dtf = proc { |o| deep_to_frozen(o, not_implemented: not_implemented) }
|
146
|
+
if object.instance_of?(Hash)
|
147
|
+
out = {}
|
148
|
+
identical = object.frozen?
|
149
|
+
object.each do |k, v|
|
150
|
+
fk = dtf[k]
|
151
|
+
fv = dtf[v]
|
152
|
+
identical &&= fk.__id__ == k.__id__
|
153
|
+
identical &&= fv.__id__ == v.__id__
|
154
|
+
out[fk] = fv
|
155
|
+
end
|
156
|
+
if !object.default.nil?
|
157
|
+
out.default = dtf[object.default]
|
158
|
+
identical &&= out.default.__id__ == object.default.__id__
|
159
|
+
end
|
160
|
+
if object.default_proc
|
161
|
+
raise(ArgumentError, "cannot make immutable copy of a Hash with default_proc")
|
162
|
+
end
|
163
|
+
if identical
|
164
|
+
object
|
165
|
+
else
|
166
|
+
out.freeze
|
167
|
+
end
|
168
|
+
elsif object.instance_of?(Array)
|
169
|
+
identical = object.frozen?
|
170
|
+
out = Array.new(object.size)
|
171
|
+
object.each_with_index do |e, i|
|
172
|
+
fe = dtf[e]
|
173
|
+
identical &&= fe.__id__ == e.__id__
|
174
|
+
out[i] = fe
|
175
|
+
end
|
176
|
+
if identical
|
177
|
+
object
|
178
|
+
else
|
179
|
+
out.freeze
|
180
|
+
end
|
181
|
+
elsif object.instance_of?(String)
|
182
|
+
if object.frozen?
|
183
|
+
object
|
184
|
+
else
|
185
|
+
object.dup.freeze
|
186
|
+
end
|
187
|
+
elsif CLASSES_ALWAYS_FROZEN.any? { |c| object.is_a?(c) } # note: `is_a?`, not `instance_of?`, here because instance_of?(Integer) is false until Fixnum/Bignum is gone. this is fine here; there is no concern of subclasses of CLASSES_ALWAYS_FROZEN duping/freezing differently (as with e.g. ActiveSupport::HashWithIndifferentAccess)
|
188
|
+
object
|
189
|
+
else
|
190
|
+
if not_implemented
|
191
|
+
not_implemented.call(object)
|
192
|
+
else
|
193
|
+
raise(NotImplementedError, [
|
194
|
+
"deep_to_frozen not implemented for class: #{object.class}",
|
195
|
+
"object: #{object.pretty_inspect.chomp}",
|
196
|
+
].join("\n"))
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
118
201
|
# ensures the given param becomes a frozen Set of Modules.
|
119
202
|
# returns the param if it is already that, otherwise initializes and freezes such a Set.
|
120
203
|
#
|
204
|
+
# @api private
|
121
205
|
# @param modules [Set, Enumerable] the object to ensure becomes a frozen Set of Modules
|
122
206
|
# @return [Set] frozen Set containing the given modules
|
123
207
|
# @raise [ArgumentError] when the modules param is not an Enumerable
|
@@ -125,8 +209,10 @@ module JSI
|
|
125
209
|
def ensure_module_set(modules)
|
126
210
|
if modules.is_a?(Set) && modules.frozen?
|
127
211
|
set = modules
|
128
|
-
|
212
|
+
elsif modules.is_a?(Enumerable)
|
129
213
|
set = Set.new(modules).freeze
|
214
|
+
else
|
215
|
+
raise(TypeError, "not given an Enumerable of Modules")
|
130
216
|
end
|
131
217
|
not_modules = set.reject { |s| s.is_a?(Module) }
|
132
218
|
if !not_modules.empty?
|
@@ -138,7 +224,5 @@ module JSI
|
|
138
224
|
|
139
225
|
set
|
140
226
|
end
|
141
|
-
|
142
|
-
extend self
|
143
227
|
end
|
144
228
|
end
|
data/lib/jsi/validation/error.rb
CHANGED
@@ -3,10 +3,7 @@
|
|
3
3
|
module JSI
|
4
4
|
module Validation
|
5
5
|
# a result of validating an instance against schemas which describe it.
|
6
|
-
# virtual base class.
|
7
6
|
class Result
|
8
|
-
include Util::Virtual
|
9
|
-
|
10
7
|
Builder = Util::AttrStruct[*%w(
|
11
8
|
result
|
12
9
|
schema
|
@@ -24,7 +21,6 @@ module JSI
|
|
24
21
|
end
|
25
22
|
|
26
23
|
def schema_issue(*_)
|
27
|
-
virtual_method
|
28
24
|
end
|
29
25
|
|
30
26
|
def schema_error(message, keyword = nil)
|
@@ -48,12 +44,12 @@ module JSI
|
|
48
44
|
subresult
|
49
45
|
end
|
50
46
|
|
47
|
+
# @param instance_child_token [String, Integer]
|
51
48
|
# @param subschema_ptr [JSI::Ptr, #to_ary]
|
52
|
-
# @param subinstance_ptr [JSI::Ptr, #to_ary]
|
53
49
|
# @return [JSI::Validation::Result]
|
54
|
-
def child_subschema_validate(
|
50
|
+
def child_subschema_validate(instance_child_token, subschema_ptr)
|
55
51
|
subresult = schema.subschema(subschema_ptr).internal_validate_instance(
|
56
|
-
instance_ptr
|
52
|
+
instance_ptr[instance_child_token],
|
57
53
|
instance_document,
|
58
54
|
validate_only: validate_only,
|
59
55
|
)
|
@@ -71,24 +67,13 @@ module JSI
|
|
71
67
|
nil
|
72
68
|
end
|
73
69
|
end
|
70
|
+
end
|
74
71
|
|
75
|
-
|
76
|
-
self.class::Builder.new(
|
77
|
-
result: self,
|
78
|
-
schema: schema,
|
79
|
-
instance_ptr: instance_ptr,
|
80
|
-
instance_document: instance_document,
|
81
|
-
validate_only: validate_only,
|
82
|
-
visited_refs: visited_refs,
|
83
|
-
)
|
84
|
-
end
|
85
|
-
|
72
|
+
class Result
|
86
73
|
# is the instance valid against its schemas?
|
87
74
|
# @return [Boolean]
|
88
75
|
def valid?
|
89
|
-
#
|
90
|
-
virtual_method
|
91
|
-
# :nocov:
|
76
|
+
#chkbug raise(NotImplementedError)
|
92
77
|
end
|
93
78
|
|
94
79
|
include Util::FingerprintHash
|
@@ -102,7 +87,7 @@ module JSI
|
|
102
87
|
valid,
|
103
88
|
message,
|
104
89
|
keyword: nil,
|
105
|
-
results:
|
90
|
+
results: Util::EMPTY_ARY
|
106
91
|
)
|
107
92
|
results.each { |res| result.schema_issues.merge(res.schema_issues) }
|
108
93
|
if !valid
|
@@ -126,7 +111,9 @@ module JSI
|
|
126
111
|
})
|
127
112
|
end
|
128
113
|
end
|
114
|
+
end
|
129
115
|
|
116
|
+
class FullResult
|
130
117
|
def initialize
|
131
118
|
@validation_errors = Set.new
|
132
119
|
@schema_issues = Set.new
|
@@ -140,7 +127,6 @@ module JSI
|
|
140
127
|
end
|
141
128
|
|
142
129
|
def freeze
|
143
|
-
@validation_errors.each(&:freeze)
|
144
130
|
@schema_issues.each(&:freeze)
|
145
131
|
@validation_errors.freeze
|
146
132
|
@schema_issues.freeze
|
@@ -156,17 +142,14 @@ module JSI
|
|
156
142
|
self
|
157
143
|
end
|
158
144
|
|
159
|
-
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
# @private
|
145
|
+
# see {Util::Private::FingerprintHash}
|
146
|
+
# @api private
|
164
147
|
def jsi_fingerprint
|
165
148
|
{
|
166
149
|
class: self.class,
|
167
150
|
validation_errors: validation_errors,
|
168
151
|
schema_issues: schema_issues,
|
169
|
-
}
|
152
|
+
}.freeze
|
170
153
|
end
|
171
154
|
end
|
172
155
|
|
@@ -178,7 +161,7 @@ module JSI
|
|
178
161
|
valid,
|
179
162
|
message,
|
180
163
|
keyword: nil,
|
181
|
-
results:
|
164
|
+
results: Util::EMPTY_ARY
|
182
165
|
)
|
183
166
|
if !valid
|
184
167
|
throw(:jsi_validation_result, INVALID)
|
@@ -189,7 +172,9 @@ module JSI
|
|
189
172
|
# noop
|
190
173
|
end
|
191
174
|
end
|
175
|
+
end
|
192
176
|
|
177
|
+
class ValidityResult
|
193
178
|
def initialize(valid)
|
194
179
|
@valid = valid
|
195
180
|
end
|
@@ -198,12 +183,13 @@ module JSI
|
|
198
183
|
@valid
|
199
184
|
end
|
200
185
|
|
201
|
-
#
|
186
|
+
# see {Util::Private::FingerprintHash}
|
187
|
+
# @api private
|
202
188
|
def jsi_fingerprint
|
203
189
|
{
|
204
190
|
class: self.class,
|
205
191
|
valid: valid?,
|
206
|
-
}
|
192
|
+
}.freeze
|
207
193
|
end
|
208
194
|
end
|
209
195
|
end
|
data/lib/jsi/version.rb
CHANGED
data/lib/jsi.rb
CHANGED
@@ -8,8 +8,6 @@ require "pathname"
|
|
8
8
|
require "bigdecimal"
|
9
9
|
require "addressable/uri"
|
10
10
|
|
11
|
-
require "jsi/util"
|
12
|
-
|
13
11
|
module JSI
|
14
12
|
# generally put in code paths that are not expected to be valid control flow paths.
|
15
13
|
# rather a NotImplementedCorrectlyError. but that's too long.
|
@@ -19,6 +17,9 @@ module JSI
|
|
19
17
|
class Bug < NotImplementedError
|
20
18
|
end
|
21
19
|
|
20
|
+
# @private TODO remove, any ruby without this is already long EOL
|
21
|
+
FrozenError = Object.const_defined?(:FrozenError) ? ::FrozenError : Class.new(StandardError)
|
22
|
+
|
22
23
|
# @private
|
23
24
|
ROOT_PATH = Pathname.new(__FILE__).dirname.parent.expand_path
|
24
25
|
|
@@ -28,53 +29,94 @@ module JSI
|
|
28
29
|
# @private
|
29
30
|
SCHEMAS_PATH = RESOURCES_PATH.join('schemas')
|
30
31
|
|
32
|
+
DEFAULT_CONTENT_TO_IMMUTABLE = proc do |content|
|
33
|
+
Util.deep_to_frozen(content, not_implemented: proc do |instance|
|
34
|
+
raise(ArgumentError, [
|
35
|
+
"JSI does not know how to make the given instance immutable.",
|
36
|
+
"See new_jsi / new_schema params `mutable` and `to_immutable` documentation for options.",
|
37
|
+
"https://www.rubydoc.info/gems/jsi/#{VERSION}/JSI/SchemaSet#new_jsi-instance_method",
|
38
|
+
"Given instance: #{instance.pretty_inspect.chomp}",
|
39
|
+
].join("\n"))
|
40
|
+
end)
|
41
|
+
end
|
42
|
+
|
43
|
+
autoload :Util, 'jsi/util'
|
31
44
|
autoload :Ptr, 'jsi/ptr'
|
32
|
-
autoload :Typelike, 'jsi/util/typelike'
|
33
45
|
autoload :Schema, 'jsi/schema'
|
34
46
|
autoload :SchemaSet, 'jsi/schema_set'
|
35
47
|
autoload :Base, 'jsi/base'
|
36
|
-
autoload
|
37
|
-
autoload :
|
48
|
+
autoload(:MetaSchemaNode, 'jsi/metaschema_node')
|
49
|
+
autoload :SchemaModule, 'jsi/schema_classes'
|
38
50
|
autoload :SchemaClasses, 'jsi/schema_classes'
|
39
51
|
autoload :SchemaRegistry, 'jsi/schema_registry'
|
40
52
|
autoload :Validation, 'jsi/validation'
|
41
53
|
autoload :JSICoder, 'jsi/jsi_coder'
|
42
54
|
|
55
|
+
autoload :JSONSchemaDraft04, 'schemas/json-schema.org/draft-04/schema'
|
56
|
+
autoload :JSONSchemaDraft06, 'schemas/json-schema.org/draft-06/schema'
|
57
|
+
autoload :JSONSchemaDraft07, 'schemas/json-schema.org/draft-07/schema'
|
43
58
|
autoload :JSONSchemaOrgDraft04, 'schemas/json-schema.org/draft-04/schema'
|
44
59
|
autoload :JSONSchemaOrgDraft06, 'schemas/json-schema.org/draft-06/schema'
|
45
60
|
autoload :JSONSchemaOrgDraft07, 'schemas/json-schema.org/draft-07/schema'
|
46
61
|
|
47
62
|
autoload :SimpleWrap, 'jsi/simple_wrap'
|
48
63
|
|
49
|
-
#
|
64
|
+
# Instantiates the given schema content as a JSI Schema, passing all params to
|
65
|
+
# {JSI.new_schema}, and returns its {Schema#jsi_schema_module JSI Schema Module}.
|
50
66
|
#
|
51
|
-
# see
|
52
|
-
|
53
|
-
|
54
|
-
# @return (see JSI::Schema.new_schema)
|
55
|
-
def self.new_schema(schema_object, **kw)
|
56
|
-
JSI::Schema.new_schema(schema_object, **kw)
|
67
|
+
# @return (see JSI::Schema::MetaSchema#new_schema_module)
|
68
|
+
def self.new_schema_module(schema_content, **kw, &block)
|
69
|
+
new_schema(schema_content, **kw, &block).jsi_schema_module
|
57
70
|
end
|
58
71
|
|
59
|
-
#
|
72
|
+
# @private pending dialect/vocabularies
|
73
|
+
# Instantiates the given document as a JSI Meta-Schema.
|
60
74
|
#
|
61
|
-
#
|
75
|
+
# @param metaschema_document an object to be instantiated as a JSI Meta-Schema
|
76
|
+
# @param schema_implementation_modules (see MetaSchemaNode#initialize)
|
77
|
+
# @param to_immutable (see SchemaSet#new_jsi)
|
78
|
+
# @return [JSI::MetaSchemaNode + JSI::Schema::MetaSchema + JSI::Schema]
|
79
|
+
def self.new_metaschema(metaschema_document,
|
80
|
+
schema_implementation_modules: ,
|
81
|
+
to_immutable: DEFAULT_CONTENT_TO_IMMUTABLE
|
82
|
+
)
|
83
|
+
metaschema_document = to_immutable.call(metaschema_document) if to_immutable
|
84
|
+
|
85
|
+
MetaSchemaNode.new(metaschema_document,
|
86
|
+
schema_implementation_modules: schema_implementation_modules,
|
87
|
+
jsi_content_to_immutable: to_immutable,
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
# @private pending dialect/vocabularies
|
92
|
+
# Instantiates the given document as a JSI Meta-Schema, passing all params to
|
93
|
+
# {new_metaschema}, and returns its {Schema#jsi_schema_module JSI Schema Module}.
|
62
94
|
#
|
63
|
-
# @
|
64
|
-
|
65
|
-
|
66
|
-
JSI::Schema.new_schema(schema_object, **kw).jsi_schema_module
|
95
|
+
# @return [JSI::SchemaModule + JSI::SchemaModule::MetaSchemaModule]
|
96
|
+
def self.new_metaschema_module(metaschema_document, **kw)
|
97
|
+
new_metaschema(metaschema_document, **kw).jsi_schema_module
|
67
98
|
end
|
68
99
|
|
69
|
-
# `JSI.schema_registry` is the {JSI::SchemaRegistry} in which schemas are registered
|
100
|
+
# `JSI.schema_registry` is the default {JSI::SchemaRegistry} in which schemas are registered and from
|
101
|
+
# which they resolve references.
|
70
102
|
#
|
71
103
|
# @return [JSI::SchemaRegistry]
|
72
104
|
def self.schema_registry
|
73
|
-
|
74
|
-
|
105
|
+
@schema_registry
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param schema_registry [JSI::SchemaRegistry]
|
109
|
+
def self.schema_registry=(schema_registry)
|
110
|
+
@schema_registry = schema_registry
|
75
111
|
end
|
76
|
-
end
|
77
112
|
|
78
|
-
|
79
|
-
|
80
|
-
|
113
|
+
DEFAULT_SCHEMA_REGISTRY = SchemaRegistry.new.tap do |schema_registry|
|
114
|
+
schema_registry.autoload_uri("http://json-schema.org/draft-04/schema") { JSI::JSONSchemaDraft04.schema }
|
115
|
+
schema_registry.autoload_uri("http://json-schema.org/draft-06/schema") { JSI::JSONSchemaDraft06.schema }
|
116
|
+
schema_registry.autoload_uri("http://json-schema.org/draft-07/schema") { JSI::JSONSchemaDraft07.schema }
|
117
|
+
end.freeze
|
118
|
+
|
119
|
+
self.schema_registry = DEFAULT_SCHEMA_REGISTRY.dup
|
120
|
+
|
121
|
+
Schema # trigger autoload, ensure JSI methods (new_schema etc) defined in schema.rb load
|
122
|
+
end
|