jsi 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/LICENSE.md +1 -1
- data/README.md +11 -6
- data/jsi.gemspec +30 -0
- data/lib/jsi/base/node.rb +183 -0
- data/lib/jsi/base.rb +135 -161
- data/lib/jsi/jsi_coder.rb +3 -3
- data/lib/jsi/metaschema.rb +0 -1
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +9 -8
- data/lib/jsi/metaschema_node.rb +48 -51
- data/lib/jsi/ptr.rb +28 -17
- data/lib/jsi/schema/application/child_application/contains.rb +11 -2
- data/lib/jsi/schema/application/child_application/items.rb +3 -3
- data/lib/jsi/schema/application/child_application/properties.rb +3 -3
- data/lib/jsi/schema/application/child_application.rb +1 -3
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
- data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
- data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
- data/lib/jsi/schema/application/inplace_application.rb +1 -6
- data/lib/jsi/schema/ref.rb +3 -2
- data/lib/jsi/schema/schema_ancestor_node.rb +11 -17
- data/lib/jsi/schema/validation/array.rb +3 -3
- data/lib/jsi/schema/validation/const.rb +1 -1
- data/lib/jsi/schema/validation/contains.rb +1 -1
- data/lib/jsi/schema/validation/dependencies.rb +1 -1
- data/lib/jsi/schema/validation/draft04/minmax.rb +6 -6
- data/lib/jsi/schema/validation/enum.rb +1 -1
- data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
- data/lib/jsi/schema/validation/items.rb +4 -4
- data/lib/jsi/schema/validation/not.rb +1 -1
- data/lib/jsi/schema/validation/numeric.rb +5 -5
- data/lib/jsi/schema/validation/object.rb +2 -2
- data/lib/jsi/schema/validation/pattern.rb +1 -1
- data/lib/jsi/schema/validation/properties.rb +3 -3
- data/lib/jsi/schema/validation/property_names.rb +1 -1
- data/lib/jsi/schema/validation/ref.rb +1 -1
- data/lib/jsi/schema/validation/required.rb +1 -1
- data/lib/jsi/schema/validation/someof.rb +3 -3
- data/lib/jsi/schema/validation/string.rb +2 -2
- data/lib/jsi/schema/validation/type.rb +1 -1
- data/lib/jsi/schema/validation.rb +1 -1
- data/lib/jsi/schema.rb +91 -85
- data/lib/jsi/schema_classes.rb +70 -45
- data/lib/jsi/schema_registry.rb +15 -5
- data/lib/jsi/schema_set.rb +42 -2
- data/lib/jsi/simple_wrap.rb +23 -4
- data/lib/jsi/util/{attr_struct.rb → private/attr_struct.rb} +40 -19
- data/lib/jsi/util/private.rb +204 -0
- data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +56 -82
- data/lib/jsi/util.rb +68 -148
- data/lib/jsi/version.rb +1 -1
- data/lib/jsi.rb +1 -17
- data/lib/schemas/json-schema.org/draft-04/schema.rb +3 -1
- data/lib/schemas/json-schema.org/draft-06/schema.rb +3 -1
- data/lib/schemas/json-schema.org/draft-07/schema.rb +3 -1
- metadata +11 -9
- data/lib/jsi/pathed_node.rb +0 -116
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0659cc34b129717ffb69089f1d3a25ba3c3ec5364d49a6c0cc93dd5979ac7638'
|
4
|
+
data.tar.gz: 5b0298a9843a61e6f5693d46dd6d86021d53767638e1f3f84e23fff4b167276e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8b68dcbb479756b8fa58a43f7a8e83accda02a8b0aa87617dabb6623a4b1a8aa05f94cb03de601cf7bf88c943ce62e42963b533ef125729847159e3b775f32c2
|
7
|
+
data.tar.gz: 49997a70900b9d250e4888bcf3e5470e68b116c3a558651db9e5551f73027ea4abd18b0585d23f5201b15e8d80f1d72b46488d1870ac530d40368c7577d3b048
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
# v0.7.0
|
2
|
+
|
3
|
+
- JSI::Base instances include Array/Hash-like modules on subclasses rather than extending each instance; are only Enumerable when appropriate instead of always
|
4
|
+
- PathedHashNode -> Base::HashNode, PathedArrayNode -> Base::ArrayNode, PathedNode merged with Base
|
5
|
+
- change application of conditional schemas to instances which do not validate, always apply them
|
6
|
+
- fix nomenclature: child is immediately below parent; descendent is anywhere at/below an ancestor
|
7
|
+
- deprecate previous misnamed methods
|
8
|
+
- Base#jsi_descendent_node, Base#jsi_ancestor_nodes
|
9
|
+
- add Schema#describes_schema!, deprecate Schema#jsi_schema_instance_modules
|
10
|
+
- Schema#keyword?
|
11
|
+
- Base#jmespath_search
|
12
|
+
- MetaschemaNode keeps its jsi_root_node (reducing an enormous number of unnecessary instantiations of MetaschemaNode)
|
13
|
+
- /metaschema_instance_modules/schema_implementation_modules/
|
14
|
+
- separate JSI::Util (public) and JSI::Util::Private
|
15
|
+
- support ruby 3
|
16
|
+
- Schema.default_metaschema is nil unless set by the application
|
17
|
+
- deprecate JSI::Typelike module, merged with Util; Arraylike -> Util::Arraylike, Hashlike -> Util::Hashlike
|
18
|
+
|
1
19
|
# v0.6.0
|
2
20
|
|
3
21
|
- initial validation; remove gem `json-schema` dependency
|
data/LICENSE.md
CHANGED
@@ -2,7 +2,7 @@ Copright © [Ethan](https://github.com/notEthan/) <ethan.jsi@unth.net>
|
|
2
2
|
|
3
3
|
[<img align="right" src="https://github.com/notEthan/jsi/raw/master/resources/icons/AGPL-3.0.png">](https://www.gnu.org/licenses/agpl-3.0.html)
|
4
4
|
|
5
|
-
JSI is
|
5
|
+
JSI is licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
|
6
6
|
|
7
7
|
GNU Affero General Public License
|
8
8
|
=================================
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# JSI: JSON Schema Instantiation
|
2
2
|
|
3
|
-
|
3
|
+
![Test CI Status](https://github.com/notEthan/jsi/actions/workflows/test.yml/badge.svg?branch=stable)
|
4
4
|
[![Coverage Status](https://coveralls.io/repos/github/notEthan/jsi/badge.svg)](https://coveralls.io/github/notEthan/jsi)
|
5
5
|
|
6
6
|
JSI offers an Object-Oriented representation for JSON data using JSON Schemas. Given your JSON Schemas, JSI constructs Ruby modules and classes which are used to instantiate your JSON data. These modules let you use JSON with all the niceties of OOP such as property accessors and application-defined instance methods.
|
@@ -46,6 +46,8 @@ We name the module that JSI will use when instantiating a contact. Named modules
|
|
46
46
|
Contact = contact_schema.jsi_schema_module
|
47
47
|
```
|
48
48
|
|
49
|
+
Note: it is more concise to instantiate the schema module with the shortcut {JSI.new_schema_module}, i.e. `Contact = JSI.new_schema_module(...)`. This example includes the intermediate step to help show all that is happening.
|
50
|
+
|
49
51
|
To instantiate the schema, we need some JSON data (expressed here as YAML)
|
50
52
|
|
51
53
|
```yaml
|
@@ -173,17 +175,20 @@ module Contact
|
|
173
175
|
end
|
174
176
|
end
|
175
177
|
|
178
|
+
bill.phone_numbers
|
179
|
+
# => ["555"]
|
180
|
+
|
176
181
|
bill.name
|
177
182
|
# => "bill esq."
|
178
183
|
bill.name = 'rob esq.'
|
179
184
|
# => "rob esq."
|
180
185
|
bill['name']
|
181
186
|
# => "rob"
|
182
|
-
bill.phone_numbers
|
183
|
-
# => ["555"]
|
184
187
|
```
|
185
188
|
|
186
|
-
|
189
|
+
`#phone_numbers` is a new method returning each number in the `phone` array - pretty straightforward.
|
190
|
+
|
191
|
+
For `#name` and `#name=`, we're overriding existing accessor methods. note the use of `super` - this invokes the accessor methods defined by JSI which these override. You could alternatively use `self['name']` and `self['name']=` in these methods, with the same effect as `super`.
|
187
192
|
|
188
193
|
Working with subschemas is just about as easy as with root schemas.
|
189
194
|
|
@@ -269,7 +274,7 @@ Let's say you're sticking to JSON types in the database - you have to do so if y
|
|
269
274
|
|
270
275
|
But if your database contains JSON, then your deserialized objects in ruby are likewise Hash / Array / basic types. You have to use subscripts instead of accessors, and you don't have any way to add methods to your data types.
|
271
276
|
|
272
|
-
JSI gives you the best of both with JSICoder. This coder dumps objects which are simple JSON types, and loads instances of a specified
|
277
|
+
JSI gives you the best of both with {JSI::JSICoder}. This coder dumps objects which are simple JSON types, and loads instances of a specified JSON Schema. Here's an example, supposing a `users` table with a JSON column `contact_info` to be instantiated using the `Contact` schema module defined in the Example section above:
|
273
278
|
|
274
279
|
```ruby
|
275
280
|
class User < ActiveRecord::Base
|
@@ -277,7 +282,7 @@ class User < ActiveRecord::Base
|
|
277
282
|
end
|
278
283
|
```
|
279
284
|
|
280
|
-
Now `user.contact_info` will be instantiated as a Contact JSI instance, from the JSON type in the database, with Contact's accessors, validations, and user-defined instance methods.
|
285
|
+
Now `user.contact_info` will be instantiated as a `Contact` JSI instance, from the JSON type in the database, with Contact's accessors, validations, and user-defined instance methods.
|
281
286
|
|
282
287
|
See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
|
283
288
|
|
data/jsi.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
lib = File.expand_path("lib", __dir__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "jsi/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "jsi"
|
7
|
+
spec.version = JSI::VERSION
|
8
|
+
spec.authors = ["Ethan"]
|
9
|
+
spec.email = ["ethan.jsi@unth.net"]
|
10
|
+
|
11
|
+
spec.summary = "JSI: JSON Schema Instantiation"
|
12
|
+
spec.description = "JSI offers an Object-Oriented representation for JSON data using JSON Schemas"
|
13
|
+
spec.homepage = "https://github.com/notEthan/jsi"
|
14
|
+
spec.license = "AGPL-3.0"
|
15
|
+
|
16
|
+
spec.files = [
|
17
|
+
'LICENSE.md',
|
18
|
+
'CHANGELOG.md',
|
19
|
+
'README.md',
|
20
|
+
'readme.rb',
|
21
|
+
'.yardopts',
|
22
|
+
'jsi.gemspec',
|
23
|
+
*Dir['lib/**/*'],
|
24
|
+
*Dir['\\{resources\\}/schemas/**/*'],
|
25
|
+
].reject { |f| File.lstat(f).ftype == 'directory' }
|
26
|
+
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_dependency "addressable", '~> 2.3'
|
30
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module JSI
|
4
|
+
module Base::Enumerable
|
5
|
+
include ::Enumerable
|
6
|
+
|
7
|
+
# an Array containing each item in this JSI.
|
8
|
+
#
|
9
|
+
# @param kw keyword arguments are passed to {Base#[]} - see its keyword params
|
10
|
+
# @return [Array]
|
11
|
+
def to_a(**kw)
|
12
|
+
# TODO remove eventually (keyword argument compatibility)
|
13
|
+
# discard when all supported ruby versions Enumerable#to_a delegate keywords to #each (3.0.1 breaks; 2.7.x warns)
|
14
|
+
# https://bugs.ruby-lang.org/issues/18289
|
15
|
+
ary = []
|
16
|
+
each(**kw) do |e|
|
17
|
+
ary << e
|
18
|
+
end
|
19
|
+
ary
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :entries, :to_a
|
23
|
+
|
24
|
+
# a jsonifiable representation of the node content
|
25
|
+
# @return [Object]
|
26
|
+
def as_json(*opt)
|
27
|
+
# include Enumerable (above) means, if ActiveSupport is loaded, its undesirable #as_json is included
|
28
|
+
# https://github.com/rails/rails/blob/v7.0.0/activesupport/lib/active_support/core_ext/object/json.rb#L139-L143
|
29
|
+
# although Base#as_json does clobber activesupport's, I want as_json defined correctly on the module too.
|
30
|
+
Typelike.as_json(jsi_node_content, *opt)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
|
35
|
+
# is a Hash (or responds to `#to_hash`)
|
36
|
+
module Base::HashNode
|
37
|
+
include Base::Enumerable
|
38
|
+
|
39
|
+
# yields each hash key and value of this node.
|
40
|
+
#
|
41
|
+
# each yielded key is a key of the instance hash, and each yielded value is the result of {Base#[]}.
|
42
|
+
#
|
43
|
+
# returns an Enumerator if no block is given.
|
44
|
+
#
|
45
|
+
# @param kw keyword arguments are passed to {Base#[]}
|
46
|
+
# @yield [Object, Object] each key and value of this hash node
|
47
|
+
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
48
|
+
def each(**kw, &block)
|
49
|
+
return to_enum(__method__, **kw) { jsi_node_content_hash_pubsend(:size) } unless block
|
50
|
+
if block.arity > 1
|
51
|
+
jsi_node_content_hash_pubsend(:each_key) { |k| yield k, self[k, **kw] }
|
52
|
+
else
|
53
|
+
jsi_node_content_hash_pubsend(:each_key) { |k| yield [k, self[k, **kw]] }
|
54
|
+
end
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
# a hash in which each key is a key of the instance hash and each value is the result of {Base#[]}
|
59
|
+
# @param kw keyword arguments are passed to {Base#[]}
|
60
|
+
# @return [Hash]
|
61
|
+
def to_hash(**kw)
|
62
|
+
{}.tap { |h| jsi_node_content_hash_pubsend(:each_key) { |k| h[k] = self[k, **kw] } }
|
63
|
+
end
|
64
|
+
|
65
|
+
include Util::Hashlike
|
66
|
+
|
67
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
68
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
|
69
|
+
# @param method_name [String, Symbol]
|
70
|
+
# @param a positional arguments are passed to the invocation of method_name
|
71
|
+
# @param b block is passed to the invocation of method_name
|
72
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
|
73
|
+
def jsi_node_content_hash_pubsend(method_name, *a, &b)
|
74
|
+
if jsi_node_content.respond_to?(method_name)
|
75
|
+
jsi_node_content.public_send(method_name, *a, &b)
|
76
|
+
else
|
77
|
+
jsi_node_content.to_hash.public_send(method_name, *a, &b)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_hash
|
82
|
+
# @param method_name [String, Symbol]
|
83
|
+
# @param a positional arguments are passed to the invocation of method_name
|
84
|
+
# @param kw keyword arguments are passed to the invocation of method_name
|
85
|
+
# @param b block is passed to the invocation of method_name
|
86
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_hash
|
87
|
+
def jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
|
88
|
+
if jsi_node_content.respond_to?(method_name)
|
89
|
+
jsi_node_content.public_send(method_name, *a, **kw, &b)
|
90
|
+
else
|
91
|
+
jsi_node_content.to_hash.public_send(method_name, *a, **kw, &b)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
|
97
|
+
SAFE_KEY_ONLY_METHODS.each do |method_name|
|
98
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
99
|
+
define_method(method_name) do |*a, &b|
|
100
|
+
jsi_node_content_hash_pubsend(method_name, *a, &b)
|
101
|
+
end
|
102
|
+
else
|
103
|
+
define_method(method_name) do |*a, **kw, &b|
|
104
|
+
jsi_node_content_hash_pubsend(method_name, *a, **kw, &b)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# module extending a {JSI::Base} object when its instance (its {Base#jsi_node_content})
|
111
|
+
# is an Array (or responds to `#to_ary`)
|
112
|
+
module Base::ArrayNode
|
113
|
+
include Base::Enumerable
|
114
|
+
|
115
|
+
# yields each array element of this node.
|
116
|
+
#
|
117
|
+
# each yielded element is the result of {Base#[]} for each index of the instance array.
|
118
|
+
#
|
119
|
+
# returns an Enumerator if no block is given.
|
120
|
+
#
|
121
|
+
# @param kw keyword arguments are passed to {Base#[]}
|
122
|
+
# @yield [Object] each element of this array node
|
123
|
+
# @return [self, Enumerator] an Enumerator if invoked without a block; otherwise self
|
124
|
+
def each(**kw, &block)
|
125
|
+
return to_enum(__method__, **kw) { jsi_node_content_ary_pubsend(:size) } unless block
|
126
|
+
jsi_node_content_ary_pubsend(:each_index) { |i| yield(self[i, **kw]) }
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
# an array, the same size as the instance array, in which the element at each index is the
|
131
|
+
# result of {Base#[]}.
|
132
|
+
# @param kw keyword arguments are passed to {Base#[]}
|
133
|
+
# @return [Array]
|
134
|
+
def to_ary(**kw)
|
135
|
+
to_a(**kw)
|
136
|
+
end
|
137
|
+
|
138
|
+
include Util::Arraylike
|
139
|
+
|
140
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
141
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
|
142
|
+
# @param method_name [String, Symbol]
|
143
|
+
# @param a positional arguments are passed to the invocation of method_name
|
144
|
+
# @param b block is passed to the invocation of method_name
|
145
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
|
146
|
+
def jsi_node_content_ary_pubsend(method_name, *a, &b)
|
147
|
+
if jsi_node_content.respond_to?(method_name)
|
148
|
+
jsi_node_content.public_send(method_name, *a, &b)
|
149
|
+
else
|
150
|
+
jsi_node_content.to_ary.public_send(method_name, *a, &b)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
else
|
154
|
+
# invokes the method with the given name on the jsi_node_content (if defined) or its #to_ary
|
155
|
+
# @param method_name [String, Symbol]
|
156
|
+
# @param a positional arguments are passed to the invocation of method_name
|
157
|
+
# @param kw keyword arguments are passed to the invocation of method_name
|
158
|
+
# @param b block is passed to the invocation of method_name
|
159
|
+
# @return [Object] the result of calling method method_name on the jsi_node_content or its #to_ary
|
160
|
+
def jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
|
161
|
+
if jsi_node_content.respond_to?(method_name)
|
162
|
+
jsi_node_content.public_send(method_name, *a, **kw, &b)
|
163
|
+
else
|
164
|
+
jsi_node_content.to_ary.public_send(method_name, *a, **kw, &b)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
|
170
|
+
# we override these methods from Arraylike
|
171
|
+
SAFE_INDEX_ONLY_METHODS.each do |method_name|
|
172
|
+
if Util::LAST_ARGUMENT_AS_KEYWORD_PARAMETERS
|
173
|
+
define_method(method_name) do |*a, &b|
|
174
|
+
jsi_node_content_ary_pubsend(method_name, *a, &b)
|
175
|
+
end
|
176
|
+
else
|
177
|
+
define_method(method_name) do |*a, **kw, &b|
|
178
|
+
jsi_node_content_ary_pubsend(method_name, *a, **kw, &b)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|