jsi 0.6.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 +33 -0
- data/LICENSE.md +1 -1
- data/README.md +29 -23
- data/jsi.gemspec +29 -0
- data/lib/jsi/base/mutability.rb +44 -0
- data/lib/jsi/base/node.rb +348 -0
- data/lib/jsi/base.rb +497 -339
- data/lib/jsi/jsi_coder.rb +19 -17
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +61 -26
- data/lib/jsi/metaschema_node.rb +161 -133
- data/lib/jsi/ptr.rb +80 -47
- data/lib/jsi/schema/application/child_application/contains.rb +11 -2
- 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/items.rb +3 -3
- data/lib/jsi/schema/application/child_application/properties.rb +3 -3
- data/lib/jsi/schema/application/child_application.rb +0 -27
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
- 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/ifthenelse.rb +3 -3
- data/lib/jsi/schema/application/inplace_application/ref.rb +2 -2
- data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
- data/lib/jsi/schema/application/inplace_application.rb +0 -32
- 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 +46 -19
- data/lib/jsi/schema/schema_ancestor_node.rb +69 -66
- data/lib/jsi/schema/validation/array.rb +3 -3
- data/lib/jsi/schema/validation/const.rb +1 -1
- data/lib/jsi/schema/validation/contains.rb +2 -2
- data/lib/jsi/schema/validation/dependencies.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +8 -6
- 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/enum.rb +1 -1
- data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
- data/lib/jsi/schema/validation/items.rb +7 -7
- data/lib/jsi/schema/validation/not.rb +1 -1
- data/lib/jsi/schema/validation/numeric.rb +5 -5
- data/lib/jsi/schema/validation/object.rb +2 -2
- data/lib/jsi/schema/validation/pattern.rb +2 -2
- data/lib/jsi/schema/validation/properties.rb +7 -7
- data/lib/jsi/schema/validation/property_names.rb +1 -1
- data/lib/jsi/schema/validation/ref.rb +2 -2
- data/lib/jsi/schema/validation/required.rb +1 -1
- data/lib/jsi/schema/validation/someof.rb +3 -3
- data/lib/jsi/schema/validation/string.rb +2 -2
- data/lib/jsi/schema/validation/type.rb +1 -1
- data/lib/jsi/schema/validation.rb +1 -3
- data/lib/jsi/schema.rb +443 -226
- data/lib/jsi/schema_classes.rb +241 -147
- data/lib/jsi/schema_registry.rb +78 -19
- data/lib/jsi/schema_set.rb +114 -28
- data/lib/jsi/simple_wrap.rb +18 -4
- data/lib/jsi/util/private/attr_struct.rb +141 -0
- data/lib/jsi/util/private/memo_map.rb +75 -0
- data/lib/jsi/util/private.rb +185 -0
- data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +79 -105
- data/lib/jsi/util.rb +157 -153
- 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 +65 -39
- data/lib/schemas/json-schema.org/draft-04/schema.rb +160 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +162 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +189 -3
- metadata +27 -11
- data/lib/jsi/metaschema.rb +0 -7
- data/lib/jsi/pathed_node.rb +0 -116
- data/lib/jsi/schema/validation/core.rb +0 -39
- data/lib/jsi/util/attr_struct.rb +0 -106
data/lib/jsi/util.rb
CHANGED
@@ -1,11 +1,96 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# JSI::Util
|
5
|
-
#
|
6
|
-
# @api private
|
4
|
+
# JSI::Util contains public utilities
|
7
5
|
module Util
|
8
|
-
autoload :
|
6
|
+
autoload :Private, 'jsi/util/private'
|
7
|
+
|
8
|
+
include Private
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
autoload :Arraylike, 'jsi/util/typelike'
|
13
|
+
autoload :Hashlike, 'jsi/util/typelike'
|
14
|
+
|
15
|
+
# yields the content of the given param `object`. for objects which have a #jsi_modified_copy
|
16
|
+
# method of their own (JSI::Base, JSI::MetaSchemaNode) that method is invoked with the given
|
17
|
+
# block. otherwise the given object itself is yielded.
|
18
|
+
#
|
19
|
+
# the given block must result in a modified copy of its block parameter
|
20
|
+
# (not destructively modifying the yielded content).
|
21
|
+
#
|
22
|
+
# @yield [Object] the content of the given object. the block should result
|
23
|
+
# in a (nondestructively) modified copy of this.
|
24
|
+
# @return [object.class] modified copy of the given object
|
25
|
+
def modified_copy(object, &block)
|
26
|
+
if object.respond_to?(:jsi_modified_copy)
|
27
|
+
object.jsi_modified_copy(&block)
|
28
|
+
else
|
29
|
+
yield(object)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# A structure like the given `object`, recursively coerced to JSON-compatible types.
|
34
|
+
#
|
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.
|
41
|
+
#
|
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 :
|
57
|
+
raise(TypeError, "json object (hash) cannot be keyed with: #{k.pretty_inspect.chomp}")
|
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?
|
67
|
+
object
|
68
|
+
elsif object.is_a?(Symbol)
|
69
|
+
object.to_s
|
70
|
+
elsif object.is_a?(Set)
|
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
|
76
|
+
else
|
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))
|
92
|
+
end
|
93
|
+
end
|
9
94
|
|
10
95
|
# a hash copied from the given hashlike, in which any symbol keys are
|
11
96
|
# converted to strings. behavior on collisions is undefined (but in the
|
@@ -25,7 +110,7 @@ module JSI
|
|
25
110
|
unless hashlike.respond_to?(:to_hash)
|
26
111
|
raise(ArgumentError, "expected argument to be a hash; got #{hashlike.class.inspect}: #{hashlike.pretty_inspect.chomp}")
|
27
112
|
end
|
28
|
-
JSI::
|
113
|
+
JSI::Util.modified_copy(hashlike) do |hash|
|
29
114
|
out = {}
|
30
115
|
hash.each do |k, v|
|
31
116
|
out[k.is_a?(Symbol) ? k.to_s : k] = v
|
@@ -35,16 +120,16 @@ module JSI
|
|
35
120
|
end
|
36
121
|
|
37
122
|
def deep_stringify_symbol_keys(object)
|
38
|
-
if object.respond_to?(:to_hash)
|
39
|
-
JSI::
|
123
|
+
if object.respond_to?(:to_hash) && !object.is_a?(Addressable::URI)
|
124
|
+
JSI::Util.modified_copy(object) do |hash|
|
40
125
|
out = {}
|
41
126
|
(hash.respond_to?(:each) ? hash : hash.to_hash).each do |k, v|
|
42
|
-
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)
|
43
128
|
end
|
44
129
|
out
|
45
130
|
end
|
46
131
|
elsif object.respond_to?(:to_ary)
|
47
|
-
JSI::
|
132
|
+
JSI::Util.modified_copy(object) do |ary|
|
48
133
|
(ary.respond_to?(:each) ? ary : ary.to_ary).map do |e|
|
49
134
|
deep_stringify_symbol_keys(e)
|
50
135
|
end
|
@@ -54,9 +139,69 @@ module JSI
|
|
54
139
|
end
|
55
140
|
end
|
56
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
|
+
|
57
201
|
# ensures the given param becomes a frozen Set of Modules.
|
58
202
|
# returns the param if it is already that, otherwise initializes and freezes such a Set.
|
59
203
|
#
|
204
|
+
# @api private
|
60
205
|
# @param modules [Set, Enumerable] the object to ensure becomes a frozen Set of Modules
|
61
206
|
# @return [Set] frozen Set containing the given modules
|
62
207
|
# @raise [ArgumentError] when the modules param is not an Enumerable
|
@@ -64,8 +209,10 @@ module JSI
|
|
64
209
|
def ensure_module_set(modules)
|
65
210
|
if modules.is_a?(Set) && modules.frozen?
|
66
211
|
set = modules
|
67
|
-
|
212
|
+
elsif modules.is_a?(Enumerable)
|
68
213
|
set = Set.new(modules).freeze
|
214
|
+
else
|
215
|
+
raise(TypeError, "not given an Enumerable of Modules")
|
69
216
|
end
|
70
217
|
not_modules = set.reject { |s| s.is_a?(Module) }
|
71
218
|
if !not_modules.empty?
|
@@ -77,148 +224,5 @@ module JSI
|
|
77
224
|
|
78
225
|
set
|
79
226
|
end
|
80
|
-
|
81
|
-
# is the given name ok to use as a ruby method name?
|
82
|
-
def ok_ruby_method_name?(name)
|
83
|
-
# must be a string
|
84
|
-
return false unless name.respond_to?(:to_str)
|
85
|
-
# must not begin with a digit
|
86
|
-
return false if name =~ /\A[0-9]/
|
87
|
-
# must not contain characters special to ruby syntax
|
88
|
-
return false if name =~ /[\\\s\#;\.,\(\)\[\]\{\}'"`%\+\-\/\*\^\|&=<>\?:!@\$~]/
|
89
|
-
|
90
|
-
return true
|
91
|
-
end
|
92
|
-
|
93
|
-
# this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
|
94
|
-
# to define a recursive function to return the length of an array:
|
95
|
-
#
|
96
|
-
# length = ycomb do |len|
|
97
|
-
# proc { |list| list == [] ? 0 : 1 + len.call(list[1..-1]) }
|
98
|
-
# end
|
99
|
-
#
|
100
|
-
# length.call([0])
|
101
|
-
# # => 1
|
102
|
-
#
|
103
|
-
# see https://en.wikipedia.org/wiki/Fixed-point_combinator#Y_combinator
|
104
|
-
# and chapter 9 of the little schemer, available as the sample chapter at
|
105
|
-
# https://felleisen.org/matthias/BTLS-index.html
|
106
|
-
def ycomb
|
107
|
-
proc { |f| f.call(f) }.call(proc { |f| yield proc { |*x| f.call(f).call(*x) } })
|
108
|
-
end
|
109
|
-
|
110
|
-
module FingerprintHash
|
111
|
-
# overrides BasicObject#==
|
112
|
-
def ==(other)
|
113
|
-
__id__ == other.__id__ || (other.respond_to?(:jsi_fingerprint) && other.jsi_fingerprint == self.jsi_fingerprint)
|
114
|
-
end
|
115
|
-
|
116
|
-
alias_method :eql?, :==
|
117
|
-
|
118
|
-
# overrides Kernel#hash
|
119
|
-
def hash
|
120
|
-
jsi_fingerprint.hash
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
class MemoMap
|
125
|
-
Result = Util::AttrStruct[*%w(
|
126
|
-
value
|
127
|
-
inputs
|
128
|
-
inputs_hash
|
129
|
-
)]
|
130
|
-
|
131
|
-
class Result
|
132
|
-
end
|
133
|
-
|
134
|
-
def initialize(key_by: nil, &block)
|
135
|
-
@key_by = key_by
|
136
|
-
@block = block
|
137
|
-
|
138
|
-
# each result has its own mutex to update its memoized value thread-safely
|
139
|
-
@result_mutexes = {}
|
140
|
-
# another mutex to thread-safely initialize each result mutex
|
141
|
-
@result_mutexes_mutex = Mutex.new
|
142
|
-
|
143
|
-
@results = {}
|
144
|
-
end
|
145
|
-
|
146
|
-
def [](*inputs)
|
147
|
-
if @key_by
|
148
|
-
key = @key_by.call(*inputs)
|
149
|
-
else
|
150
|
-
key = inputs
|
151
|
-
end
|
152
|
-
result_mutex = @result_mutexes_mutex.synchronize do
|
153
|
-
@result_mutexes[key] ||= Mutex.new
|
154
|
-
end
|
155
|
-
|
156
|
-
result_mutex.synchronize do
|
157
|
-
inputs_hash = inputs.hash
|
158
|
-
if @results.key?(key) && inputs_hash == @results[key].inputs_hash && inputs == @results[key].inputs
|
159
|
-
@results[key].value
|
160
|
-
else
|
161
|
-
value = @block.call(*inputs)
|
162
|
-
@results[key] = Result.new(value: value, inputs: inputs, inputs_hash: inputs_hash)
|
163
|
-
value
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
module Memoize
|
170
|
-
def self.extended(object)
|
171
|
-
object.send(:jsi_initialize_memos)
|
172
|
-
end
|
173
|
-
|
174
|
-
private
|
175
|
-
|
176
|
-
def jsi_initialize_memos
|
177
|
-
@jsi_memomaps_mutex = Mutex.new
|
178
|
-
@jsi_memomaps = {}
|
179
|
-
end
|
180
|
-
|
181
|
-
# @return [Util::MemoMap]
|
182
|
-
def jsi_memomap(name, **options, &block)
|
183
|
-
raise(Bug, 'must jsi_initialize_memos') unless @jsi_memomaps
|
184
|
-
unless @jsi_memomaps.key?(name)
|
185
|
-
@jsi_memomaps_mutex.synchronize do
|
186
|
-
# note: this ||= appears redundant with `unless @jsi_memomaps.key?(name)`,
|
187
|
-
# but that check is not thread safe. this check is.
|
188
|
-
@jsi_memomaps[name] ||= Util::MemoMap.new(**options, &block)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
@jsi_memomaps[name]
|
192
|
-
end
|
193
|
-
|
194
|
-
def jsi_memoize(name, *inputs, &block)
|
195
|
-
jsi_memomap(name, &block)[*inputs]
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
module Virtual
|
200
|
-
class InstantiationError < StandardError
|
201
|
-
end
|
202
|
-
|
203
|
-
# this virtual class is not intended to be instantiated except by its subclasses, which override #initialize
|
204
|
-
def initialize
|
205
|
-
# :nocov:
|
206
|
-
raise(InstantiationError, "cannot instantiate virtual class #{self.class}")
|
207
|
-
# :nocov:
|
208
|
-
end
|
209
|
-
|
210
|
-
# virtual_method is used to indicate that the method calling it must be implemented on the (non-virtual) subclass
|
211
|
-
def virtual_method
|
212
|
-
# :nocov:
|
213
|
-
raise(Bug, "class #{self.class} must implement #{caller_locations.first.label}")
|
214
|
-
# :nocov:
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
|
-
public
|
219
|
-
|
220
|
-
extend self
|
221
227
|
end
|
222
|
-
public
|
223
|
-
extend Util
|
224
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,9 +8,6 @@ require "pathname"
|
|
8
8
|
require "bigdecimal"
|
9
9
|
require "addressable/uri"
|
10
10
|
|
11
|
-
require "jsi/util"
|
12
|
-
require "jsi/typelike_modules"
|
13
|
-
|
14
11
|
module JSI
|
15
12
|
# generally put in code paths that are not expected to be valid control flow paths.
|
16
13
|
# rather a NotImplementedCorrectlyError. but that's too long.
|
@@ -20,6 +17,9 @@ module JSI
|
|
20
17
|
class Bug < NotImplementedError
|
21
18
|
end
|
22
19
|
|
20
|
+
# @private TODO remove, any ruby without this is already long EOL
|
21
|
+
FrozenError = Object.const_defined?(:FrozenError) ? ::FrozenError : Class.new(StandardError)
|
22
|
+
|
23
23
|
# @private
|
24
24
|
ROOT_PATH = Pathname.new(__FILE__).dirname.parent.expand_path
|
25
25
|
|
@@ -29,68 +29,94 @@ module JSI
|
|
29
29
|
# @private
|
30
30
|
SCHEMAS_PATH = RESOURCES_PATH.join('schemas')
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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)
|
38
41
|
end
|
39
42
|
|
40
|
-
autoload :
|
41
|
-
autoload :
|
42
|
-
autoload :Hashlike, 'jsi/typelike_modules'
|
43
|
-
autoload :Arraylike, 'jsi/typelike_modules'
|
43
|
+
autoload :Util, 'jsi/util'
|
44
|
+
autoload :Ptr, 'jsi/ptr'
|
44
45
|
autoload :Schema, 'jsi/schema'
|
45
46
|
autoload :SchemaSet, 'jsi/schema_set'
|
46
47
|
autoload :Base, 'jsi/base'
|
47
|
-
autoload
|
48
|
-
autoload :
|
48
|
+
autoload(:MetaSchemaNode, 'jsi/metaschema_node')
|
49
|
+
autoload :SchemaModule, 'jsi/schema_classes'
|
49
50
|
autoload :SchemaClasses, 'jsi/schema_classes'
|
50
51
|
autoload :SchemaRegistry, 'jsi/schema_registry'
|
51
52
|
autoload :Validation, 'jsi/validation'
|
52
53
|
autoload :JSICoder, 'jsi/jsi_coder'
|
53
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'
|
54
58
|
autoload :JSONSchemaOrgDraft04, 'schemas/json-schema.org/draft-04/schema'
|
55
59
|
autoload :JSONSchemaOrgDraft06, 'schemas/json-schema.org/draft-06/schema'
|
56
60
|
autoload :JSONSchemaOrgDraft07, 'schemas/json-schema.org/draft-07/schema'
|
57
61
|
|
58
62
|
autoload :SimpleWrap, 'jsi/simple_wrap'
|
59
63
|
|
60
|
-
#
|
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}.
|
61
66
|
#
|
62
|
-
# see
|
63
|
-
|
64
|
-
|
65
|
-
# @return (see JSI::Schema.new_schema)
|
66
|
-
def self.new_schema(schema_object, **kw)
|
67
|
-
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
|
68
70
|
end
|
69
71
|
|
70
|
-
#
|
71
|
-
#
|
72
|
-
# shortcut to chain {JSI::Schema.new_schema} + {Schema#jsi_schema_module}.
|
72
|
+
# @private pending dialect/vocabularies
|
73
|
+
# Instantiates the given document as a JSI Meta-Schema.
|
73
74
|
#
|
74
|
-
# @param
|
75
|
-
# @
|
76
|
-
|
77
|
-
|
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
|
+
)
|
78
89
|
end
|
79
90
|
|
80
|
-
# @private
|
81
|
-
|
82
|
-
|
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}.
|
94
|
+
#
|
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
|
83
98
|
end
|
84
99
|
|
85
|
-
# `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.
|
86
102
|
#
|
87
103
|
# @return [JSI::SchemaRegistry]
|
88
104
|
def self.schema_registry
|
89
|
-
|
90
|
-
|
105
|
+
@schema_registry
|
106
|
+
end
|
107
|
+
|
108
|
+
# @param schema_registry [JSI::SchemaRegistry]
|
109
|
+
def self.schema_registry=(schema_registry)
|
110
|
+
@schema_registry = schema_registry
|
91
111
|
end
|
92
|
-
end
|
93
112
|
|
94
|
-
|
95
|
-
|
96
|
-
|
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
|