jsi 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/LICENSE.md +1 -1
  4. data/README.md +11 -6
  5. data/jsi.gemspec +30 -0
  6. data/lib/jsi/base/node.rb +183 -0
  7. data/lib/jsi/base.rb +135 -161
  8. data/lib/jsi/jsi_coder.rb +3 -3
  9. data/lib/jsi/metaschema.rb +0 -1
  10. data/lib/jsi/metaschema_node/bootstrap_schema.rb +9 -8
  11. data/lib/jsi/metaschema_node.rb +48 -51
  12. data/lib/jsi/ptr.rb +28 -17
  13. data/lib/jsi/schema/application/child_application/contains.rb +11 -2
  14. data/lib/jsi/schema/application/child_application/items.rb +3 -3
  15. data/lib/jsi/schema/application/child_application/properties.rb +3 -3
  16. data/lib/jsi/schema/application/child_application.rb +1 -3
  17. data/lib/jsi/schema/application/inplace_application/dependencies.rb +1 -1
  18. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +3 -3
  19. data/lib/jsi/schema/application/inplace_application/ref.rb +1 -1
  20. data/lib/jsi/schema/application/inplace_application/someof.rb +26 -11
  21. data/lib/jsi/schema/application/inplace_application.rb +1 -6
  22. data/lib/jsi/schema/ref.rb +3 -2
  23. data/lib/jsi/schema/schema_ancestor_node.rb +11 -17
  24. data/lib/jsi/schema/validation/array.rb +3 -3
  25. data/lib/jsi/schema/validation/const.rb +1 -1
  26. data/lib/jsi/schema/validation/contains.rb +1 -1
  27. data/lib/jsi/schema/validation/dependencies.rb +1 -1
  28. data/lib/jsi/schema/validation/draft04/minmax.rb +6 -6
  29. data/lib/jsi/schema/validation/enum.rb +1 -1
  30. data/lib/jsi/schema/validation/ifthenelse.rb +5 -5
  31. data/lib/jsi/schema/validation/items.rb +4 -4
  32. data/lib/jsi/schema/validation/not.rb +1 -1
  33. data/lib/jsi/schema/validation/numeric.rb +5 -5
  34. data/lib/jsi/schema/validation/object.rb +2 -2
  35. data/lib/jsi/schema/validation/pattern.rb +1 -1
  36. data/lib/jsi/schema/validation/properties.rb +3 -3
  37. data/lib/jsi/schema/validation/property_names.rb +1 -1
  38. data/lib/jsi/schema/validation/ref.rb +1 -1
  39. data/lib/jsi/schema/validation/required.rb +1 -1
  40. data/lib/jsi/schema/validation/someof.rb +3 -3
  41. data/lib/jsi/schema/validation/string.rb +2 -2
  42. data/lib/jsi/schema/validation/type.rb +1 -1
  43. data/lib/jsi/schema/validation.rb +1 -1
  44. data/lib/jsi/schema.rb +91 -85
  45. data/lib/jsi/schema_classes.rb +70 -45
  46. data/lib/jsi/schema_registry.rb +15 -5
  47. data/lib/jsi/schema_set.rb +42 -2
  48. data/lib/jsi/simple_wrap.rb +23 -4
  49. data/lib/jsi/util/{attr_struct.rb → private/attr_struct.rb} +40 -19
  50. data/lib/jsi/util/private.rb +204 -0
  51. data/lib/jsi/{typelike_modules.rb → util/typelike.rb} +56 -82
  52. data/lib/jsi/util.rb +68 -148
  53. data/lib/jsi/version.rb +1 -1
  54. data/lib/jsi.rb +1 -17
  55. data/lib/schemas/json-schema.org/draft-04/schema.rb +3 -1
  56. data/lib/schemas/json-schema.org/draft-06/schema.rb +3 -1
  57. data/lib/schemas/json-schema.org/draft-07/schema.rb +3 -1
  58. metadata +11 -9
  59. data/lib/jsi/pathed_node.rb +0 -116
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0e03bfff724257e11f9001d2845666d25f317b3761d5b62dc39816ad3fa3c28e
4
- data.tar.gz: d315e1424f2bd8eef67a2ad562c7668b6b8582b9bda25c8ba74cc892b1a9b1e4
3
+ metadata.gz: '0659cc34b129717ffb69089f1d3a25ba3c3ec5364d49a6c0cc93dd5979ac7638'
4
+ data.tar.gz: 5b0298a9843a61e6f5693d46dd6d86021d53767638e1f3f84e23fff4b167276e
5
5
  SHA512:
6
- metadata.gz: e62b9bf206486a77a3d1202388ddcd0fb29d326abf5a0b19fd6e74454ae05a494e4f88dcddaa42c7fc0b8b071877a406f213cde4589b21383778d9c713aa91b7
7
- data.tar.gz: a07b6d591b0cd8bfe28d5923018931f4ee8fc3dba393b240dbefd74a26e813e0a759ecab482be4da7a7e2fa3d4c74a0542c98fcef35364a1a07684e0dc24d2f0
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 Open Source Software licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
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
- [![Build Status](https://travis-ci.org/notEthan/jsi.svg?branch=master)](https://travis-ci.org/notEthan/jsi)
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
- Note the use of `super` - you can call to accessors defined by JSI and make your accessors act as wrappers. You can alternatively use `[]` and `[]=` with the same effect.
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 JSI schema. Here's an example, supposing a `users` table with a JSON column `contact_info`:
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