jsi 0.6.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|