jsi 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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