jsi-dev 0.0.0.pre.kramdown

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +8 -0
  3. data/CHANGELOG.md +101 -0
  4. data/LICENSE.md +613 -0
  5. data/README.md +303 -0
  6. data/docs/glossary.md +281 -0
  7. data/jsi.gemspec +30 -0
  8. data/lib/jsi/base/node.rb +373 -0
  9. data/lib/jsi/base.rb +738 -0
  10. data/lib/jsi/jsi_coder.rb +92 -0
  11. data/lib/jsi/metaschema.rb +6 -0
  12. data/lib/jsi/metaschema_node/bootstrap_schema.rb +126 -0
  13. data/lib/jsi/metaschema_node.rb +262 -0
  14. data/lib/jsi/ptr.rb +314 -0
  15. data/lib/jsi/schema/application/child_application/contains.rb +25 -0
  16. data/lib/jsi/schema/application/child_application/draft04.rb +21 -0
  17. data/lib/jsi/schema/application/child_application/draft06.rb +28 -0
  18. data/lib/jsi/schema/application/child_application/draft07.rb +28 -0
  19. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  20. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  21. data/lib/jsi/schema/application/child_application.rb +13 -0
  22. data/lib/jsi/schema/application/draft04.rb +8 -0
  23. data/lib/jsi/schema/application/draft06.rb +8 -0
  24. data/lib/jsi/schema/application/draft07.rb +8 -0
  25. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  26. data/lib/jsi/schema/application/inplace_application/draft04.rb +25 -0
  27. data/lib/jsi/schema/application/inplace_application/draft06.rb +26 -0
  28. data/lib/jsi/schema/application/inplace_application/draft07.rb +32 -0
  29. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  30. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  31. data/lib/jsi/schema/application/inplace_application/someof.rb +44 -0
  32. data/lib/jsi/schema/application/inplace_application.rb +14 -0
  33. data/lib/jsi/schema/application.rb +12 -0
  34. data/lib/jsi/schema/draft04.rb +13 -0
  35. data/lib/jsi/schema/draft06.rb +13 -0
  36. data/lib/jsi/schema/draft07.rb +13 -0
  37. data/lib/jsi/schema/issue.rb +36 -0
  38. data/lib/jsi/schema/ref.rb +183 -0
  39. data/lib/jsi/schema/schema_ancestor_node.rb +122 -0
  40. data/lib/jsi/schema/validation/array.rb +69 -0
  41. data/lib/jsi/schema/validation/const.rb +20 -0
  42. data/lib/jsi/schema/validation/contains.rb +25 -0
  43. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  44. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  45. data/lib/jsi/schema/validation/draft04.rb +110 -0
  46. data/lib/jsi/schema/validation/draft06.rb +120 -0
  47. data/lib/jsi/schema/validation/draft07.rb +157 -0
  48. data/lib/jsi/schema/validation/enum.rb +25 -0
  49. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  50. data/lib/jsi/schema/validation/items.rb +54 -0
  51. data/lib/jsi/schema/validation/not.rb +20 -0
  52. data/lib/jsi/schema/validation/numeric.rb +121 -0
  53. data/lib/jsi/schema/validation/object.rb +45 -0
  54. data/lib/jsi/schema/validation/pattern.rb +34 -0
  55. data/lib/jsi/schema/validation/properties.rb +101 -0
  56. data/lib/jsi/schema/validation/property_names.rb +32 -0
  57. data/lib/jsi/schema/validation/ref.rb +40 -0
  58. data/lib/jsi/schema/validation/required.rb +27 -0
  59. data/lib/jsi/schema/validation/someof.rb +90 -0
  60. data/lib/jsi/schema/validation/string.rb +47 -0
  61. data/lib/jsi/schema/validation/type.rb +49 -0
  62. data/lib/jsi/schema/validation.rb +49 -0
  63. data/lib/jsi/schema.rb +792 -0
  64. data/lib/jsi/schema_classes.rb +357 -0
  65. data/lib/jsi/schema_registry.rb +190 -0
  66. data/lib/jsi/schema_set.rb +219 -0
  67. data/lib/jsi/simple_wrap.rb +26 -0
  68. data/lib/jsi/util/private/attr_struct.rb +130 -0
  69. data/lib/jsi/util/private/memo_map.rb +75 -0
  70. data/lib/jsi/util/private.rb +202 -0
  71. data/lib/jsi/util/typelike.rb +225 -0
  72. data/lib/jsi/util.rb +227 -0
  73. data/lib/jsi/validation/error.rb +34 -0
  74. data/lib/jsi/validation/result.rb +212 -0
  75. data/lib/jsi/validation.rb +15 -0
  76. data/lib/jsi/version.rb +5 -0
  77. data/lib/jsi.rb +105 -0
  78. data/lib/schemas/json-schema.org/draft-04/schema.rb +169 -0
  79. data/lib/schemas/json-schema.org/draft-06/schema.rb +171 -0
  80. data/lib/schemas/json-schema.org/draft-07/schema.rb +198 -0
  81. data/readme.rb +138 -0
  82. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  83. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  84. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  85. metadata +155 -0
data/README.md ADDED
@@ -0,0 +1,303 @@
1
+ # JSI: JSON Schema Instantiation
2
+
3
+ ![Test CI Status](https://github.com/notEthan/jsi/actions/workflows/test.yml/badge.svg?branch=stable)
4
+ [![Coverage Status](https://coveralls.io/repos/github/notEthan/jsi/badge.svg)](https://coveralls.io/github/notEthan/jsi)
5
+
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.
7
+
8
+ To learn more about JSON Schema see <https://json-schema.org/>.
9
+
10
+ JSI marries object-oriented programming with JSON Schemas by associating a module with each schema, and extending every instance described by a schema with that module. When an application adds methods to a schema module, those methods can be used on its instances.
11
+
12
+ A JSI instance aims to offer a fairly unobtrusive wrapper around its JSON data, which is usually a Hash (JSON Object) or Array described by one or more JSON Schemas. JSI instances have accessors for property names described by schemas, schema validation, and other nice things. Mostly though, you use a JSI as you would use its underlying data, calling the same methods (e.g. `#[]`, `#map`, `#repeated_permutation`) and passing it to anything that duck-types expecting `#to_ary` or `#to_hash`.
13
+
14
+ Note: The canonical location of this README is on [RubyDoc](http://rubydoc.info/gems/jsi/). When viewed on [Github](https://github.com/notEthan/jsi/), it may be inconsistent with the latest released gem, and Yardoc links will not work.
15
+
16
+ ## Example
17
+
18
+ Words are boring, let's code. Here's a schema in yaml:
19
+
20
+ ```yaml
21
+ $schema: "http://json-schema.org/draft-07/schema"
22
+ description: "A Contact"
23
+ type: "object"
24
+ properties:
25
+ name: {type: "string"}
26
+ phone:
27
+ type: "array"
28
+ items:
29
+ description: "A phone number"
30
+ type: "object"
31
+ properties:
32
+ location: {type: "string"}
33
+ number: {type: "string"}
34
+ ```
35
+
36
+ We pass that to {JSI.new_schema} which will instantiate a JSI Schema which represents it:
37
+
38
+ ```ruby
39
+ # this would usually load YAML or JSON; the schema object is inlined for copypastability.
40
+ contact_schema = JSI.new_schema({"$schema" => "http://json-schema.org/draft-07/schema", "description" => "A Contact", "type" => "object", "properties" => {"name" => {"type" => "string"}, "phone" => {"type" => "array", "items" => {"type" => "object", "properties" => {"location" => {"type" => "string"}, "number" => {"type" => "string"}}}}}})
41
+ ```
42
+
43
+ We name the module that JSI will use when instantiating a contact. Named modules are better to work with, and JSI will indicate the names of schema modules in its `#inspect` output.
44
+
45
+ ```ruby
46
+ Contact = contact_schema.jsi_schema_module
47
+ ```
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
+
51
+ To instantiate the schema, we need some JSON data (expressed here as YAML)
52
+
53
+ ```yaml
54
+ name: bill
55
+ phone:
56
+ - location: home
57
+ number: "555"
58
+ nickname: big b
59
+ ```
60
+
61
+ So, if we construct an instance like:
62
+
63
+ ```ruby
64
+ # this would usually load YAML or JSON; the schema instance is inlined for copypastability.
65
+ bill = Contact.new_jsi({"name" => "bill", "phone" => [{"location" => "home", "number" => "555"}], "nickname" => "big b"})
66
+ # => #{<JSI (Contact)>
67
+ # "name" => "bill",
68
+ # "phone" => #[<JSI (Contact.properties["phone"])>
69
+ # #{<JSI (Contact.properties["phone"].items)>
70
+ # "location" => "home",
71
+ # "number" => "555"
72
+ # }
73
+ # ],
74
+ # "nickname" => "big b"
75
+ # }
76
+ ```
77
+
78
+ Note that the hash keys are strings. JSI, being designed with JSON in mind, is geared toward string keys.
79
+
80
+ We get accessors for the Contact:
81
+
82
+ ```ruby
83
+ bill.name
84
+ # => "bill"
85
+ ```
86
+
87
+ but also nested accessors - `#phone` is an instance of its array-type schema, and each phone item is an instance of another object-type schema with `#location` and `#number` accessors:
88
+
89
+ ```ruby
90
+ bill.phone.map(&:location)
91
+ # => ["home"]
92
+ ```
93
+
94
+ We also get validations, as you'd expect given that's largely what json-schema exists to do:
95
+
96
+ ```ruby
97
+ bill.jsi_valid?
98
+ # => true
99
+ ```
100
+
101
+ ... and validations on the nested schema instances (`#phone` here), showing in this example validation failure on /phone/0/number:
102
+
103
+ ```ruby
104
+ bad = Contact.new_jsi({'phone' => [{'number' => [5, 5, 5]}]})
105
+ # => #{<JSI (Contact)>
106
+ # "phone" => #[<JSI (Contact.properties["phone"])>
107
+ # #{<JSI (Contact.properties["phone"].items)>
108
+ # "number" => #[<JSI (Contact.properties["phone"].items.properties["number"])> 5, 5, 5]
109
+ # }
110
+ # ]
111
+ # }
112
+ bad.phone.jsi_validate
113
+ # => #<JSI::Validation::FullResult
114
+ # @validation_errors=
115
+ # #<Set: {#<JSI::Validation::Error
116
+ # message: "instance type does not match `type` value",
117
+ # keyword: "type",
118
+ # schema: #{<JSI (JSI::JSONSchemaDraft07) Schema> "type" => "string"},
119
+ # instance_ptr: JSI::Ptr["phone", 0, "number"],
120
+ # instance_document: {"phone"=>[{"number"=>[5, 5, 5]}]}
121
+ # >,
122
+ # ...
123
+ # >
124
+ ```
125
+
126
+ Since the underlying instance is a ruby hash (json object), we can use it like a hash with `#[]` or, say, `#transform_values`:
127
+
128
+ ```ruby
129
+ # note that #size here is actually referring to multiple different methods;
130
+ # for name and nickname it is String#size but for phone it is Array#size.
131
+ bill.transform_values(&:size)
132
+ # => {"name" => 4, "phone" => 1, "nickname" => 5}
133
+
134
+ bill['nickname']
135
+ # => "big b"
136
+ ```
137
+
138
+ There's plenty more JSI has to offer, but this should give you a pretty good idea of basic usage.
139
+
140
+ ## Terminology and Concepts
141
+
142
+ - `JSI::Base` is the base class for each JSI schema class representing instances of JSON Schemas.
143
+ - a "JSI Schema" is a JSON Schema, instantiated as (usually) a JSI::Base described by a metaschema (see the sections on Metaschemas below). a JSI Schema is an instance of the module `JSI::Schema`.
144
+ - a "JSI Schema Module" is a module which represents one schema, dynamically created by that Schema. Instances of that schema are extended with its JSI schema module. applications may reopen these modules to add functionality to JSI instances described by a given schema.
145
+ - a "JSI schema class" is a subclass of `JSI::Base` representing one or more JSON schemas. Instances of such a class are described by all of the represented schemas. A JSI schema class includes the JSI schema module of each represented schema.
146
+ - "instance" is a term that is significantly overloaded in this space, so documentation will attempt to be clear what kind of instance is meant:
147
+ - a schema instance refers broadly to a data structure that is described by a JSON schema.
148
+ - a JSI instance (or just "a JSI") is a ruby object instantiating a JSI schema class (subclass of `JSI::Base`). This wraps the content of the schema instance (see `JSI::Base#jsi_instance`), and ties it to the schemas which describe the instance (`JSI::Base#jsi_schemas`).
149
+ - "schema" refers to either a parsed JSON schema (generally a ruby Hash) or a JSI schema.
150
+
151
+ ## Supported specification versions
152
+
153
+ JSI supports these JSON Schema specification versions:
154
+
155
+ | Version | `$schema` URI | JSI Schema Module |
156
+ | --- | --- | --- |
157
+ | Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::JSONSchemaDraft04} |
158
+ | Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::JSONSchemaDraft06} |
159
+ | Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::JSONSchemaDraft07} |
160
+
161
+ ## JSI and Object Oriented Programming
162
+
163
+ Instantiating your schema is a starting point. But, since the major point of object-oriented programming is applying methods to your objects, of course you want to be able to define your own methods. To do this we reopen the JSI module we defined. Referring back to the Example section above, we reopen the `Contact` module:
164
+
165
+ ```ruby
166
+ module Contact
167
+ def phone_numbers
168
+ phone.map(&:number)
169
+ end
170
+ def name
171
+ super + ' esq.'
172
+ end
173
+ def name=(name)
174
+ super(name.chomp(' esq.'))
175
+ end
176
+ end
177
+
178
+ bill.phone_numbers
179
+ # => ["555"]
180
+
181
+ bill.name
182
+ # => "bill esq."
183
+ bill.name = 'rob esq.'
184
+ # => "rob esq."
185
+ bill['name']
186
+ # => "rob"
187
+ ```
188
+
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`.
192
+
193
+ Working with subschemas is just about as easy as with root schemas.
194
+
195
+ You can subscript or use property accessors on a JSI schema module to refer to the schema modules of its subschemas, e.g.:
196
+
197
+ ```ruby
198
+ Contact.properties['phone'].items
199
+ # => Contact.properties["phone"].items (JSI Schema Module)
200
+ ```
201
+
202
+ Opening a subschema module with [`module_exec`](https://ruby-doc.org/core/Module.html#method-i-module_exec), you can add methods to instances of the subschema.
203
+
204
+ ```ruby
205
+ Contact.properties['phone'].items.module_exec do
206
+ def number_with_dashes
207
+ number.split(//).join('-')
208
+ end
209
+ end
210
+ bill.phone.first.number_with_dashes
211
+ # => "5-5-5"
212
+ ```
213
+
214
+ A recommended convention for naming subschemas is to define them in the namespace of the module of their
215
+ parent schema. The module can then be opened to add methods to the subschema's module.
216
+
217
+ ```ruby
218
+ module Contact
219
+ Phone = properties['phone'].items
220
+ module Phone
221
+ def number_with_dashes
222
+ number.split(//).join('-')
223
+ end
224
+ end
225
+ end
226
+ ```
227
+
228
+ However, that is only a convention, and a flat namespace works fine too.
229
+
230
+ ```ruby
231
+ ContactPhone = Contact.properties['phone'].items
232
+ module ContactPhone
233
+ def number_with_dashes
234
+ number.split(//).join('-')
235
+ end
236
+ end
237
+ ```
238
+
239
+ ### A note on Classes
240
+
241
+ The classes used to instantiate JSIs are dynamically generated subclasses of JSI::Base which include the JSI Schema Module of each schema describing the given instance. These are mostly intended to be ignored: applications aren't expected to instantiate these directly (rather, `#new_jsi` on a Schema or Schema Module is intended), and they are not intended for subclassing or method definition (applications should instead define methods on a schema's {JSI::Schema#jsi_schema_module}).
242
+
243
+ ## Registration
244
+
245
+ In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry (a {JSI::SchemaRegistry}) which associates URIs with schemas (or resources containing schemas). The default registry is accessible on {JSI.schema_registry}.
246
+
247
+ Schemas instantiated with `.new_schema`, and their subschemas, are by default registered with `JSI.schema_registry` if they are identified by an absolute URI. This can be controlled by params `register` and `schema_registry`.
248
+
249
+ Schemas can automatically be lazily loaded by registering a block which instantiates them with {JSI::SchemaRegistry#autoload_uri} (see its documentation).
250
+
251
+ ## Validation
252
+
253
+ JSI implements all required features, and many optional features, for validation according to supported JSON Schema specifications. To validate instances, see methods {JSI::Base#jsi_validate}, {JSI::Base#jsi_valid?}, {JSI::Schema#instance_validate}, {JSI::Schema#instance_valid?}.
254
+
255
+ The following optional features are not completely supported:
256
+
257
+ - The `format` keyword does not perform any validation.
258
+ - Regular expressions are interpreted by Ruby's Regexp class, whereas JSON Schema recommends interpreting these as ECMA 262 regular expressions. Certain expressions behave differently, particularly `^` and `$`.
259
+ - Keywords `contentMediaType` and `contentEncoding` do not perform validation.
260
+
261
+ ## Metaschemas
262
+
263
+ A metaschema is a schema which describes schemas. Likewise, a schema is an instance of a metaschema.
264
+
265
+ In JSI, a schema is generally a JSI::Base instance whose schemas include a metaschema.
266
+
267
+ A self-descriptive metaschema - most commonly one of the JSON schema draft metaschemas - is an object whose schemas include itself. This is instantiated in JSI as a JSI::MetaschemaNode, a special subclass of JSI::Base.
268
+
269
+ ## ActiveRecord serialization
270
+
271
+ A really excellent place to use JSI is when dealing with serialized columns in ActiveRecord.
272
+
273
+ Let's say you're sticking to JSON types in the database - you have to do so if you're using JSON columns, or JSON serialization, and if you have dealt with arbitrary yaml- or marshal-serialized objects in ruby, you have probably found that approach has its shortcomings when the implementation of your classes changes.
274
+
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.
276
+
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:
278
+
279
+ ```ruby
280
+ class User < ActiveRecord::Base
281
+ serialize :contact_info, JSI::JSICoder.new(Contact)
282
+ end
283
+ ```
284
+
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.
286
+
287
+ See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
288
+
289
+ ## Keying Hashes (JSON Objects)
290
+
291
+ Unlike Ruby, JSON only supports string keys. It is recommended to use strings as hash keys for all JSI instances, but JSI does not enforce this, nor does it do any key conversion. You may also use [ActiveSupport::HashWithIndifferentAccess](https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html) as the instance of a JSI in order to gain the benefits that offers over a plain hash. Note that activesupport is not a dependency of jsi and would be required separately for this.
292
+
293
+ ## Contributing
294
+
295
+ Issues and pull requests are welcome on GitHub at https://github.com/notEthan/jsi.
296
+
297
+ ## License
298
+
299
+ [<img align="right" src="https://github.com/notEthan/jsi/raw/v0.3.0/resources/icons/AGPL-3.0.png">](https://www.gnu.org/licenses/agpl-3.0.html)
300
+
301
+ JSI is licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
302
+
303
+ Unlike the MIT or BSD licenses more commonly used with Ruby gems, this license requires that if you modify JSI and propagate your changes, e.g. by including it in a web application, your modified version must be publicly available. The common path of forking on Github should satisfy this requirement.
data/docs/glossary.md ADDED
@@ -0,0 +1,281 @@
1
+ # Glossary
2
+
3
+
4
+ ## Background
5
+
6
+ The terminology JSI uses comes from a number of sources:
7
+
8
+ - [JSON Schema](https://json-schema.org/) and its [specifications](https://json-schema.org/specification-links.html)
9
+ - The [JSON Schema Glossary](https://json-schema.org/learn/glossary.html)
10
+ - [JSON](https://www.json.org/) and its specification
11
+ - Tree data structure ([Wikipedia](https://en.wikipedia.org/wiki/Tree_(data_structure)))
12
+ - Object-oriented programming ([Wikipedia](https://en.wikipedia.org/wiki/Object-oriented_programming))
13
+ - The Ruby programming language
14
+ - [JSON Pointer](https://www.rfc-editor.org/rfc/rfc6901)
15
+
16
+ The terminology from these can be contradictory, e.g. 'object' in JSON meaning what is a Hash in Ruby, but 'object' in Ruby meaning any object as in object-oriented programming. This glossary aims to clarify any ambiguity and introduce any terms which may not be familiar to the reader.
17
+
18
+
19
+ ## Terms
20
+
21
+
22
+ - ### JSI
23
+
24
+ [JSI]: #JSI
25
+ [a JSI]: #JSI
26
+
27
+ JSI is the name of this library. As a [countable](https://en.wikipedia.org/wiki/Count_noun), "a JSI" refers to the library's instantiation of an instance of a set of [schema]s. This is a Ruby instance of a subclass of {JSI::Base}.
28
+
29
+ The subclass of JSI::Base which a JSI is instantiated as includes the [schema module] of each schema that describes the instance (its {JSI::Base#jsi_schemas}), as well as type-specific modules for [array] and [hash/object] instances.
30
+
31
+
32
+ - ### node
33
+
34
+ [node]: #node
35
+
36
+ A node is an element in a [document] at a location identified by a [pointer].
37
+
38
+ In JSI a node generally means [a JSI] - a JSI::Base instance is often referred to just as "a JSI", but is referred to as a node in the context of its relationship to other nodes in its document.
39
+
40
+
41
+ - ### node content
42
+
43
+ [content]: #node_content
44
+
45
+ The content of a [node] is the parsed JSON instance.
46
+ It is generally a Ruby Hash, Array, String, Integer, Float or BigDecimal, true, false, or nil.
47
+
48
+ This content is referred to as the 'instance' in relation to [schema]s that describe it.
49
+
50
+ See {JSI::Base#jsi_node_content} (also aliased as {JSI::Base#jsi_instance}.
51
+
52
+
53
+ - ### document
54
+
55
+ [document]: #document
56
+
57
+ The document is the [content] of the [root] [node].
58
+
59
+ See {JSI::Base#jsi_document}.
60
+
61
+
62
+ - ### root
63
+
64
+ [root]: #root
65
+
66
+ A [node] representing the whole of a [document].
67
+
68
+ Its [pointer] is empty (has zero [token]s).
69
+
70
+ See {JSI::Base#jsi_root_node}.
71
+
72
+
73
+ - ### complex
74
+
75
+ [complex]: #complex
76
+
77
+ A [node] that can have [child]ren is complex. Its [content] is an [array] or a [hash/object].
78
+
79
+ Hash and Array nodes can mostly be used like Ruby Hashes and Arrays. JSI defines or delegates the methods of Hash and Array with nearly perfect compatibility, and supports [implicit conversion](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html) with `#to_hash` and `#to_ary`.
80
+
81
+ These nodes also support implicit conversion for the instance's content, treating any object responding to `#to_hash` or `#to_ary` like Hash or Array - though it is most common that actual Hash and Array instances will be the content. (This support may be incomplete for node content that is implicitly convertible but does not respond to certain methods, especially `#[]`.)
82
+
83
+
84
+ - ### child
85
+
86
+ [child]: #child
87
+
88
+ A [node] immediately below another node, its [parent]. Identified by one [token] relative to the parent.
89
+
90
+ See {JSI::Base#[]}.
91
+
92
+
93
+ - ### parent
94
+
95
+ [parent]: #parent
96
+
97
+ A [node] immediately above some number of other nodes, its [child]ren. A node that can be a parent node must be [complex].
98
+
99
+ See {JSI::Base#jsi_parent_node}.
100
+
101
+
102
+ - ### descendent
103
+
104
+ [descendent]: #descendent
105
+
106
+ A [node] anywhere below another node, its [ancestor]. Identified by a relative [pointer]. A node is considered to be a descendent of itself (at an empty relative pointer).
107
+
108
+ See {JSI::Base#jsi_descendent_node}, {JSI::Base#jsi_each_descendent_node}.
109
+
110
+
111
+ - ### ancestor
112
+
113
+ [ancestor]: #ancestor
114
+
115
+ A [node] anywhere above another node, its [descendent]. A node is considered to be an ancestor of itself, and the [root] node is an ancestor of every node in the [document].
116
+
117
+ See {JSI::Base#jsi_ancestor_nodes}.
118
+
119
+
120
+ - ### token
121
+
122
+ [token]: #token
123
+
124
+ An [array] [index] or [hash/object] [property name/key] that identifies a child node of its parent. Generally a String or non-negative Integer. [JSON Pointer](https://www.rfc-editor.org/rfc/rfc6901) calls this a "reference token".
125
+
126
+ A sequence of tokens comprises a [pointer].
127
+
128
+
129
+ - ### pointer
130
+
131
+ [pointer]: #pointer
132
+
133
+ A sequence of [token]s which identifies a [descendent] of a [node], instantiated as a {JSI::Ptr}.
134
+
135
+ [JSON Pointers](https://www.rfc-editor.org/rfc/rfc6901) are parsed to JSI pointers.
136
+
137
+ A pointer may be referred to as 'absolute' when identifying a descendent of the root node (see {JSI::Base#jsi_ptr}), or 'relative' identifying a descendent of any node (such as the pointer passed to {JSI::Base#jsi_descendent_node}).
138
+
139
+
140
+ - ### hash/object
141
+
142
+ [hash/object]: #hash_object
143
+
144
+ The [content] of a [complex] JSI Hash [node] (see {JSI::Base::HashNode}), representing a JSON object, is typically a Ruby Hash, or [implicitly convertible](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html) with `#to_hash`.
145
+
146
+
147
+ - ### array
148
+
149
+ [array]: #array
150
+
151
+ The [content] of a [complex] JSI Array [node] (see {JSI::Base::ArrayNode}) is typically a Ruby Array, or [implicitly convertible](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html) with `#to_ary`.
152
+
153
+
154
+ - ### property name/key
155
+
156
+ [property name/key]: #property_name_key
157
+
158
+ A [token] identifying a [child] of a [hash/object] [node]. In Ruby, a Hash key; in JSON Schema, an object property name. Property names are expected to be strings, though Ruby Hash keys do not have this limitation. Note that Symbols are not Strings, and Symbols should not be used in JSI [node content].
159
+
160
+ Property names can be described by schemas (using the `propertyNames` keyword), and can be JSI instances of those schemas. See {JSI::Base::HashNode#jsi_each_propertyName}.
161
+
162
+
163
+ - ### index
164
+
165
+ [index]: #index
166
+
167
+ A [token] identifying a [child] of an [array] [node]. A non-negative integer.
168
+
169
+
170
+ - ### instance
171
+
172
+ [instance]: #instance
173
+
174
+ A heavily-overloaded term. Context should make it clear in what sense it is being used. 'Instance' can refer to an *object* or a *relationship*, in Ruby or JSON Schema or JSI instantiation:
175
+
176
+ - JSON Schema: the _instance_ (JSON data) is an _instance_ (relationship) of [JSON Schemas][schema] that describe it
177
+ - Ruby: the _instance_ (an Object) is an _instance_ (relationship) of a Class and included Modules
178
+ - JSI: the _instance_ ([a JSI]) is an _instance_ (relationship) of JSI Schemas
179
+
180
+ These all operate in parallel in JSI: a JSI instance represents a JSON instance, it is described by JSI Schemas which represent JSON Schemas, and it is a Ruby instance of [JSI Schema Modules][schema module] of each schema that describes it.
181
+
182
+
183
+ - ### schema
184
+
185
+ [schema]: #schema
186
+
187
+ A JSI Schema is [a JSI] that represents a JSON Schema. It is a Ruby instance of the module {JSI::Schema}.
188
+
189
+ A schema describes a set of [instance]s. Any JSI instance that is described by a given schema is a Ruby instance of that schema's [schema module].
190
+
191
+ A schema is described by a [meta-schema] and is a Ruby instance of that meta-schema's [schema module].
192
+
193
+
194
+ - ### schema module
195
+
196
+ [schema module]: #schema_module
197
+
198
+ A JSI Schema Module is a Ruby module associated with a particular [schema]. Any JSI [instance] that is described by the schema is a Ruby instance of the schema's schema module. This is a {JSI::SchemaModule}.
199
+
200
+ See {JSI::Schema#jsi_schema_module}.
201
+
202
+ A JSI Schema Module is not to be confused with the module {JSI::Schema} - a schema *is* a Ruby instance of the module JSI::Schema, and it *has* a JSI Schema Module. The module JSI::Schema is included on the JSI Schema module of a [meta-schema].
203
+
204
+
205
+ - ### meta-schema
206
+
207
+ [meta-schema]: #meta-schema
208
+
209
+ A meta-schema is a [schema] that describes schemas, i.e. [instance]s of the meta-schema are schemas.
210
+
211
+ As with any other instance, a JSI schema includes the [schema module] of the meta-schema that describes it. The meta-schema's schema module defines the functionality for its instances to behave as schemas, including the module {JSI::Schema} as well as other modules implementing functionality particular to that meta-schema.
212
+
213
+ A meta-schema is described by a meta-schema, which may be itself or another meta-schema. Examples of self-describing meta-schemas are the JSON Schema meta-schemas. An example of the latter is the [schema describing the OpenAPI v3.0 Schema object](https://github.com/OAI/OpenAPI-Specification/blob/3.0.3/schemas/v3.0/schema.yaml#L203), which describes schemas in OpenAPI documents, but is itself described by JSON Schema draft 04.
214
+
215
+ A self-describing meta-schema is a Ruby instance of its own schema module.
216
+
217
+ See {JSI::Schema::DescribesSchema} and {JSI::SchemaModule::DescribesSchemaModule}, {JSI::Schema#describes_schema?} and {JSI::Schema#describes_schema!}
218
+
219
+
220
+ - ### resource
221
+
222
+ [resource]: #resource
223
+
224
+ A resource, or schema resource, is either:
225
+
226
+ - A [schema] that is identified by an absolute URI (declared with an id keyword)
227
+ - The root of a document containing schemas, whether or not the root is itself a schema. (Technically the root of any document can be considered a resource, but it is only useful when the document contains schemas.)
228
+
229
+ The nearest ancestor that is a resource is a node's "resource root" - this is distinct from the [root] node of the whole document.
230
+
231
+ Relative URIs and [pointer]s used by a schema (e.g. in `$ref` or `$id`) are resolved relative to its resource root and that resource's id.
232
+
233
+ See {JSI::Schema#schema_resource_root}
234
+
235
+
236
+ - ### schema application
237
+
238
+ [schema application]: #schema_application
239
+
240
+ The computation of schemas that apply describing a [node] at a given location. This involves several steps:
241
+
242
+ - **root indicated schemas**: Application begins with the schemas (usually just one schema) indicated as describing the [root]. `#new_jsi` is invoked on a {JSI::SchemaSet} of the indicated schemas, or more commonly on one schema or [schema module]. These are the root's {JSI::Base#jsi_indicated_schemas}.
243
+ - **root applied schemas**: [in-place application] is performed on each of the root indicated schemas to compute its applied schemas, i.e. its {JSI::Base#jsi_schemas}.
244
+ - Descending from the root to the given node, for each [token] of the node's [pointer]:
245
+ - **child indicated schemas**: [child application] is performed on each applied schema of the parent on the current token. This results in the child's indicated schemas.
246
+ - **child applied schemas**: [in-place application] is performed on each child indicated schema to compute its applied schemas.
247
+
248
+ The schemas that apply describing the node are the result of the final in-place application.
249
+
250
+
251
+ - ### child application
252
+
253
+ [child application]: #child_application
254
+
255
+ The computation of subschemas of a given schema that apply to a [child] of a [complex] instance on a given [token]. These come from subschemas defined on child applicator keywords such as `properties` and `items`. The result may be an empty schema set if no such keywords are present or none apply.
256
+
257
+
258
+ - ### in-place application
259
+
260
+ [in-place application]: #in_place_application
261
+
262
+ The resolution or expansion of a schema to a set of **applied schemas** for a given instance. "In-place" means all the schemas apply to the same location in the instance, in contrast to [child application]. This is a recursive process.
263
+
264
+ - If the schema contains a `$ref` keyword:
265
+ - The reference is resolved and in-place application is performed on the resolved schema.
266
+ - The schema containing the reference is _not_ applied.
267
+ - No other applicator keywords should be present; if present they are ignored.
268
+
269
+ The resulting applied schemas are those of the reference's resolved schema.
270
+
271
+ - Otherwise:
272
+ - The schema applies itself (it is added to the set of applied schemas).
273
+ - Any in-place applicator keywords (`anyOf`, `dependencies`, etc.) are evaluated for subschemas that apply to the instance. For each such subschema, in-place application is performed and the resulting schemas are added to the applied schemas.
274
+
275
+ The resulting applied schemas are the given schema plus the results of in-place application of each applicable subschema.
276
+
277
+ - ### validation
278
+
279
+ [validation]: #validation
280
+
281
+ The process of determining whether a given [instance] is valid against the [schema]s that describe it, or collecting validation errors indicating why the instance is not valid. See {JSI::Base#jsi_valid?}, {JSI::Base#jsi_validate}, {JSI::Schema#instance_valid?}, {JSI::Schema#instance_validate}
data/jsi.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ require_relative "lib/jsi/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "jsi-dev"
5
+ spec.version = JSI::VERSION
6
+ spec.authors = ["Ethan"]
7
+ spec.email = ["ethan.jsi@unth.net"]
8
+
9
+ spec.summary = "JSI: JSON Schema Instantiation"
10
+ spec.description = "JSI offers an Object-Oriented representation for JSON data using JSON Schemas"
11
+ spec.homepage = "https://github.com/notEthan/jsi"
12
+ spec.license = "AGPL-3.0"
13
+
14
+ spec.files = [
15
+ 'LICENSE.md',
16
+ 'CHANGELOG.md',
17
+ 'README.md',
18
+ 'readme.rb',
19
+ '.yardopts',
20
+ 'jsi.gemspec',
21
+ *Dir['lib/**/*'],
22
+ *Dir['\\{resources\\}/schemas/**/*'],
23
+ *Dir['docs/**/*'],
24
+ ].reject { |f| File.lstat(f).ftype == 'directory' }
25
+
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_dependency "addressable", '~> 2.3'
29
+ spec.add_development_dependency "maruku"
30
+ end