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.
- checksums.yaml +4 -4
- data/.simplecov +3 -1
- data/CHANGELOG.md +7 -0
- data/LICENSE.md +1 -1
- data/README.md +1 -1
- data/lib/jsi.rb +13 -7
- data/lib/jsi/base.rb +84 -69
- data/lib/jsi/jsi_coder.rb +2 -2
- data/lib/jsi/json/node.rb +2 -2
- data/lib/jsi/json/pointer.rb +82 -90
- data/lib/jsi/metaschema_node.rb +27 -26
- data/lib/jsi/schema.rb +44 -61
- data/lib/jsi/schema_classes.rb +23 -16
- data/lib/jsi/util.rb +30 -29
- data/lib/jsi/version.rb +1 -1
- data/test/base_array_test.rb +14 -14
- data/test/base_hash_test.rb +13 -13
- data/test/base_test.rb +17 -22
- data/test/jsi_coder_test.rb +4 -4
- data/test/jsi_json_node_test.rb +4 -4
- data/test/jsi_json_pointer_test.rb +3 -7
- data/test/metaschema_node_test.rb +1 -1
- data/test/schema_test.rb +70 -48
- data/test/test_helper.rb +2 -2
- data/test/util_test.rb +1 -1
- metadata +2 -2
data/lib/jsi/metaschema_node.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
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
|
-
@
|
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
|
-
|
71
|
-
nil
|
72
|
-
end
|
75
|
+
end.to_set
|
73
76
|
|
74
|
-
|
75
|
-
if
|
77
|
+
@jsi_schemas.each do |schema|
|
78
|
+
if schema.node_ptr == metaschema_root_ptr
|
76
79
|
extend JSI::Schema
|
77
80
|
end
|
78
|
-
if
|
81
|
+
if schema.node_ptr == node_ptr
|
79
82
|
extend Metaschema
|
80
83
|
end
|
81
|
-
|
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
|
-
#
|
110
|
-
attr_reader :
|
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
|
190
|
-
|
190
|
+
if jsi_schemas.any?
|
191
|
+
class_n_schemas = "#{self.class} (#{jsi_schemas.map { |s| s.node_ptr.uri }.join(' ')})"
|
191
192
|
else
|
192
|
-
|
193
|
+
class_n_schemas = self.class.to_s
|
193
194
|
end
|
194
195
|
[
|
195
|
-
|
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
|
|
data/lib/jsi/schema.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# JSI::Schema
|
5
|
-
#
|
6
|
-
#
|
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(
|
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
|
-
|
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
|
131
|
+
# @return [Class < JSI::Base] a JSI class for this one schema
|
134
132
|
def jsi_schema_class
|
135
|
-
JSI.
|
133
|
+
JSI.class_for_schemas(Set[self])
|
136
134
|
end
|
137
135
|
|
138
|
-
#
|
139
|
-
#
|
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
|
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.
|
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
|
-
#
|
152
|
-
#
|
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
|
-
#
|
156
|
-
#
|
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
|
-
|
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
|
-
|
164
|
-
self
|
165
|
-
end
|
162
|
+
end.to_set
|
166
163
|
end
|
167
164
|
|
168
|
-
#
|
169
|
-
#
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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
|
-
|
179
|
-
nil
|
180
|
-
end
|
174
|
+
end.to_set
|
181
175
|
end
|
182
176
|
end
|
183
177
|
|
184
|
-
#
|
185
|
-
#
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
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
|
-
#
|
202
|
-
#
|
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
|
data/lib/jsi/schema_classes.rb
CHANGED
@@ -11,11 +11,11 @@ module JSI
|
|
11
11
|
|
12
12
|
# @return [String]
|
13
13
|
def inspect
|
14
|
-
|
14
|
+
uri = schema.schema_id || schema.node_ptr.uri
|
15
15
|
if name
|
16
|
-
"#{name} (#{
|
16
|
+
"#{name} (#{uri})"
|
17
17
|
else
|
18
|
-
"(JSI Schema Module: #{
|
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.
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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::
|
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
|
-
|
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
|
-
|
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
|
data/lib/jsi/util.rb
CHANGED
@@ -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
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
77
|
+
alias_method :eql?, :==
|
79
78
|
|
80
|
-
|
81
|
-
|
82
|
-
|
79
|
+
# overrides Kernel#hash
|
80
|
+
def hash
|
81
|
+
jsi_fingerprint.hash
|
82
|
+
end
|
83
83
|
end
|
84
|
-
end
|
85
84
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
106
|
+
public
|
107
|
+
extend Util
|
107
108
|
end
|