jsi 0.6.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +6 -1
  3. data/CHANGELOG.md +33 -0
  4. data/LICENSE.md +1 -1
  5. data/README.md +29 -23
  6. data/jsi.gemspec +29 -0
  7. data/lib/jsi/base/mutability.rb +44 -0
  8. data/lib/jsi/base/node.rb +348 -0
  9. data/lib/jsi/base.rb +497 -339
  10. data/lib/jsi/jsi_coder.rb +19 -17
  11. data/lib/jsi/metaschema_node/bootstrap_schema.rb +61 -26
  12. data/lib/jsi/metaschema_node.rb +161 -133
  13. data/lib/jsi/ptr.rb +80 -47
  14. data/lib/jsi/schema/application/child_application/contains.rb +11 -2
  15. data/lib/jsi/schema/application/child_application/draft04.rb +0 -1
  16. data/lib/jsi/schema/application/child_application/draft06.rb +0 -1
  17. data/lib/jsi/schema/application/child_application/draft07.rb +0 -1
  18. data/lib/jsi/schema/application/child_application/items.rb +3 -3
  19. data/lib/jsi/schema/application/child_application/properties.rb +3 -3
  20. data/lib/jsi/schema/application/child_application.rb +0 -27
  21. data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
  22. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -1
  23. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -1
  24. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -1
  25. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
  26. data/lib/jsi/schema/application/inplace_application/ref.rb +2 -2
  27. data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
  28. data/lib/jsi/schema/application/inplace_application.rb +0 -32
  29. data/lib/jsi/schema/draft04.rb +0 -1
  30. data/lib/jsi/schema/draft06.rb +0 -1
  31. data/lib/jsi/schema/draft07.rb +0 -1
  32. data/lib/jsi/schema/ref.rb +46 -19
  33. data/lib/jsi/schema/schema_ancestor_node.rb +69 -66
  34. data/lib/jsi/schema/validation/array.rb +3 -3
  35. data/lib/jsi/schema/validation/const.rb +1 -1
  36. data/lib/jsi/schema/validation/contains.rb +2 -2
  37. data/lib/jsi/schema/validation/dependencies.rb +1 -1
  38. data/lib/jsi/schema/validation/draft04/minmax.rb +8 -6
  39. data/lib/jsi/schema/validation/draft04.rb +0 -2
  40. data/lib/jsi/schema/validation/draft06.rb +0 -2
  41. data/lib/jsi/schema/validation/draft07.rb +0 -2
  42. data/lib/jsi/schema/validation/enum.rb +1 -1
  43. data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
  44. data/lib/jsi/schema/validation/items.rb +7 -7
  45. data/lib/jsi/schema/validation/not.rb +1 -1
  46. data/lib/jsi/schema/validation/numeric.rb +5 -5
  47. data/lib/jsi/schema/validation/object.rb +2 -2
  48. data/lib/jsi/schema/validation/pattern.rb +2 -2
  49. data/lib/jsi/schema/validation/properties.rb +7 -7
  50. data/lib/jsi/schema/validation/property_names.rb +1 -1
  51. data/lib/jsi/schema/validation/ref.rb +2 -2
  52. data/lib/jsi/schema/validation/required.rb +1 -1
  53. data/lib/jsi/schema/validation/someof.rb +3 -3
  54. data/lib/jsi/schema/validation/string.rb +2 -2
  55. data/lib/jsi/schema/validation/type.rb +1 -1
  56. data/lib/jsi/schema/validation.rb +1 -3
  57. data/lib/jsi/schema.rb +443 -226
  58. data/lib/jsi/schema_classes.rb +241 -147
  59. data/lib/jsi/schema_registry.rb +78 -19
  60. data/lib/jsi/schema_set.rb +114 -28
  61. data/lib/jsi/simple_wrap.rb +18 -4
  62. data/lib/jsi/util/private/attr_struct.rb +141 -0
  63. data/lib/jsi/util/private/memo_map.rb +75 -0
  64. data/lib/jsi/util/private.rb +185 -0
  65. data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +79 -105
  66. data/lib/jsi/util.rb +157 -153
  67. data/lib/jsi/validation/error.rb +4 -0
  68. data/lib/jsi/validation/result.rb +18 -32
  69. data/lib/jsi/version.rb +1 -1
  70. data/lib/jsi.rb +65 -39
  71. data/lib/schemas/json-schema.org/draft-04/schema.rb +160 -3
  72. data/lib/schemas/json-schema.org/draft-06/schema.rb +162 -3
  73. data/lib/schemas/json-schema.org/draft-07/schema.rb +189 -3
  74. metadata +27 -11
  75. data/lib/jsi/metaschema.rb +0 -7
  76. data/lib/jsi/pathed_node.rb +0 -116
  77. data/lib/jsi/schema/validation/core.rb +0 -39
  78. 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 classes, modules, constants, and methods are internal, and will be added and removed without warning.
5
- #
6
- # @api private
4
+ # JSI::Util contains public utilities
7
5
  module Util
8
- autoload :AttrStruct, 'jsi/util/attr_struct'
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::Typelike.modified_copy(hashlike) do |hash|
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::Typelike.modified_copy(object) do |hash|
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::Typelike.modified_copy(object) do |ary|
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
- else
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
@@ -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.6.0".freeze
4
+ VERSION = "0.8.0".freeze
5
5
  end
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
- autoload :Ptr, 'jsi/ptr'
33
-
34
- # @private
35
- # @deprecated
36
- module JSON
37
- Pointer = Ptr
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 :PathedNode, 'jsi/pathed_node'
41
- autoload :Typelike, 'jsi/typelike_modules'
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 :Metaschema, 'jsi/metaschema'
48
- autoload :MetaschemaNode, 'jsi/metaschema_node'
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
- # 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}.
61
66
  #
62
- # see {JSI::Schema.new_schema}
63
- #
64
- # @param (see JSI::Schema.new_schema)
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
- # instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
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 (see JSI::Schema.new_schema)
75
- # @return [Module, JSI::SchemaModule] the JSI Schema Module of the schema
76
- def self.new_schema_module(schema_object, **kw)
77
- JSI::Schema.new_schema(schema_object, **kw).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
+ )
78
89
  end
79
90
 
80
- # @private @deprecated
81
- def self.class_for_schemas(schemas)
82
- SchemaClasses.class_for_schemas(schemas.map { |schema| JSI.new_schema(schema) })
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
- return @schema_registry if instance_variable_defined?(:@schema_registry)
90
- @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
91
111
  end
92
- end
93
112
 
94
- JSI.schema_registry.autoload_uri("http://json-schema.org/draft-04/schema") { JSI::JSONSchemaOrgDraft04.schema }
95
- JSI.schema_registry.autoload_uri("http://json-schema.org/draft-06/schema") { JSI::JSONSchemaOrgDraft06.schema }
96
- 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