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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -1
  3. data/CHANGELOG.md +15 -0
  4. data/README.md +19 -18
  5. data/jsi.gemspec +2 -3
  6. data/lib/jsi/base/mutability.rb +44 -0
  7. data/lib/jsi/base/node.rb +199 -34
  8. data/lib/jsi/base.rb +412 -228
  9. data/lib/jsi/jsi_coder.rb +18 -16
  10. data/lib/jsi/metaschema_node/bootstrap_schema.rb +57 -23
  11. data/lib/jsi/metaschema_node.rb +138 -107
  12. data/lib/jsi/ptr.rb +59 -37
  13. data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
  14. data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
  15. data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
  16. data/lib/jsi/schema/application/child_application.rb +0 -25
  17. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
  18. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
  19. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
  20. data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
  21. data/lib/jsi/schema/application/inplace_application/someof.rb +1 -1
  22. data/lib/jsi/schema/application/inplace_application.rb +0 -27
  23. data/lib/jsi/schema/draft04.rb +0 -1
  24. data/lib/jsi/schema/draft06.rb +0 -1
  25. data/lib/jsi/schema/draft07.rb +0 -1
  26. data/lib/jsi/schema/ref.rb +44 -18
  27. data/lib/jsi/schema/schema_ancestor_node.rb +65 -56
  28. data/lib/jsi/schema/validation/contains.rb +1 -1
  29. data/lib/jsi/schema/validation/draft04/minmax.rb +2 -0
  30. data/lib/jsi/schema/validation/draft04.rb +0 -2
  31. data/lib/jsi/schema/validation/draft06.rb +0 -2
  32. data/lib/jsi/schema/validation/draft07.rb +0 -2
  33. data/lib/jsi/schema/validation/items.rb +3 -3
  34. data/lib/jsi/schema/validation/pattern.rb +1 -1
  35. data/lib/jsi/schema/validation/properties.rb +4 -4
  36. data/lib/jsi/schema/validation/ref.rb +1 -1
  37. data/lib/jsi/schema/validation.rb +0 -2
  38. data/lib/jsi/schema.rb +405 -194
  39. data/lib/jsi/schema_classes.rb +196 -127
  40. data/lib/jsi/schema_registry.rb +66 -17
  41. data/lib/jsi/schema_set.rb +76 -30
  42. data/lib/jsi/simple_wrap.rb +2 -7
  43. data/lib/jsi/util/private/attr_struct.rb +28 -14
  44. data/lib/jsi/util/private/memo_map.rb +75 -0
  45. data/lib/jsi/util/private.rb +73 -92
  46. data/lib/jsi/util/typelike.rb +28 -28
  47. data/lib/jsi/util.rb +120 -36
  48. data/lib/jsi/validation/error.rb +4 -0
  49. data/lib/jsi/validation/result.rb +18 -32
  50. data/lib/jsi/version.rb +1 -1
  51. data/lib/jsi.rb +67 -25
  52. data/lib/schemas/json-schema.org/draft-04/schema.rb +159 -4
  53. data/lib/schemas/json-schema.org/draft-06/schema.rb +161 -4
  54. data/lib/schemas/json-schema.org/draft-07/schema.rb +188 -4
  55. metadata +19 -5
  56. data/lib/jsi/metaschema.rb +0 -6
  57. data/lib/jsi/schema/validation/core.rb +0 -39
@@ -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 && self_respondingto_key.key?(key)
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
- dup.update(other, &block)
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
- alias_method :to_s, :inspect
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.group_sub {
114
- q.nest(2) {
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
- alias_method :to_s, :inspect
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.group_sub {
218
- q.nest(2) {
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::MetaschemaNode) that method is invoked with the given
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
- return yield(object)
29
+ yield(object)
28
30
  end
29
31
  end
30
32
 
31
- # recursive method to express the given argument object in json-compatible
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
- # similar effect could be achieved by requiring 'json/add/core' and using #as_json,
37
- # but I don't much care for how it represents classes that are
38
- # not naturally expressable in JSON, and prefer not to load its
39
- # monkey-patching.
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] the object to be converted to jsonifiability
42
- # @return [Array, Hash, String, Boolean, NilClass, Numeric] jsonifiable
43
- # expression of param object
44
- # @raise [TypeError] when the object (or an object nested with a hash or
45
- # array of object) cannot be expressed as json
46
- def as_json(object, *opt)
47
- if object.is_a?(JSI::Base)
48
- as_json(object.jsi_node_content, *opt)
49
- elsif object.respond_to?(:to_hash)
50
- (object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
51
- unless k.is_a?(Symbol) || k.respond_to?(:to_str)
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
- end
54
- {k.to_s => as_json(v, *opt)}
55
- end.inject({}, &:update)
56
- elsif object.respond_to?(:to_ary)
57
- (object.respond_to?(:map) ? object : object.to_ary).map { |e| as_json(e, *opt) }
58
- elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| object.is_a?(c) }
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, *opt)
64
- elsif object.respond_to?(:as_json)
65
- as_json(object.as_json(*opt), *opt)
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
- raise(TypeError, "cannot express object as json: #{object.pretty_inspect.chomp}")
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
- else
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
@@ -29,6 +29,10 @@ module JSI
29
29
  # document containing the instance at instance_ptr
30
30
  # @return [Object]
31
31
  class Error
32
+ def initialize(attributes = {})
33
+ super
34
+ freeze
35
+ end
32
36
  end
33
37
  end
34
38
  end
@@ -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(subschema_ptr, subinstance_ptr)
50
+ def child_subschema_validate(instance_child_token, subschema_ptr)
55
51
  subresult = schema.subschema(subschema_ptr).internal_validate_instance(
56
- instance_ptr + subinstance_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
- def builder(schema, instance_ptr, instance_document, validate_only, visited_refs)
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
- # :nocov:
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
- def +(result)
160
- FullResult.new.merge(self).merge(result)
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
- # @private
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- VERSION = "0.7.0".freeze
4
+ VERSION = "0.8.0".freeze
5
5
  end
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 :Metaschema, 'jsi/metaschema'
37
- autoload :MetaschemaNode, 'jsi/metaschema_node'
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
- # instantiates a given schema object as a JSI Schema.
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 {JSI::Schema.new_schema}
52
- #
53
- # @param (see JSI::Schema.new_schema)
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
- # instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
72
+ # @private pending dialect/vocabularies
73
+ # Instantiates the given document as a JSI Meta-Schema.
60
74
  #
61
- # shortcut to chain {JSI::Schema.new_schema} + {Schema#jsi_schema_module}.
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
- # @param (see JSI::Schema.new_schema)
64
- # @return [Module, JSI::SchemaModule] the JSI Schema Module of the schema
65
- def self.new_schema_module(schema_object, **kw)
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
- return @schema_registry if instance_variable_defined?(:@schema_registry)
74
- @schema_registry = SchemaRegistry.new
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
- JSI.schema_registry.autoload_uri("http://json-schema.org/draft-04/schema") { JSI::JSONSchemaOrgDraft04.schema }
79
- JSI.schema_registry.autoload_uri("http://json-schema.org/draft-06/schema") { JSI::JSONSchemaOrgDraft06.schema }
80
- JSI.schema_registry.autoload_uri("http://json-schema.org/draft-07/schema") { JSI::JSONSchemaOrgDraft07.schema }
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