jsi 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +15 -0
- data/README.md +105 -38
- data/lib/jsi/base.rb +349 -155
- data/lib/jsi/jsi_coder.rb +5 -4
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
- data/lib/jsi/metaschema_node.rb +156 -129
- data/lib/jsi/pathed_node.rb +47 -49
- data/lib/jsi/ptr.rb +292 -0
- data/lib/jsi/schema/application/child_application/contains.rb +16 -0
- data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
- data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
- data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
- data/lib/jsi/schema/application/child_application/items.rb +18 -0
- data/lib/jsi/schema/application/child_application/properties.rb +25 -0
- data/lib/jsi/schema/application/child_application.rb +40 -0
- data/lib/jsi/schema/application/draft04.rb +8 -0
- data/lib/jsi/schema/application/draft06.rb +8 -0
- data/lib/jsi/schema/application/draft07.rb +8 -0
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
- data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
- data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
- data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
- data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
- data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
- data/lib/jsi/schema/application/inplace_application.rb +46 -0
- data/lib/jsi/schema/application.rb +12 -0
- data/lib/jsi/schema/draft04.rb +14 -0
- data/lib/jsi/schema/draft06.rb +14 -0
- data/lib/jsi/schema/draft07.rb +14 -0
- data/lib/jsi/schema/issue.rb +36 -0
- data/lib/jsi/schema/ref.rb +159 -0
- data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
- data/lib/jsi/schema/validation/array.rb +69 -0
- data/lib/jsi/schema/validation/const.rb +20 -0
- data/lib/jsi/schema/validation/contains.rb +25 -0
- data/lib/jsi/schema/validation/core.rb +39 -0
- data/lib/jsi/schema/validation/dependencies.rb +49 -0
- data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
- data/lib/jsi/schema/validation/draft04.rb +112 -0
- data/lib/jsi/schema/validation/draft06.rb +122 -0
- data/lib/jsi/schema/validation/draft07.rb +159 -0
- data/lib/jsi/schema/validation/enum.rb +25 -0
- data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
- data/lib/jsi/schema/validation/items.rb +54 -0
- data/lib/jsi/schema/validation/not.rb +20 -0
- data/lib/jsi/schema/validation/numeric.rb +121 -0
- data/lib/jsi/schema/validation/object.rb +45 -0
- data/lib/jsi/schema/validation/pattern.rb +34 -0
- data/lib/jsi/schema/validation/properties.rb +101 -0
- data/lib/jsi/schema/validation/property_names.rb +32 -0
- data/lib/jsi/schema/validation/ref.rb +40 -0
- data/lib/jsi/schema/validation/required.rb +27 -0
- data/lib/jsi/schema/validation/someof.rb +90 -0
- data/lib/jsi/schema/validation/string.rb +47 -0
- data/lib/jsi/schema/validation/type.rb +49 -0
- data/lib/jsi/schema/validation.rb +51 -0
- data/lib/jsi/schema.rb +486 -133
- data/lib/jsi/schema_classes.rb +157 -42
- data/lib/jsi/schema_registry.rb +141 -0
- data/lib/jsi/schema_set.rb +141 -0
- data/lib/jsi/simple_wrap.rb +2 -2
- data/lib/jsi/typelike_modules.rb +52 -37
- data/lib/jsi/util/attr_struct.rb +106 -0
- data/lib/jsi/util.rb +141 -25
- data/lib/jsi/validation/error.rb +34 -0
- data/lib/jsi/validation/result.rb +210 -0
- data/lib/jsi/validation.rb +15 -0
- data/lib/jsi/version.rb +3 -1
- data/lib/jsi.rb +55 -9
- data/lib/schemas/json-schema.org/draft-04/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
- data/readme.rb +138 -0
- data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
- data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
- data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
- metadata +69 -118
- data/.simplecov +0 -3
- data/Rakefile.rb +0 -9
- data/jsi.gemspec +0 -28
- data/lib/jsi/base/to_rb.rb +0 -128
- data/lib/jsi/json/node.rb +0 -203
- data/lib/jsi/json/pointer.rb +0 -419
- data/lib/jsi/json-schema-fragments.rb +0 -61
- data/lib/jsi/json.rb +0 -10
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +0 -323
- data/test/base_hash_test.rb +0 -337
- data/test/base_test.rb +0 -486
- data/test/jsi_coder_test.rb +0 -85
- data/test/jsi_json_arraynode_test.rb +0 -150
- data/test/jsi_json_hashnode_test.rb +0 -132
- data/test/jsi_json_node_test.rb +0 -257
- data/test/jsi_json_pointer_test.rb +0 -102
- data/test/jsi_test.rb +0 -11
- data/test/jsi_typelike_as_json_test.rb +0 -53
- data/test/metaschema_node_test.rb +0 -19
- data/test/schema_module_test.rb +0 -21
- data/test/schema_test.rb +0 -208
- data/test/spreedly_openapi_test.rb +0 -8
- data/test/test_helper.rb +0 -97
- data/test/util_test.rb +0 -62
data/lib/jsi/typelike_modules.rb
CHANGED
@@ -3,10 +3,9 @@
|
|
3
3
|
module JSI
|
4
4
|
# a module relating to objects that act like Hash or Array instances
|
5
5
|
module Typelike
|
6
|
-
# yields the content of the given param `object`. for objects which have a
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# is yielded.
|
6
|
+
# yields the content of the given param `object`. for objects which have a #jsi_modified_copy
|
7
|
+
# method of their own (JSI::Base, JSI::MetaschemaNode) that method is invoked with the given
|
8
|
+
# block. otherwise the given object itself is yielded.
|
10
9
|
#
|
11
10
|
# the given block must result in a modified copy of its block parameter
|
12
11
|
# (not destructively modifying the yielded content).
|
@@ -15,8 +14,8 @@ module JSI
|
|
15
14
|
# in a (nondestructively) modified copy of this.
|
16
15
|
# @return [object.class] modified copy of the given object
|
17
16
|
def self.modified_copy(object, &block)
|
18
|
-
if object.respond_to?(:
|
19
|
-
object.
|
17
|
+
if object.respond_to?(:jsi_modified_copy)
|
18
|
+
object.jsi_modified_copy(&block)
|
20
19
|
else
|
21
20
|
return yield(object)
|
22
21
|
end
|
@@ -27,8 +26,8 @@ module JSI
|
|
27
26
|
# will raise TypeError if an object is given that is not a type that seems
|
28
27
|
# to be expressable as json.
|
29
28
|
#
|
30
|
-
# similar effect could be achieved by requiring 'json/add/core' and using
|
31
|
-
#
|
29
|
+
# similar effect could be achieved by requiring 'json/add/core' and using #as_json,
|
30
|
+
# but I don't much care for how it represents classes that are
|
32
31
|
# not naturally expressable in JSON, and prefer not to load its
|
33
32
|
# monkey-patching.
|
34
33
|
#
|
@@ -39,7 +38,7 @@ module JSI
|
|
39
38
|
# array of object) cannot be expressed as json
|
40
39
|
def self.as_json(object, *opt)
|
41
40
|
if object.is_a?(JSI::PathedNode)
|
42
|
-
as_json(object.
|
41
|
+
as_json(object.jsi_node_content, *opt)
|
43
42
|
elsif object.respond_to?(:to_hash)
|
44
43
|
(object.respond_to?(:map) ? object : object.to_hash).map do |k, v|
|
45
44
|
unless k.is_a?(Symbol) || k.respond_to?(:to_str)
|
@@ -86,7 +85,7 @@ module JSI
|
|
86
85
|
end
|
87
86
|
safe_modified_copy_methods.each do |method_name|
|
88
87
|
define_method(method_name) do |*a, &b|
|
89
|
-
|
88
|
+
jsi_modified_copy do |object_to_modify|
|
90
89
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
91
90
|
responsive_object.public_send(method_name, *a, &b)
|
92
91
|
end
|
@@ -94,19 +93,19 @@ module JSI
|
|
94
93
|
end
|
95
94
|
safe_kv_block_modified_copy_methods.each do |method_name|
|
96
95
|
define_method(method_name) do |*a, &b|
|
97
|
-
|
96
|
+
jsi_modified_copy do |object_to_modify|
|
98
97
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_hash
|
99
|
-
responsive_object.public_send(method_name
|
100
|
-
b.call(k, self[k])
|
98
|
+
responsive_object.public_send(method_name) do |k, _v|
|
99
|
+
b.call(k, self[k, *a])
|
101
100
|
end
|
102
101
|
end
|
103
102
|
end
|
104
103
|
end
|
105
104
|
|
106
|
-
#
|
105
|
+
# like [Hash#update](https://ruby-doc.org/core/Hash.html#method-i-update)
|
107
106
|
# @param other [#to_hash] the other hash to update this hash from
|
108
107
|
# @yield [key, oldval, newval] for entries with duplicate keys, the value of each duplicate key
|
109
|
-
# is determined by calling the block with the key, its value in
|
108
|
+
# is determined by calling the block with the key, its value in self and its value in other.
|
110
109
|
# @return self, updated with other
|
111
110
|
# @raise [TypeError] when `other` does not respond to #to_hash
|
112
111
|
def update(other, &block)
|
@@ -115,7 +114,7 @@ module JSI
|
|
115
114
|
end
|
116
115
|
self_respondingto_key = self.respond_to?(:key?) ? self : to_hash
|
117
116
|
other.to_hash.each_pair do |key, value|
|
118
|
-
if
|
117
|
+
if block && self_respondingto_key.key?(key)
|
119
118
|
value = yield(key, self[key], value)
|
120
119
|
end
|
121
120
|
self[key] = value
|
@@ -125,33 +124,34 @@ module JSI
|
|
125
124
|
|
126
125
|
alias_method :merge!, :update
|
127
126
|
|
128
|
-
#
|
127
|
+
# like [Hash#merge](https://ruby-doc.org/core/Hash.html#method-i-merge)
|
129
128
|
# @param other [#to_hash] the other hash to merge into this
|
130
129
|
# @yield [key, oldval, newval] for entries with duplicate keys, the value of each duplicate key
|
131
|
-
# is determined by calling the block with the key, its value in
|
130
|
+
# is determined by calling the block with the key, its value in self and its value in other.
|
132
131
|
# @return duplicate of this hash with the other hash merged in
|
133
132
|
# @raise [TypeError] when `other` does not respond to #to_hash
|
134
133
|
def merge(other, &block)
|
135
134
|
dup.update(other, &block)
|
136
135
|
end
|
137
136
|
|
138
|
-
#
|
139
|
-
#
|
137
|
+
# basically the same #inspect as Hash, but has the class name and, if responsive,
|
138
|
+
# self's #jsi_object_group_text
|
139
|
+
# @return [String]
|
140
140
|
def inspect
|
141
|
-
object_group_str = (respond_to?(:
|
142
|
-
"\#{<#{object_group_str}>#{
|
141
|
+
object_group_str = (respond_to?(:jsi_object_group_text) ? self.jsi_object_group_text : [self.class]).join(' ')
|
142
|
+
"\#{<#{object_group_str}>#{self.map { |k, v| " #{k.inspect} => #{v.inspect}" }.join(',')}}"
|
143
143
|
end
|
144
144
|
|
145
145
|
alias_method :to_s, :inspect
|
146
146
|
|
147
|
-
# pretty-prints a representation this
|
147
|
+
# pretty-prints a representation of this hashlike to the given printer
|
148
148
|
# @return [void]
|
149
149
|
def pretty_print(q)
|
150
|
-
object_group_str = (respond_to?(:
|
150
|
+
object_group_str = (respond_to?(:jsi_object_group_text) ? jsi_object_group_text : [self.class]).join(' ')
|
151
151
|
q.text "\#{<#{object_group_str}>"
|
152
152
|
q.group_sub {
|
153
153
|
q.nest(2) {
|
154
|
-
q.breakable(
|
154
|
+
q.breakable(empty? ? '' : ' ')
|
155
155
|
q.seplist(self, nil, :each_pair) { |k, v|
|
156
156
|
q.group {
|
157
157
|
q.pp k
|
@@ -177,7 +177,7 @@ module JSI
|
|
177
177
|
# methods which do not need to access the element.
|
178
178
|
SAFE_INDEX_ONLY_METHODS = %w(each_index empty? length size)
|
179
179
|
# there are some ambiguous ones that are omitted, like #sort, #map / #collect.
|
180
|
-
SAFE_INDEX_ELEMENT_METHODS = %w(| & * + - <=> abbrev
|
180
|
+
SAFE_INDEX_ELEMENT_METHODS = %w(| & * + - <=> abbrev at bsearch bsearch_index combination compact count cycle dig drop drop_while fetch find_index first include? index join last pack permutation product reject repeated_combination repeated_permutation reverse reverse_each rindex rotate sample select shelljoin shuffle slice sort take take_while transpose uniq values_at zip)
|
181
181
|
DESTRUCTIVE_METHODS = %w(<< clear collect! compact! concat delete delete_at delete_if fill flatten! insert keep_if map! pop push reject! replace reverse! rotate! select! shift shuffle! slice! sort! sort_by! uniq! unshift)
|
182
182
|
|
183
183
|
# methods (well, method) that returns a modified copy and doesn't need any handling of block variable(s)
|
@@ -193,7 +193,7 @@ module JSI
|
|
193
193
|
end
|
194
194
|
safe_modified_copy_methods.each do |method_name|
|
195
195
|
define_method(method_name) do |*a, &b|
|
196
|
-
|
196
|
+
jsi_modified_copy do |object_to_modify|
|
197
197
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
198
198
|
responsive_object.public_send(method_name, *a, &b)
|
199
199
|
end
|
@@ -201,33 +201,48 @@ module JSI
|
|
201
201
|
end
|
202
202
|
safe_el_block_methods.each do |method_name|
|
203
203
|
define_method(method_name) do |*a, &b|
|
204
|
-
|
204
|
+
jsi_modified_copy do |object_to_modify|
|
205
205
|
i = 0
|
206
206
|
responsive_object = object_to_modify.respond_to?(method_name) ? object_to_modify : object_to_modify.to_ary
|
207
|
-
responsive_object.public_send(method_name
|
208
|
-
b.call(self[i]).tap { i += 1 }
|
207
|
+
responsive_object.public_send(method_name) do |_e|
|
208
|
+
b.call(self[i, *a]).tap { i += 1 }
|
209
209
|
end
|
210
210
|
end
|
211
211
|
end
|
212
212
|
end
|
213
213
|
|
214
|
-
#
|
215
|
-
|
214
|
+
# see [Array#assoc](https://ruby-doc.org/core/Array.html#method-i-assoc)
|
215
|
+
def assoc(obj)
|
216
|
+
# note: assoc implemented here (instead of delegated) due to inconsistencies in whether
|
217
|
+
# other implementations expect each element to be an Array or to respond to #to_ary
|
218
|
+
detect { |e| e.respond_to?(:to_ary) and e[0] == obj }
|
219
|
+
end
|
220
|
+
|
221
|
+
# see [Array#rassoc](https://ruby-doc.org/core/Array.html#method-i-rassoc)
|
222
|
+
def rassoc(obj)
|
223
|
+
# note: rassoc implemented here (instead of delegated) due to inconsistencies in whether
|
224
|
+
# other implementations expect each element to be an Array or to respond to #to_ary
|
225
|
+
detect { |e| e.respond_to?(:to_ary) and e[1] == obj }
|
226
|
+
end
|
227
|
+
|
228
|
+
# basically the same #inspect as Array, but has the class name and, if responsive,
|
229
|
+
# self's #jsi_object_group_text
|
230
|
+
# @return [String]
|
216
231
|
def inspect
|
217
|
-
object_group_str = (respond_to?(:
|
218
|
-
"\#[<#{object_group_str}>#{
|
232
|
+
object_group_str = (respond_to?(:jsi_object_group_text) ? jsi_object_group_text : [self.class]).join(' ')
|
233
|
+
"\#[<#{object_group_str}>#{self.map { |e| ' ' + e.inspect }.join(',')}]"
|
219
234
|
end
|
220
235
|
|
221
236
|
alias_method :to_s, :inspect
|
222
237
|
|
223
|
-
# pretty-prints a representation this
|
238
|
+
# pretty-prints a representation of this arraylike to the given printer
|
224
239
|
# @return [void]
|
225
240
|
def pretty_print(q)
|
226
|
-
object_group_str = (respond_to?(:
|
241
|
+
object_group_str = (respond_to?(:jsi_object_group_text) ? jsi_object_group_text : [self.class]).join(' ')
|
227
242
|
q.text "\#[<#{object_group_str}>"
|
228
243
|
q.group_sub {
|
229
244
|
q.nest(2) {
|
230
|
-
q.breakable(
|
245
|
+
q.breakable(empty? ? '' : ' ')
|
231
246
|
q.seplist(self, nil, :each) { |e|
|
232
247
|
q.pp e
|
233
248
|
}
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSI
|
4
|
+
module Util
|
5
|
+
# like a Struct, but stores all the attributes in one @attributes Hash, instead of individual instance
|
6
|
+
# variables for each attribute.
|
7
|
+
# this tends to be easier to work with and more flexible. keys which are symbols are converted to strings.
|
8
|
+
class AttrStruct
|
9
|
+
class AttrStructError < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class UndefinedAttributeKey < AttrStructError
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# creates a AttrStruct subclass with the given attribute keys.
|
17
|
+
# @param attribute_keys [Enumerable<String, Symbol>]
|
18
|
+
def [](*attribute_keys)
|
19
|
+
unless self == AttrStruct
|
20
|
+
# :nocov:
|
21
|
+
raise(NotImplementedError, "AttrStruct multiple inheritance not supported")
|
22
|
+
# :nocov:
|
23
|
+
end
|
24
|
+
|
25
|
+
bad = attribute_keys.reject { |key| key.respond_to?(:to_str) || key.is_a?(Symbol) }
|
26
|
+
unless bad.empty?
|
27
|
+
raise ArgumentError, "attribute keys must be String or Symbol; got keys: #{bad.map(&:inspect).join(', ')}"
|
28
|
+
end
|
29
|
+
attribute_keys = attribute_keys.map { |key| key.is_a?(Symbol) ? key.to_s : key }
|
30
|
+
|
31
|
+
Class.new(AttrStruct).tap do |klass|
|
32
|
+
klass.define_singleton_method(:attribute_keys) { attribute_keys }
|
33
|
+
klass.send(:define_method, :attribute_keys) { attribute_keys }
|
34
|
+
attribute_keys.each do |attribute_key|
|
35
|
+
# reader
|
36
|
+
klass.send(:define_method, attribute_key) do
|
37
|
+
@attributes[attribute_key]
|
38
|
+
end
|
39
|
+
|
40
|
+
# writer
|
41
|
+
klass.send(:define_method, "#{attribute_key}=") do |value|
|
42
|
+
@attributes[attribute_key] = value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(attributes = {})
|
50
|
+
unless attributes.respond_to?(:to_hash)
|
51
|
+
raise(TypeError, "expected attributes to be a Hash; got: #{attributes.inspect}")
|
52
|
+
end
|
53
|
+
attributes = attributes.map { |k, v| {k.is_a?(Symbol) ? k.to_s : k => v} }.inject({}, &:update)
|
54
|
+
bad = attributes.keys.reject { |k| self.attribute_keys.include?(k) }
|
55
|
+
unless bad.empty?
|
56
|
+
raise UndefinedAttributeKey, "undefined attribute keys: #{bad.map(&:inspect).join(', ')}"
|
57
|
+
end
|
58
|
+
@attributes = attributes
|
59
|
+
end
|
60
|
+
|
61
|
+
def [](key)
|
62
|
+
key = key.to_s if key.is_a?(Symbol)
|
63
|
+
@attributes[key]
|
64
|
+
end
|
65
|
+
|
66
|
+
def []=(key, value)
|
67
|
+
key = key.to_s if key.is_a?(Symbol)
|
68
|
+
unless self.attribute_keys.include?(key)
|
69
|
+
raise UndefinedAttributeKey, "undefined attribute key: #{key.inspect}"
|
70
|
+
end
|
71
|
+
@attributes[key] = value
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [String]
|
75
|
+
def inspect
|
76
|
+
"\#<#{self.class.name}#{@attributes.empty? ? '' : ' '}#{@attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
|
77
|
+
end
|
78
|
+
|
79
|
+
# pretty-prints a representation of self to the given printer
|
80
|
+
# @return [void]
|
81
|
+
def pretty_print(q)
|
82
|
+
q.text '#<'
|
83
|
+
q.text self.class.name
|
84
|
+
q.group_sub {
|
85
|
+
q.nest(2) {
|
86
|
+
q.breakable(@attributes.empty? ? '' : ' ')
|
87
|
+
q.seplist(@attributes, nil, :each_pair) { |k, v|
|
88
|
+
q.group {
|
89
|
+
q.text k
|
90
|
+
q.text ': '
|
91
|
+
q.pp v
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
q.breakable ''
|
97
|
+
q.text '>'
|
98
|
+
end
|
99
|
+
|
100
|
+
include FingerprintHash
|
101
|
+
def jsi_fingerprint
|
102
|
+
{class: self.class, attributes: @attributes}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/jsi/util.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# JSI::Util classes, modules, constants, and methods are
|
5
|
-
#
|
4
|
+
# JSI::Util classes, modules, constants, and methods are internal, and will be added and removed without warning.
|
5
|
+
#
|
6
|
+
# @api private
|
6
7
|
module Util
|
7
|
-
|
8
|
-
NOOP = -> (*_) { }
|
8
|
+
autoload :AttrStruct, 'jsi/util/attr_struct'
|
9
9
|
|
10
|
-
#
|
10
|
+
# a hash copied from the given hashlike, in which any symbol keys are
|
11
11
|
# converted to strings. behavior on collisions is undefined (but in the
|
12
12
|
# future could take a block like
|
13
13
|
# ActiveSupport::HashWithIndifferentAccess#update)
|
@@ -54,24 +54,63 @@ module JSI
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
|
+
# ensures the given param becomes a frozen Set of Modules.
|
58
|
+
# returns the param if it is already that, otherwise initializes and freezes such a Set.
|
59
|
+
#
|
60
|
+
# @param modules [Set, Enumerable] the object to ensure becomes a frozen Set of Modules
|
61
|
+
# @return [Set] frozen Set containing the given modules
|
62
|
+
# @raise [ArgumentError] when the modules param is not an Enumerable
|
63
|
+
# @raise [Schema::NotASchemaError] when the modules param contains objects which are not Schemas
|
64
|
+
def ensure_module_set(modules)
|
65
|
+
if modules.is_a?(Set) && modules.frozen?
|
66
|
+
set = modules
|
67
|
+
else
|
68
|
+
set = Set.new(modules).freeze
|
69
|
+
end
|
70
|
+
not_modules = set.reject { |s| s.is_a?(Module) }
|
71
|
+
if !not_modules.empty?
|
72
|
+
raise(TypeError, [
|
73
|
+
"ensure_module_set given non-Module objects:",
|
74
|
+
*not_modules.map { |ns| ns.pretty_inspect.chomp },
|
75
|
+
].join("\n"))
|
76
|
+
end
|
77
|
+
|
78
|
+
set
|
79
|
+
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
|
+
|
57
93
|
# this is the Y-combinator, which allows anonymous recursive functions. for a simple example,
|
58
94
|
# to define a recursive function to return the length of an array:
|
59
95
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
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
|
63
102
|
#
|
64
|
-
# see https://
|
65
|
-
# and chapter 9 of the little schemer, available as the sample chapter at
|
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
|
66
106
|
def ycomb
|
67
107
|
proc { |f| f.call(f) }.call(proc { |f| yield proc { |*x| f.call(f).call(*x) } })
|
68
108
|
end
|
69
|
-
module_function :ycomb
|
70
109
|
|
71
110
|
module FingerprintHash
|
72
111
|
# overrides BasicObject#==
|
73
112
|
def ==(other)
|
74
|
-
|
113
|
+
__id__ == other.__id__ || (other.respond_to?(:jsi_fingerprint) && other.jsi_fingerprint == self.jsi_fingerprint)
|
75
114
|
end
|
76
115
|
|
77
116
|
alias_method :eql?, :==
|
@@ -82,26 +121,103 @@ module JSI
|
|
82
121
|
end
|
83
122
|
end
|
84
123
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
124
|
+
class MemoMap
|
125
|
+
Result = Util::AttrStruct[*%w(
|
126
|
+
value
|
127
|
+
inputs
|
128
|
+
inputs_hash
|
129
|
+
)]
|
130
|
+
|
131
|
+
class Result
|
92
132
|
end
|
93
133
|
|
94
|
-
def
|
95
|
-
@
|
96
|
-
|
97
|
-
|
98
|
-
|
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
|
99
160
|
else
|
100
|
-
@
|
161
|
+
value = @block.call(*inputs)
|
162
|
+
@results[key] = Result.new(value: value, inputs: inputs, inputs_hash: inputs_hash)
|
163
|
+
value
|
101
164
|
end
|
102
165
|
end
|
103
166
|
end
|
104
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
|
105
221
|
end
|
106
222
|
public
|
107
223
|
extend Util
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSI
|
4
|
+
module Validation
|
5
|
+
Error = Util::AttrStruct[*%w(
|
6
|
+
message
|
7
|
+
keyword
|
8
|
+
schema
|
9
|
+
instance_ptr
|
10
|
+
instance_document
|
11
|
+
)]
|
12
|
+
|
13
|
+
# a validation error of a schema instance against a schema
|
14
|
+
#
|
15
|
+
# @!attribute message
|
16
|
+
# a message describing the error
|
17
|
+
# @return [String]
|
18
|
+
# @!attribute keyword
|
19
|
+
# the keyword of the schema which failed to validate.
|
20
|
+
# this may be absent if the error is not from a schema keyword (i.e, `false` schema).
|
21
|
+
# @return [String]
|
22
|
+
# @!attribute schema
|
23
|
+
# the schema against which the instance failed to validate
|
24
|
+
# @return [JSI::Schema]
|
25
|
+
# @!attribute instance_ptr
|
26
|
+
# pointer to the instance in instance_document
|
27
|
+
# @return [JSI::Ptr]
|
28
|
+
# @!attribute instance_document
|
29
|
+
# document containing the instance at instance_ptr
|
30
|
+
# @return [Object]
|
31
|
+
class Error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|