jsi 0.3.0 → 0.4.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.
@@ -24,7 +24,7 @@ module JSI
24
24
  # its schema is the metaschema.
25
25
  class MetaschemaNode
26
26
  include PathedNode
27
- include Memoize
27
+ include Util::Memoize
28
28
 
29
29
  # not every MetaschemaNode is actually an Enumerable, but it's better to include Enumerable on
30
30
  # the class than to conditionally extend the instance.
@@ -49,39 +49,39 @@ module JSI
49
49
  end
50
50
 
51
51
  instance_for_schema = node_document
52
- schema_ptr = node_ptr.reference_tokens.inject(root_schema_ptr) do |ptr0, tok|
52
+ schema_ptrs = node_ptr.reference_tokens.inject(Set.new << root_schema_ptr) do |ptrs, tok|
53
53
  if instance_for_schema.respond_to?(:to_ary)
54
- ptr1 = ptr0 && ptr0.schema_subschema_ptr_for_index(node_document, tok)
54
+ subschema_ptrs_for_token = ptrs.map do |ptr|
55
+ ptr.schema_subschema_ptrs_for_index(node_document, tok)
56
+ end.inject(Set.new, &:|)
55
57
  else
56
- ptr1 = ptr0 && ptr0.schema_subschema_ptr_for_property_name(node_document, tok)
58
+ subschema_ptrs_for_token = ptrs.map do |ptr|
59
+ ptr.schema_subschema_ptrs_for_property_name(node_document, tok)
60
+ end.inject(Set.new, &:|)
57
61
  end
58
62
  instance_for_schema = instance_for_schema[tok]
59
- ptr2 = ptr1 && ptr1.deref(node_document)
60
- ptr3 = ptr2 && ptr2.schema_match_ptr_to_instance(node_document, instance_for_schema)
61
- ptr3 # only God may judge my variable names
63
+ ptrs_for_instance = subschema_ptrs_for_token.map do |ptr|
64
+ ptr.schema_match_ptrs_to_instance(node_document, instance_for_schema)
65
+ end.inject(Set.new, &:|)
66
+ ptrs_for_instance
62
67
  end
63
68
 
64
- @schema = if schema_ptr
69
+ @jsi_schemas = schema_ptrs.map do |schema_ptr|
65
70
  if schema_ptr == node_ptr
66
71
  self
67
72
  else
68
73
  new_node(node_ptr: schema_ptr)
69
74
  end
70
- else
71
- nil
72
- end
75
+ end.to_set
73
76
 
74
- if schema_ptr
75
- if schema_ptr == metaschema_root_ptr
77
+ @jsi_schemas.each do |schema|
78
+ if schema.node_ptr == metaschema_root_ptr
76
79
  extend JSI::Schema
77
80
  end
78
- if schema_ptr == node_ptr
81
+ if schema.node_ptr == node_ptr
79
82
  extend Metaschema
80
83
  end
81
- end
82
-
83
- if @schema
84
- extend(JSI::SchemaClasses.accessor_module_for_schema(@schema, conflicting_modules: [Metaschema, Schema, MetaschemaNode, PathedArrayNode, PathedHashNode]))
84
+ extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [Metaschema, Schema, MetaschemaNode, PathedArrayNode, PathedHashNode]))
85
85
  end
86
86
 
87
87
  # workarounds
@@ -106,8 +106,8 @@ module JSI
106
106
  attr_reader :metaschema_root_ptr
107
107
  # ptr to the schema of the root of the node_document
108
108
  attr_reader :root_schema_ptr
109
- # a JSI::Schema describing this MetaschemaNode
110
- attr_reader :schema
109
+ # JSI::Schemas describing this MetaschemaNode
110
+ attr_reader :jsi_schemas
111
111
 
112
112
  # @return [MetaschemaNode] document root MetaschemaNode
113
113
  def document_root_node
@@ -134,7 +134,7 @@ module JSI
134
134
  raise(NoMethodError, "cannot subcript (using token: #{token.inspect}) from content: #{node_content.pretty_inspect.chomp}")
135
135
  end
136
136
 
137
- jsi_memoize(:[], token, value, token_in_range) do |token, value, token_in_range|
137
+ result = jsi_memoize(:[], token, value, token_in_range) do |token, value, token_in_range|
138
138
  if token_in_range
139
139
  value_node = new_node(node_ptr: node_ptr[token])
140
140
 
@@ -148,6 +148,7 @@ module JSI
148
148
  nil
149
149
  end
150
150
  end
151
+ result
151
152
  end
152
153
 
153
154
  # if this MetaschemaNode is a $ref then the $ref is followed. otherwise this MetaschemaNode is returned.
@@ -186,13 +187,13 @@ module JSI
186
187
 
187
188
  # @return [Array<String>]
188
189
  def object_group_text
189
- if schema
190
- class_n_schema = "#{self.class} (#{schema.node_ptr.fragment})"
190
+ if jsi_schemas.any?
191
+ class_n_schemas = "#{self.class} (#{jsi_schemas.map { |s| s.node_ptr.uri }.join(' ')})"
191
192
  else
192
- class_n_schema = self.class.to_s
193
+ class_n_schemas = self.class.to_s
193
194
  end
194
195
  [
195
- class_n_schema,
196
+ class_n_schemas,
196
197
  is_a?(Metaschema) ? "Metaschema" : is_a?(Schema) ? "Schema" : nil,
197
198
  *(node_content.respond_to?(:object_group_text) ? node_content.object_group_text : []),
198
199
  ].compact
@@ -202,7 +203,7 @@ module JSI
202
203
  def jsi_fingerprint
203
204
  {class: self.class, node_document: node_document}.merge(our_initialize_params)
204
205
  end
205
- include FingerprintHash
206
+ include Util::FingerprintHash
206
207
 
207
208
  private
208
209
 
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- # JSI::Schema represents a JSON Schema. initialized from a Hash-like schema
5
- # object, JSI::Schema is a relatively simple class to abstract useful methods
6
- # applied to a JSON Schema.
4
+ # JSI::Schema is a module which extends instances which represent JSON schemas.
5
+ #
6
+ # the content of an instance which is a JSI::Schema (referred to in this context as schema_content) is
7
+ # expected to be a Hash (JSON object) or a Boolean.
7
8
  module Schema
8
9
  class Error < StandardError
9
10
  end
@@ -12,8 +13,6 @@ module JSI
12
13
  class NotASchemaError < Error
13
14
  end
14
15
 
15
- include Memoize
16
-
17
16
  # JSI::Schema::DescribesSchema: a schema which describes another schema. this module
18
17
  # extends a JSI::Schema instance and indicates that JSIs which instantiate the schema
19
18
  # are themselves also schemas.
@@ -109,14 +108,13 @@ module JSI
109
108
  parent_auri = Addressable::URI.parse(parent_id)
110
109
  if parent_auri.fragment
111
110
  # add onto the fragment
112
- parent_id_path = JSI::JSON::Pointer.from_fragment('#' + parent_auri.fragment).reference_tokens
111
+ parent_id_path = JSI::JSON::Pointer.from_fragment(parent_auri.fragment).reference_tokens
113
112
  path_from_id_node = parent_id_path + path_from_id_node
114
113
  parent_auri.fragment = nil
115
114
  #else: no fragment so parent_id good as is
116
115
  end
117
116
 
118
- fragment = JSI::JSON::Pointer.new(path_from_id_node).fragment
119
- schema_id = parent_auri.to_s + fragment
117
+ schema_id = parent_auri.merge(fragment: JSI::JSON::Pointer.new(path_from_id_node).fragment).to_s
120
118
 
121
119
  schema_id
122
120
  else
@@ -130,17 +128,20 @@ module JSI
130
128
  JSI::SchemaClasses.module_for_schema(self)
131
129
  end
132
130
 
133
- # @return [Class subclassing JSI::Base] shortcut for JSI.class_for_schema(schema)
131
+ # @return [Class < JSI::Base] a JSI class for this one schema
134
132
  def jsi_schema_class
135
- JSI.class_for_schema(self)
133
+ JSI.class_for_schemas(Set[self])
136
134
  end
137
135
 
138
- # calls #new on the class for this schema with the given arguments. for parameters,
139
- # see JSI::Base#initialize documentation.
136
+ # instantiates the given other_instance as a JSI::Base class for schemas matched from this schema to the
137
+ # other_instance.
138
+ #
139
+ # any parameters are passed to JSI::Base#initialize, but none are normally used.
140
140
  #
141
- # @return [JSI::Base] a JSI whose schema is this schema and whose instance is the given instance
141
+ # @return [JSI::Base] a JSI whose instance is the given instance and whose schemas are matched from this
142
+ # schema.
142
143
  def new_jsi(other_instance, *a, &b)
143
- JSI.class_for_schema(match_to_instance(other_instance)).new(other_instance, *a, &b)
144
+ JSI.class_for_schemas(match_to_instance(other_instance)).new(other_instance, *a, &b)
144
145
  end
145
146
 
146
147
  # @return [Boolean] does this schema itself describe a schema?
@@ -148,60 +149,48 @@ module JSI
148
149
  is_a?(JSI::Schema::DescribesSchema)
149
150
  end
150
151
 
151
- # if this schema is a oneOf, allOf, anyOf schema, #match_to_instance finds
152
- # one of the subschemas that matches the given instance and returns it. if
153
- # there are no matching *Of schemas, this schema is returned.
152
+ # checks this schema for applicators ($ref, allOf, etc.) which should be applied to the given instance.
153
+ # returns these as a Set of {JSI::Schema}s.
154
154
  #
155
- # @param other_instance [Object] the instance to which to attempt to match *Of subschemas
156
- # @return [JSI::Schema] a matched subschema, or this schema (self)
155
+ # the returned set will contain this schema itself, unless this schema contains a $ref keyword.
156
+ #
157
+ # @param other_instance [Object] the instance to check any applicators against
158
+ # @return [Set<JSI::Schema>] matched applicator schemas
157
159
  def match_to_instance(other_instance)
158
- ptr = node_ptr
159
- ptr = ptr.deref(node_document)
160
- ptr = ptr.schema_match_ptr_to_instance(node_document, other_instance)
161
- if ptr
160
+ node_ptr.schema_match_ptrs_to_instance(node_document, other_instance).map do |ptr|
162
161
  ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
163
- else
164
- self
165
- end
162
+ end.to_set
166
163
  end
167
164
 
168
- # @param property_name [String] the property name for which to find a subschema
169
- # @return [JSI::Schema, nil] a subschema from `properties`, `patternProperties`, or `additionalProperties` for the given token
170
- def subschema_for_property(property_name)
171
- jsi_memoize(:subschema_for_property, property_name) do |property_name|
172
- ptr = node_ptr
173
- ptr = ptr.deref(node_document)
174
- ptr = ptr.schema_subschema_ptr_for_property_name(node_document, property_name)
175
- if ptr
176
- ptr = ptr.deref(node_document)
165
+ # returns a set of subschemas of this schema for the given property name, from keywords
166
+ # `properties`, `patternProperties`, and `additionalProperties`.
167
+ #
168
+ # @param property_name [String] the property name for which to find subschemas
169
+ # @return [Set<JSI::Schema>] subschemas of this schema for the given property_name
170
+ def subschemas_for_property_name(property_name)
171
+ jsi_memoize(:subschemas_for_property_name, property_name) do |property_name|
172
+ node_ptr.schema_subschema_ptrs_for_property_name(node_document, property_name).map do |ptr|
177
173
  ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
178
- else
179
- nil
180
- end
174
+ end.to_set
181
175
  end
182
176
  end
183
177
 
184
- # @param index [Integer] the array index for which to find a subschema
185
- # @return [JSI::Schema, nil] a subschema from `items` or `additionalItems` for the given token
186
- def subschema_for_index(index)
187
- jsi_memoize(:subschema_for_index, index) do |index|
188
- ptr = node_ptr
189
- ptr = ptr.deref(node_document)
190
- ptr = ptr.schema_subschema_ptr_for_index(node_document, index)
191
- if ptr
192
- ptr = ptr.deref(node_document)
178
+ # returns a set of subschemas of this schema for the given array index, from keywords
179
+ # `items` and `additionalItems`.
180
+ #
181
+ # @param index [Integer] the array index for which to find subschemas
182
+ # @return [Set<JSI::Schema>] subschemas of this schema for the given array index
183
+ def subschemas_for_index(index)
184
+ jsi_memoize(:subschemas_for_index, index) do |index|
185
+ node_ptr.schema_subschema_ptrs_for_index(node_document, index).map do |ptr|
193
186
  ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
194
- else
195
- nil
196
- end
187
+ end.to_set
197
188
  end
198
189
  end
199
190
 
200
- # @return [Set] any object property names this schema indicates may be
201
- # present on its instances. this includes, if present: keys of this
202
- # schema's "properties" object; entries of this schema's array of
203
- # "required" property keys. if this schema has allOf subschemas, those
204
- # schemas are checked (recursively) for their described object property names.
191
+ # @return [Set] any object property names this schema indicates may be present on its instances.
192
+ # this includes any keys of this schema's "properties" object and any entries of this schema's
193
+ # array of "required" property keys.
205
194
  def described_object_property_names
206
195
  jsi_memoize(:described_object_property_names) do
207
196
  Set.new.tap do |property_names|
@@ -211,12 +200,6 @@ module JSI
211
200
  if node_content.respond_to?(:to_hash) && node_content['required'].respond_to?(:to_ary)
212
201
  property_names.merge(node_content['required'].to_ary)
213
202
  end
214
- # we should look at dependencies (TODO).
215
- if respond_to?(:to_hash) && self['allOf'].respond_to?(:to_ary)
216
- self['allOf'].select{ |s| s.is_a?(JSI::Schema) }.map(&:deref).map do |allOf_schema|
217
- property_names.merge(allOf_schema.described_object_property_names)
218
- end
219
- end
220
203
  end
221
204
  end
222
205
  end
@@ -11,11 +11,11 @@ module JSI
11
11
 
12
12
  # @return [String]
13
13
  def inspect
14
- idfrag = schema.schema_id || schema.node_ptr.fragment
14
+ uri = schema.schema_id || schema.node_ptr.uri
15
15
  if name
16
- "#{name} (#{idfrag})"
16
+ "#{name} (#{uri})"
17
17
  else
18
- "(JSI Schema Module: #{idfrag})"
18
+ "(JSI Schema Module: #{uri})"
19
19
  end
20
20
  end
21
21
 
@@ -29,16 +29,16 @@ module JSI
29
29
  # this module is just a namespace for schema classes.
30
30
  module SchemaClasses
31
31
  class << self
32
- include Memoize
33
-
34
- # see {JSI.class_for_schema}
35
- def class_for_schema(schema_object)
36
- jsi_memoize(:class_for_schema, JSI::Schema.from_object(schema_object)) do |schema|
37
- Class.new(Base).instance_exec(schema) do |schema|
38
- define_singleton_method(:schema) { schema }
39
- define_method(:schema) { schema }
40
- include(schema.jsi_schema_module)
41
-
32
+ include Util::Memoize
33
+
34
+ # see {JSI.class_for_schemas}
35
+ def class_for_schemas(schema_objects)
36
+ schemas = schema_objects.map { |schema_object| JSI::Schema.from_object(schema_object) }.to_set
37
+ jsi_memoize(:class_for_schemas, schemas) do |schemas|
38
+ Class.new(Base).instance_exec(schemas) do |schemas|
39
+ define_singleton_method(:jsi_class_schemas) { schemas }
40
+ define_method(:jsi_schemas) { schemas }
41
+ schemas.each { |schema| include(schema.jsi_schema_module) }
42
42
  jsi_class = self
43
43
  define_method(:jsi_class) { jsi_class }
44
44
 
@@ -61,11 +61,13 @@ module JSI
61
61
 
62
62
  extend SchemaModule
63
63
 
64
- include JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [JSI::Base, JSI::BaseArray, JSI::BaseHash])
64
+ include JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [JSI::Base, JSI::PathedArrayNode, JSI::PathedHashNode])
65
65
 
66
66
  @possibly_schema_node = schema
67
67
  extend(SchemaModulePossibly)
68
- extend(JSI::SchemaClasses.accessor_module_for_schema(schema.schema, conflicting_modules: [Module, SchemaModule, SchemaModulePossibly]))
68
+ schema.jsi_schemas.each do |schema_schema|
69
+ extend(JSI::SchemaClasses.accessor_module_for_schema(schema_schema, conflicting_modules: [Module, SchemaModule, SchemaModulePossibly]))
70
+ end
69
71
  end
70
72
  end
71
73
  end
@@ -78,6 +80,9 @@ module JSI
78
80
  # @return [Module] a module of accessors (setters and getters) for described property names of the given
79
81
  # schema
80
82
  def accessor_module_for_schema(schema, conflicting_modules: )
83
+ unless schema.is_a?(JSI::Schema)
84
+ raise(JSI::Schema::NotASchemaError, "not a schema: #{schema.pretty_inspect.chomp}")
85
+ end
81
86
  jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules) do |schema, conflicting_modules|
82
87
  Module.new.tap do |m|
83
88
  m.module_eval do
@@ -143,7 +148,9 @@ module JSI
143
148
  raise(TypeError, "cannot instantiate NotASchemaModule for a JSI::Schema node: #{node.pretty_inspect.chomp}")
144
149
  end
145
150
  @possibly_schema_node = node
146
- extend(JSI::SchemaClasses.accessor_module_for_schema(node.schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly]))
151
+ node.jsi_schemas.each do |schema|
152
+ extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly]))
153
+ end
147
154
  end
148
155
 
149
156
  include SchemaModulePossibly
@@ -1,6 +1,8 @@
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
+ # do not rely on them.
4
6
  module Util
5
7
  # a proc which does nothing
6
8
  NOOP = -> (*_) { }
@@ -16,7 +18,7 @@ module JSI
16
18
  # the return if you need to ensure it is not the same instance as the
17
19
  # argument instance.
18
20
  #
19
- # @param hash [#to_hash] the hash from which to convert symbol keys to strings
21
+ # @param hashlike [#to_hash] the hash from which to convert symbol keys to strings
20
22
  # @return [same class as the param `hash`, or Hash if the former cannot be done] a
21
23
  # hash(-like) instance containing no symbol keys
22
24
  def stringify_symbol_keys(hashlike)
@@ -65,43 +67,42 @@ module JSI
65
67
  proc { |f| f.call(f) }.call(proc { |f| yield proc { |*x| f.call(f).call(*x) } })
66
68
  end
67
69
  module_function :ycomb
68
- end
69
- public
70
- extend Util
71
70
 
72
- module FingerprintHash
73
- # overrides BasicObject#==
74
- def ==(other)
75
- object_id == other.object_id || (other.respond_to?(:jsi_fingerprint) && other.jsi_fingerprint == self.jsi_fingerprint)
76
- end
71
+ module FingerprintHash
72
+ # overrides BasicObject#==
73
+ def ==(other)
74
+ object_id == other.object_id || (other.respond_to?(:jsi_fingerprint) && other.jsi_fingerprint == self.jsi_fingerprint)
75
+ end
77
76
 
78
- alias_method :eql?, :==
77
+ alias_method :eql?, :==
79
78
 
80
- # overrides Kernel#hash
81
- def hash
82
- jsi_fingerprint.hash
79
+ # overrides Kernel#hash
80
+ def hash
81
+ jsi_fingerprint.hash
82
+ end
83
83
  end
84
- end
85
84
 
86
- module Memoize
87
- def jsi_memoize(key, *args_)
88
- @jsi_memos ||= {}
89
- @jsi_memos[key] ||= Hash.new do |h, args|
90
- h[args] = yield(*args)
85
+ module Memoize
86
+ def jsi_memoize(key, *args_)
87
+ @jsi_memos ||= {}
88
+ @jsi_memos[key] ||= Hash.new do |h, args|
89
+ h[args] = yield(*args)
90
+ end
91
+ @jsi_memos[key][args_]
91
92
  end
92
- @jsi_memos[key][args_]
93
- end
94
93
 
95
- def jsi_clear_memo(key, *args)
96
- @jsi_memos ||= {}
97
- if @jsi_memos[key]
98
- if args.empty?
99
- @jsi_memos[key].clear
100
- else
101
- @jsi_memos[key].delete(args)
94
+ def jsi_clear_memo(key, *args)
95
+ @jsi_memos ||= {}
96
+ if @jsi_memos[key]
97
+ if args.empty?
98
+ @jsi_memos[key].clear
99
+ else
100
+ @jsi_memos[key].delete(args)
101
+ end
102
102
  end
103
103
  end
104
104
  end
105
105
  end
106
- extend Memoize
106
+ public
107
+ extend Util
107
108
  end