jsi 0.7.0 → 0.8.0

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