jsi 0.8.2 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +3 -2
  3. data/CHANGELOG.md +8 -3
  4. data/LICENSE.md +2 -3
  5. data/README.md +68 -31
  6. data/docs/Glossary.md +313 -0
  7. data/jsi.gemspec +1 -0
  8. data/lib/jsi/base/mutability.rb +4 -0
  9. data/lib/jsi/base/node.rb +63 -24
  10. data/lib/jsi/base.rb +556 -173
  11. data/lib/jsi/metaschema_node/bootstrap_schema.rb +106 -56
  12. data/lib/jsi/metaschema_node.rb +227 -160
  13. data/lib/jsi/ptr.rb +32 -15
  14. data/lib/jsi/ref.rb +197 -0
  15. data/lib/jsi/registry.rb +311 -0
  16. data/lib/jsi/schema/cxt/child_application.rb +35 -0
  17. data/lib/jsi/schema/cxt/inplace_application.rb +37 -0
  18. data/lib/jsi/schema/cxt.rb +80 -0
  19. data/lib/jsi/schema/dialect.rb +137 -0
  20. data/lib/jsi/schema/draft04.rb +113 -5
  21. data/lib/jsi/schema/draft06.rb +123 -5
  22. data/lib/jsi/schema/draft07.rb +157 -5
  23. data/lib/jsi/schema/draft202012.rb +303 -0
  24. data/lib/jsi/schema/dynamic_anchor_map.rb +63 -0
  25. data/lib/jsi/schema/element.rb +69 -0
  26. data/lib/jsi/schema/elements/anchor.rb +13 -0
  27. data/lib/jsi/schema/elements/array_validation.rb +82 -0
  28. data/lib/jsi/schema/elements/comment.rb +10 -0
  29. data/lib/jsi/schema/{validation → elements}/const.rb +11 -7
  30. data/lib/jsi/schema/elements/contains.rb +59 -0
  31. data/lib/jsi/schema/elements/contains_minmax.rb +91 -0
  32. data/lib/jsi/schema/elements/content_encoding.rb +10 -0
  33. data/lib/jsi/schema/elements/content_media_type.rb +10 -0
  34. data/lib/jsi/schema/elements/content_schema.rb +16 -0
  35. data/lib/jsi/schema/elements/default.rb +11 -0
  36. data/lib/jsi/schema/elements/definitions.rb +19 -0
  37. data/lib/jsi/schema/elements/dependencies.rb +99 -0
  38. data/lib/jsi/schema/elements/dependent_required.rb +49 -0
  39. data/lib/jsi/schema/elements/dependent_schemas.rb +69 -0
  40. data/lib/jsi/schema/elements/dynamic_ref.rb +69 -0
  41. data/lib/jsi/schema/elements/enum.rb +26 -0
  42. data/lib/jsi/schema/elements/examples.rb +10 -0
  43. data/lib/jsi/schema/elements/format.rb +10 -0
  44. data/lib/jsi/schema/elements/id.rb +30 -0
  45. data/lib/jsi/schema/elements/if_then_else.rb +82 -0
  46. data/lib/jsi/schema/elements/info_bool.rb +10 -0
  47. data/lib/jsi/schema/elements/info_string.rb +10 -0
  48. data/lib/jsi/schema/elements/items.rb +93 -0
  49. data/lib/jsi/schema/elements/items_prefixed.rb +96 -0
  50. data/lib/jsi/schema/elements/not.rb +31 -0
  51. data/lib/jsi/schema/elements/numeric.rb +137 -0
  52. data/lib/jsi/schema/elements/numeric_draft04.rb +77 -0
  53. data/lib/jsi/schema/elements/object_validation.rb +55 -0
  54. data/lib/jsi/schema/elements/pattern.rb +35 -0
  55. data/lib/jsi/schema/elements/properties.rb +145 -0
  56. data/lib/jsi/schema/elements/property_names.rb +48 -0
  57. data/lib/jsi/schema/elements/ref.rb +62 -0
  58. data/lib/jsi/schema/elements/required.rb +34 -0
  59. data/lib/jsi/schema/elements/self.rb +24 -0
  60. data/lib/jsi/schema/elements/some_of.rb +180 -0
  61. data/lib/jsi/schema/elements/string_validation.rb +57 -0
  62. data/lib/jsi/schema/elements/type.rb +43 -0
  63. data/lib/jsi/schema/elements/unevaluated_items.rb +54 -0
  64. data/lib/jsi/schema/elements/unevaluated_properties.rb +54 -0
  65. data/lib/jsi/schema/elements/xschema.rb +10 -0
  66. data/lib/jsi/schema/elements/xvocabulary.rb +10 -0
  67. data/lib/jsi/schema/elements.rb +101 -0
  68. data/lib/jsi/schema/issue.rb +3 -4
  69. data/lib/jsi/schema/schema_ancestor_node.rb +103 -50
  70. data/lib/jsi/schema/vocabulary.rb +36 -0
  71. data/lib/jsi/schema.rb +519 -337
  72. data/lib/jsi/schema_classes.rb +168 -124
  73. data/lib/jsi/schema_set.rb +67 -126
  74. data/lib/jsi/set.rb +23 -0
  75. data/lib/jsi/simple_wrap.rb +13 -16
  76. data/lib/jsi/struct.rb +57 -0
  77. data/lib/jsi/uri.rb +40 -0
  78. data/lib/jsi/util/private/memo_map.rb +9 -13
  79. data/lib/jsi/util/private.rb +57 -12
  80. data/lib/jsi/util/typelike.rb +19 -64
  81. data/lib/jsi/util.rb +52 -34
  82. data/lib/jsi/validation/error.rb +41 -2
  83. data/lib/jsi/validation/result.rb +118 -71
  84. data/lib/jsi/validation.rb +1 -6
  85. data/lib/jsi/version.rb +1 -1
  86. data/lib/jsi.rb +158 -41
  87. data/lib/schemas/json-schema.org/draft/2020-12/schema.rb +67 -0
  88. data/lib/schemas/json-schema.org/draft-04/schema.rb +63 -106
  89. data/lib/schemas/json-schema.org/draft-06/schema.rb +56 -105
  90. data/lib/schemas/json-schema.org/draft-07/schema.rb +67 -124
  91. data/readme.rb +3 -3
  92. data/{resources}/schemas/2020-12_strict.json +19 -0
  93. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/applicator.json +48 -0
  94. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/content.json +17 -0
  95. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/core.json +51 -0
  96. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-annotation.json +14 -0
  97. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/format-assertion.json +14 -0
  98. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/meta-data.json +37 -0
  99. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/unevaluated.json +15 -0
  100. data/{resources}/schemas/json-schema.org/draft/2020-12/meta/validation.json +98 -0
  101. data/{resources}/schemas/json-schema.org/draft/2020-12/schema.json +58 -0
  102. metadata +69 -47
  103. data/lib/jsi/schema/application/child_application/contains.rb +0 -25
  104. data/lib/jsi/schema/application/child_application/draft04.rb +0 -21
  105. data/lib/jsi/schema/application/child_application/draft06.rb +0 -28
  106. data/lib/jsi/schema/application/child_application/draft07.rb +0 -28
  107. data/lib/jsi/schema/application/child_application/items.rb +0 -18
  108. data/lib/jsi/schema/application/child_application/properties.rb +0 -25
  109. data/lib/jsi/schema/application/child_application.rb +0 -13
  110. data/lib/jsi/schema/application/draft04.rb +0 -8
  111. data/lib/jsi/schema/application/draft06.rb +0 -8
  112. data/lib/jsi/schema/application/draft07.rb +0 -8
  113. data/lib/jsi/schema/application/inplace_application/dependencies.rb +0 -28
  114. data/lib/jsi/schema/application/inplace_application/draft04.rb +0 -25
  115. data/lib/jsi/schema/application/inplace_application/draft06.rb +0 -26
  116. data/lib/jsi/schema/application/inplace_application/draft07.rb +0 -32
  117. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +0 -20
  118. data/lib/jsi/schema/application/inplace_application/ref.rb +0 -18
  119. data/lib/jsi/schema/application/inplace_application/someof.rb +0 -44
  120. data/lib/jsi/schema/application/inplace_application.rb +0 -14
  121. data/lib/jsi/schema/application.rb +0 -12
  122. data/lib/jsi/schema/ref.rb +0 -186
  123. data/lib/jsi/schema/validation/array.rb +0 -69
  124. data/lib/jsi/schema/validation/contains.rb +0 -25
  125. data/lib/jsi/schema/validation/dependencies.rb +0 -49
  126. data/lib/jsi/schema/validation/draft04/minmax.rb +0 -93
  127. data/lib/jsi/schema/validation/draft04.rb +0 -110
  128. data/lib/jsi/schema/validation/draft06.rb +0 -120
  129. data/lib/jsi/schema/validation/draft07.rb +0 -157
  130. data/lib/jsi/schema/validation/enum.rb +0 -25
  131. data/lib/jsi/schema/validation/ifthenelse.rb +0 -46
  132. data/lib/jsi/schema/validation/items.rb +0 -54
  133. data/lib/jsi/schema/validation/not.rb +0 -20
  134. data/lib/jsi/schema/validation/numeric.rb +0 -121
  135. data/lib/jsi/schema/validation/object.rb +0 -45
  136. data/lib/jsi/schema/validation/pattern.rb +0 -34
  137. data/lib/jsi/schema/validation/properties.rb +0 -101
  138. data/lib/jsi/schema/validation/property_names.rb +0 -32
  139. data/lib/jsi/schema/validation/ref.rb +0 -40
  140. data/lib/jsi/schema/validation/required.rb +0 -27
  141. data/lib/jsi/schema/validation/someof.rb +0 -90
  142. data/lib/jsi/schema/validation/string.rb +0 -47
  143. data/lib/jsi/schema/validation/type.rb +0 -49
  144. data/lib/jsi/schema/validation.rb +0 -49
  145. data/lib/jsi/schema_registry.rb +0 -200
  146. data/lib/jsi/util/private/attr_struct.rb +0 -141
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1afec35b87d23f2fba8a85b82c2064cce1c3e2121b312ecbb9a484df161647a0
4
- data.tar.gz: 90a488102c7f238fc9f983ee2a702699d2bf09af583942698a682a7e2c577be1
3
+ metadata.gz: 4f2957a6c197645dfb23fd3aaccd510203eb1dc437cbac88a51ad67d51f96d1d
4
+ data.tar.gz: 47f22bc77a8c2927262d92eeb79c40671100a8669d5847674b96af01d05d2820
5
5
  SHA512:
6
- metadata.gz: 66dd7b0af2007287d3d5ed34644f14ca8600671a8d09c3568345846f395426d37d250aa1b33c5e711d27591173b3ca5997186129dc248d8b0eaaab801c60a5da
7
- data.tar.gz: 21df5cc4f501977db1d65b2a675f2e22c138b82658b069f35924ea1bdadf768a9d6a6aa7496d3f91b220c02fe25fc45626aed01decf380b5dd6db89663c9ab1d
6
+ metadata.gz: 0d50b67c3ad1dfd95c9aa0c41d5a728506f03e36422254b19d3c30dc90f0477d9a72d5e8c798347b51cde405a0a4c60e291f38bb0227afa113febfa7988ed9e6
7
+ data.tar.gz: adee61e63e13c3d71f03fd98a499ec58497007e5c1aceebb0589d31d29566c0821a33f54f864f73cdefc5bf3421a49e72483cefc2b63f9ad9db561449d01668c
data/.yardopts CHANGED
@@ -1,6 +1,7 @@
1
1
  --main README.md
2
2
  --markup=markdown
3
- --markup-provider=commonmarker
3
+ --markup-provider=redcarpet
4
4
  --no-private
5
5
  --hide-void-return
6
- {lib}/**/*.rb
6
+ --files docs/**/*.md
7
+ lib/**/*.rb
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
- # v0.8.2
2
-
3
- - bugfix
1
+ # v0.9.0
2
+
3
+ - JSON Schema draft 2020-12 (JSI::JSONSchemaDraft202012)
4
+ - new architecture for Schema with Dialect, Vocabulary, Element
5
+ - Base#jsi_conf / Base::Conf
6
+ - JSI.translator
7
+ - Base#jsi_valid!, Schema#instance_valid!
8
+ - much more
4
9
 
5
10
  # v0.8.1
6
11
 
data/LICENSE.md CHANGED
@@ -1,8 +1,7 @@
1
- Copright © [Ethan](https://github.com/notEthan/) <ethan.jsi@unth.net>
1
+ JSI Copyright © [Ethan](https://github.com/notEthan/) <ethan.jsi@unth.net>, licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
2
2
 
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)
3
+ [<img align="right" src="https://www.gnu.org/graphics/agplv3-155x51.png">](https://www.gnu.org/licenses/agpl-3.0.html)
4
4
 
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
5
 
7
6
  GNU Affero General Public License
8
7
  =================================
data/README.md CHANGED
@@ -1,18 +1,20 @@
1
1
  # JSI: JSON Schema Instantiation
2
2
 
3
- ![Test CI Status](https://github.com/notEthan/jsi/actions/workflows/test.yml/badge.svg?branch=stable)
3
+ ![Test CI Status](https://github.com/notEthan/jsi/actions/workflows/test.yml/badge.svg?branch=main)
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.
7
7
 
8
8
  To learn more about JSON Schema see <https://json-schema.org/>.
9
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.
10
+ JSI marries object-oriented programming with JSON Schemas by associating a module with each schema, and constructing every instance described by a schema to be an instance of that module. When an application adds methods to a schema module, those methods can be used on the schema's instances.
11
11
 
12
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
13
 
14
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
15
 
16
+ JSI documents a {file:docs/Glossary.md Glossary} of relevant terms.
17
+
16
18
  ## Example
17
19
 
18
20
  Words are boring, let's code. You can follow along from the code blocks - install the gem (`gem install jsi`), load an irb (`irb -r jsi`), and copy/paste/hack.
@@ -38,7 +40,8 @@ properties:
38
40
  We pass that to {JSI.new_schema} which will instantiate a JSI Schema which represents it:
39
41
 
40
42
  ```ruby
41
- # this would usually load YAML or JSON; the schema object is inlined for copypastability.
43
+ # this would usually load YAML or JSON; the schema content
44
+ # is inlined here for copypastability.
42
45
  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"}}}}}})
43
46
  ```
44
47
 
@@ -64,7 +67,8 @@ So, if we construct an instance like:
64
67
 
65
68
  ```ruby
66
69
  bill = Contact.new_jsi(
67
- # this would typically load JSON or YAML; the schema instance is inlined for copypastability.
70
+ # this would usually load JSON or YAML; the instance content
71
+ # is inlined for copypastability.
68
72
  {"name" => "bill", "phone" => [{"location" => "home", "number" => "555"}], "nickname" => "big b"},
69
73
  # note: bill is mutable to demonstrate setters below; the default is immutable.
70
74
  mutable: true
@@ -115,17 +119,28 @@ bad = Contact.new_jsi({'phone' => [{'number' => [5, 5, 5]}]})
115
119
  # }
116
120
  # ]
117
121
  # }
118
- bad.phone.jsi_validate
119
- # => #<JSI::Validation::FullResult
120
- # @validation_errors=
121
- # #<Set: {#<JSI::Validation::Error
122
- # message: "instance type does not match `type` value",
123
- # keyword: "type",
124
- # schema: #{<JSI (JSI::JSONSchemaDraft07) Schema> "type" => "string"},
125
- # instance_ptr: JSI::Ptr["phone", 0, "number"],
126
- # instance_document: {"phone"=>[{"number"=>[5, 5, 5]}]}
127
- # >,
128
- # ...
122
+ bad.phone[0].jsi_validate
123
+ # =>
124
+ # #<JSI::Validation::Result::Full (INVALID)
125
+ # validation errors: JSI::Set[
126
+ # #<JSI::Validation::Error
127
+ # message: "instance object properties are not all valid against corresponding `properties` schemas",
128
+ # instance: {"number" => [5, 5, 5]},
129
+ # instance_ptr: JSI::Ptr["phone", 0],
130
+ # keyword: "properties",
131
+ # schema uri: JSI::URI["#/properties/phone/items"],
132
+ # nested_errors: JSI::Set[
133
+ # #<JSI::Validation::Error
134
+ # message: "instance type does not match `type` value",
135
+ # instance: [5, 5, 5],
136
+ # instance_ptr: JSI::Ptr["phone", 0, "number"],
137
+ # keyword: "type",
138
+ # schema uri: JSI::URI["#/properties/phone/items/properties/number"],
139
+ # nested_errors: JSI::Set[]
140
+ # >
141
+ # ]
142
+ # >
143
+ # ]
129
144
  # >
130
145
  ```
131
146
 
@@ -147,8 +162,8 @@ There's plenty more JSI has to offer, but this should give you a pretty good ide
147
162
 
148
163
  - `JSI::Base` is the base class for each JSI schema class representing instances of JSON Schemas.
149
164
  - a "JSI Schema" is a JSON Schema, instantiated as (usually) a JSI::Base described by a meta-schema (see the section on meta-schemas below). A JSI Schema is an instance of the module `JSI::Schema`.
150
- - 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.
151
- - 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.
165
+ - a "JSI Schema Module" is a module associated with one schema, dynamically created by that schema. Instances of that schema are ruby instances of its JSI schema module. Applications may reopen these modules to add functionality to JSI instances described by the schema.
166
+ - a "JSI schema class" is a subclass of `JSI::Base` representing any number of 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.
152
167
  - "instance" is a term that is significantly overloaded in this space, so documentation will attempt to be clear what kind of instance is meant:
153
168
  - a schema instance refers broadly to a data structure that is described by a JSON schema.
154
169
  - 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`).
@@ -163,6 +178,15 @@ JSI supports these JSON Schema specification versions:
163
178
  | Draft 4 | `http://json-schema.org/draft-04/schema#` | {JSI::JSONSchemaDraft04} |
164
179
  | Draft 6 | `http://json-schema.org/draft-06/schema#` | {JSI::JSONSchemaDraft06} |
165
180
  | Draft 7 | `http://json-schema.org/draft-07/schema#` | {JSI::JSONSchemaDraft07} |
181
+ | Draft 2020-12 | `https://json-schema.org/draft/2020-12/schema` | {JSI::JSONSchemaDraft202012} |
182
+
183
+ Caveats:
184
+
185
+ - 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 `$`.
186
+ - The `format` keyword does not perform validation. This may be implemented in the future.
187
+ - Draft 2020-12: `$schema` has no effect except at the document root ([#341](https://github.com/notEthan/jsi/issues/341))
188
+ - Draft 7: Keywords `contentMediaType` and `contentEncoding` do not perform validation.
189
+ - Draft 4: `$ref` is only used as a reference from schemas - it will not be followed when used on objects that are not schemas. This is consistent with specifications since Draft 4, but in Draft 4 the [JSON Reference](https://datatracker.ietf.org/doc/html/draft-pbryan-zyp-json-ref-03) specification would allow `$ref` to be used anywhere. JSI does not do this.
166
190
 
167
191
  ## JSI and Object Oriented Programming
168
192
 
@@ -198,7 +222,7 @@ For `#name` and `#name=`, we're overriding existing accessor methods. note the u
198
222
 
199
223
  Working with subschemas to add methods is just about as easy as with root schemas.
200
224
 
201
- You can subscript or use property accessors on a JSI schema module to refer to the schema modules of its subschemas, e.g.:
225
+ You can use `#[]` or property accessors on a JSI schema module to refer to the schema modules of its subschemas, e.g.:
202
226
 
203
227
  ```ruby
204
228
  Contact.properties['phone'].items
@@ -249,24 +273,20 @@ The classes used to instantiate JSIs are dynamically generated subclasses of JSI
249
273
 
250
274
  JSI instances are immutable by default. Mutable JSIs may be instantiated using the `mutable` param of `new_jsi`. Immutable JSIs are much more performant, because mutation may change what schemas apply to nodes in a document, and checking for that is costly. It is not recommended to instantiate large documents as mutable; their JSI instances become unusably slow.
251
275
 
276
+ If you are parsing with JSON.parse or YAML.load, it is recommended to pass the `freeze: true` option to these, which lets JSI skip making a frozen copy.
277
+
252
278
  ## Registration
253
279
 
254
- 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}.
280
+ In order for references across documents (generally from a `$ref` schema keyword) to resolve, JSI provides a registry (a {JSI::Registry}) which associates URIs with schemas (or resources containing schemas). The default registry is accessible on {JSI.registry}.
255
281
 
256
- 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`.
282
+ Schemas instantiated with `.new_schema`, and their subschemas, are by default registered with `JSI.registry` if they are identified by an absolute URI. This can be controlled by the `register` param and `registry` configuration.
257
283
 
258
- Schemas can automatically be lazily loaded by registering a block which instantiates them with {JSI::SchemaRegistry#autoload_uri} (see its documentation).
284
+ Schemas can automatically be lazily loaded by registering a block which instantiates them with {JSI::Registry#autoload_uri} (see its documentation).
259
285
 
260
286
  ## Validation
261
287
 
262
288
  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?}.
263
289
 
264
- The following optional features are not completely supported:
265
-
266
- - The `format` keyword does not perform any validation.
267
- - 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 `$`.
268
- - Keywords `contentMediaType` and `contentEncoding` do not perform validation.
269
-
270
290
  ## Meta-Schemas
271
291
 
272
292
  A meta-schema is a schema that describes schemas. Likewise, a schema is an instance of a meta-schema.
@@ -281,7 +301,7 @@ A really excellent place to use JSI is when dealing with serialized columns in A
281
301
 
282
302
  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.
283
303
 
284
- 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.
304
+ But if your database contains JSON, then your deserialized objects in ruby are likewise Hash / Array / simple types. You have to use `#[]` instead of accessors, and you don't have any way to add methods to your data types.
285
305
 
286
306
  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:
287
307
 
@@ -295,9 +315,26 @@ Now `user.contact_info` will be instantiated as a `Contact` JSI instance, from t
295
315
 
296
316
  See the gem [`arms`](https://github.com/notEthan/arms) if you wish to serialize the dumped JSON-compatible objects further as text.
297
317
 
298
- ## Keying Hashes (JSON Objects)
318
+ ## Hash keys (JSON Object property names)
319
+
320
+ For JSI instances containing Hashes, their keys should be strings. Hashes keyed with symbols are popular in Ruby, but this is not compatible with JSON.
321
+
322
+ JSI generally does not accommodate symbol keys. However, the syntax for Hash literals with symbol keys (`key: "value"` or `"key": "value"` rather than `"key" => "value"`) conveniently resembles JSON such that you can often paste JSON right into your Ruby (apart from Ruby's `nil` vs JSON's `null`). To enable this, JSI offers key conversion on instantiation: methods `new_jsi` and `new_schema` take a boolean param `stringify_symbol_keys` to recursively convert. This _only_ affects instantiation - no key conversion is done once the JSI has been initialized, e.g. by {JSI::Base#[]} or any methods of {JSI::Base::HashNode}.
323
+
324
+ ```ruby
325
+ # instantiate schema s and instance j, converting keys
326
+ s = JSI.new_schema(
327
+ # valid JSON
328
+ {
329
+ "$schema": "http://json-schema.org/draft-07/schema#",
330
+ "type": "array"
331
+ },
332
+ stringify_symbol_keys: true,
333
+ )
334
+ j = s.new_jsi([{"foo": "bar"}, {"foo": "baz"}], stringify_symbol_keys: true)
335
+ ```
299
336
 
300
- 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.
337
+ Third party libraries such as [ActiveSupport::HashWithIndifferentAccess](https://api.rubyonrails.org/classes/ActiveSupport/HashWithIndifferentAccess.html) or [Hashie](https://github.com/hashie/hashie) with [IndifferentAccess](https://github.com/hashie/hashie#indifferentaccess) exist to make symbol keys interchangeable with string keys. A JSI can be instantiated with an indifferent Hash as its content, but there will be various inconsistencies when accessing values with a string vs a symbol, and this is not recommended or supported.
301
338
 
302
339
  ## Contributing
303
340
 
@@ -305,7 +342,7 @@ Issues and pull requests are welcome on GitHub at https://github.com/notEthan/js
305
342
 
306
343
  ## License
307
344
 
308
- [<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)
345
+ [<img align="right" src="https://www.gnu.org/graphics/agplv3-155x51.png">](https://www.gnu.org/licenses/agpl-3.0.html)
309
346
 
310
347
  JSI is licensed under the terms of the [GNU Affero General Public License version 3](https://www.gnu.org/licenses/agpl-3.0.html).
311
348
 
data/docs/Glossary.md ADDED
@@ -0,0 +1,313 @@
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 described by a set of [schema]s. This is a Ruby instance of {JSI::Base}, and also an instance of the [schema module] belonging to each schema that describes it (its {JSI::Base#jsi_schemas}).
28
+
29
+
30
+ ### node
31
+
32
+ [node]: #node
33
+
34
+ A node is part of a [document] at a location identified by a [pointer].
35
+
36
+ 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.
37
+
38
+
39
+ ### node content
40
+
41
+ [content]: #node_content
42
+
43
+ The content of a [node] is the parsed JSON instance.
44
+ It is generally a Ruby Hash, Array, String, Integer, Float or BigDecimal, true, false, or nil.
45
+
46
+ This content is referred to as the 'instance' in relation to [schema]s that describe it.
47
+
48
+ See {JSI::Base#jsi_node_content} (also aliased as {JSI::Base#jsi_instance}.
49
+
50
+
51
+ ### document
52
+
53
+ [document]: #document
54
+
55
+ The document is the [content] of the [root] [node].
56
+
57
+ See {JSI::Base#jsi_document}.
58
+
59
+
60
+ ### root
61
+
62
+ [root]: #root
63
+
64
+ A [node] representing the whole of a [document].
65
+
66
+ Its [pointer] is empty (has zero [token]s).
67
+
68
+ See {JSI::Base#jsi_root_node}.
69
+
70
+
71
+ ### child
72
+
73
+ [child]: #child
74
+
75
+ A [node] immediately below another node, its [parent]. Identified by one [token] relative to the parent.
76
+
77
+ See {JSI::Base#[]} and {JSI::Base#jsi_child_node}.
78
+
79
+
80
+ ### parent
81
+
82
+ [parent]: #parent
83
+
84
+ A [node] immediately above some number of other nodes, its [child]ren. Only a [hash/object] or [array] can be a parent.
85
+
86
+ See {JSI::Base#jsi_parent_node}.
87
+
88
+
89
+ ### descendent
90
+
91
+ [descendent]: #descendent
92
+
93
+ 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).
94
+
95
+ See {JSI::Base#jsi_descendent_node}, {JSI::Base#jsi_each_descendent_node}.
96
+
97
+
98
+ ### ancestor
99
+
100
+ [ancestor]: #ancestor
101
+
102
+ A [node] above any number of other nodes, its [descendent]s. A node is considered to be an ancestor of itself, and the [root] node is an ancestor of every node in the [document].
103
+
104
+ See {JSI::Base#jsi_ancestor_nodes}.
105
+
106
+
107
+ ### token
108
+
109
+ [token]: #token
110
+
111
+ An [array] [index] or [hash/object] [key/property name] 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".
112
+
113
+ A sequence of tokens comprises a [pointer].
114
+
115
+
116
+ ### pointer
117
+
118
+ [pointer]: #pointer
119
+
120
+ A sequence of [token]s which identifies a [descendent] of a [node], instantiated as a {JSI::Ptr}.
121
+
122
+ [JSON Pointers](https://www.rfc-editor.org/rfc/rfc6901) are parsed to JSI pointers.
123
+
124
+ 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 ancestor node (such as the pointer passed to {JSI::Base#jsi_descendent_node}).
125
+
126
+
127
+ ### hash/object
128
+
129
+ [hash/object]: #hash_object
130
+
131
+ In JSON, an object; in Ruby, a Hash, or something [implicitly convertible](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html) with `#to_hash`.
132
+
133
+ In JSI, a [node] whose content is a Hash/`#to_hash`, and which is a {JSI::Base::HashNode}). These nodes are largely used as one would use a Hash, aiming to replicate Hash's API, as well being implicitly convertible with `#to_hash`.
134
+
135
+ A hash/object has [child] nodes on each [key/property name].
136
+
137
+
138
+ ### array
139
+
140
+ [array]: #array
141
+
142
+ In JSON, an array; in Ruby, an Array, or something [implicitly convertible](https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html) with `#to_ary`.
143
+
144
+ In JSI, a [node] whose content is an Array/`#to_ary`, and which is a {JSI::Base::ArrayNode}). These nodes are largely used as one would use an Array, aiming to replicate Array's API, as well being implicitly convertible with `#to_ary`.
145
+
146
+ An array has [child] nodes on each [index].
147
+
148
+
149
+ ### key/property name
150
+
151
+ [key/property name]: #key_property_name
152
+
153
+ 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 always strings in JSON. Note that symbols are not compatible and generally should not be used.
154
+
155
+ 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}.
156
+
157
+
158
+ ### index
159
+
160
+ [index]: #index
161
+
162
+ A [token] identifying a [child] of an [array] [node]. A non-negative integer. This may be represented in string form in a [pointer].
163
+
164
+
165
+ ### instance
166
+
167
+ [instance]: #instance
168
+
169
+ 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:
170
+
171
+ - JSON Schema: the *instance* (JSON data) is an *instance* (relationship) of JSON Schemas that describe it
172
+ - Ruby: the *instance* (an Object) is an *instance* (relationship) of a Class and included Modules
173
+ - JSI: the *instance* ([a JSI]) is an *instance* (relationship) of [JSI Schemas][schema]
174
+
175
+ 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 the [JSI Schema Modules][schema module] of the schemas that describe it.
176
+
177
+
178
+ ### schema
179
+
180
+ [schema]: #schema
181
+
182
+ A JSI Schema is [a JSI] that represents a JSON Schema. It is a Ruby instance of {JSI::Base} and the module {JSI::Schema}.
183
+
184
+ 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].
185
+
186
+ A schema is described by a [meta-schema] and is a Ruby instance of that meta-schema's [schema module].
187
+
188
+
189
+ ### schema module
190
+
191
+ [schema module]: #schema_module
192
+
193
+ A JSI Schema Module is a Ruby module associated with a particular [schema]. Any JSI instance that is described by that schema is a Ruby instance of the schema's schema module. This is a {JSI::SchemaModule}.
194
+
195
+ See {JSI::Schema#jsi_schema_module}.
196
+
197
+ 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].
198
+
199
+
200
+ ### meta-schema
201
+
202
+ [meta-schema]: #meta-schema
203
+
204
+ A meta-schema is a [schema] that describes schemas, i.e. [instance]s of the meta-schema are schemas.
205
+
206
+ As with any other JSI instance, a JSI schema is an instance of 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. It includes the module {JSI::Schema}.
207
+
208
+ 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 the JSON Schema draft-04 meta-schema.
209
+
210
+ A self-describing meta-schema is a Ruby instance of its own schema module.
211
+
212
+ A meta-schema has a [dialect] that defines the functionality of the schemas it describes.
213
+
214
+ In JSI, a meta-schema is a {JSI::Base} that is a {JSI::Schema::MetaSchema}. Its schema module is a {JSI::SchemaModule::MetaSchemaModule}, and includes {JSI::Schema}. See also {JSI::Schema#describes_schema?} and {JSI::Schema#describes_schema!}.
215
+
216
+
217
+ ### dialect
218
+
219
+ [dialect]: #dialect
220
+
221
+ A dialect defines all the keywords of a JSON Schema, how they operate, and any other aspects of schema behavior. It consists of a set of one or more [vocabularies][vocabulary]. Note that while not all specifications of dialects use the terms 'dialect' or 'vocabulary', JSI uses these abstractions for all supported specifications.
222
+
223
+ Examples of dialects include:
224
+ - Each published JSON Schema specification
225
+ - variants of JSON Schema defined by OpenAPI 2.x and 3.x
226
+ - custom dialects composed of vocabularies specified using the `$vocabulary` keyword
227
+
228
+ A dialect defines some or all of:
229
+
230
+ - a set of keywords
231
+ - those keywords' behavior and interactions with other keywords
232
+ - non-keyword behaviors of a schema (e.g. boolean schemas)
233
+ - division of keywords into vocabularies
234
+ - how vocabularies operate
235
+ - a [meta-schema] that describes/validates instances of the schema the dialect defines
236
+
237
+ Represented as a {JSI::Schema::Dialect}.
238
+
239
+
240
+ ### vocabulary
241
+
242
+ [vocabulary]: #vocabulary
243
+
244
+ A vocabulary is one part of a [dialect]'s definition of schema keywords and behaviors. Dialects are composed of one or more vocabularies.
245
+
246
+ A dialect whose specification does not define vocabularies is implemented using one vocabulary. Vocabularies were not defined for JSON Schema up to draft 07.
247
+
248
+ Represented as a {JSI::Schema::Vocabulary}.
249
+
250
+
251
+ ### resource
252
+
253
+ [resource]: #resource
254
+
255
+ A resource, or schema resource, is either:
256
+
257
+ - A [schema] that is identified by an absolute URI (typically declared with an id keyword)
258
+ - 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.)
259
+
260
+ For a given node, its *resource root* is the nearest ancestor that is a resource - this is distinct from the [root] node of the whole document.
261
+
262
+ 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.
263
+
264
+ See {JSI::Schema#schema_resource_root}.
265
+
266
+
267
+ ### schema application
268
+
269
+ [schema application]: #schema_application
270
+
271
+ The computation of the [schema]s that apply describing a particular [node]. This involves resolving `$ref`s, choosing what conditional schemas apply (e.g. which subschema of a `oneOf` applies), and recursing down children applying child applicator schemas. The steps of this process:
272
+
273
+ - **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}.
274
+ - **root applied schemas**: [in-place application] is performed on each of the root's indicated schemas to compute its applied schemas.
275
+ - Descending from the root to the given node, for each [token] of the node's [pointer]:
276
+ - **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.
277
+ - **child applied schemas**: [in-place application] is performed on each of the child's indicated schemas to compute its applied schemas.
278
+
279
+ The schemas that apply describing the node are the result of the final in-place application.
280
+
281
+
282
+ ### child application
283
+
284
+ [child application]: #child_application
285
+
286
+ The computation of subschemas of a given schema that describe a [child] of an 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.
287
+
288
+
289
+ ### in-place application
290
+
291
+ [in-place application]: #in_place_application
292
+
293
+ The 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.
294
+
295
+ - If the schema contains a `$ref` keyword, *and* the specification for the schema is draft-07 or older:
296
+ - The reference is resolved.
297
+ - In-place application recurses on the resolved schema.
298
+ - The rest of the schema is ignored. The schema does not apply itself, and any other applicator keywords are ignored (none should be present).
299
+
300
+ The resulting applied schemas are the resolved schema's in-place applicator schemas.
301
+
302
+ - Otherwise:
303
+ - The schema applies itself (it is added to the set of applied schemas).
304
+ - Any in-place applicator keywords (`anyOf`, `dependencies`, etc.) are evaluated for subschemas that apply to the instance. References are resolved from `$ref` or `$dynamicRef`, if present. For each such schema, in-place application recurses.
305
+
306
+ The resulting applied schemas consist of each recursively applied in-place applicator schema.
307
+
308
+
309
+ ### validation
310
+
311
+ [validation]: #validation
312
+
313
+ 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::Base#jsi_valid!}, {JSI::Schema#instance_valid?}, {JSI::Schema#instance_validate}, {JSI::Schema#instance_valid!}.
data/jsi.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  'jsi.gemspec',
21
21
  *Dir['lib/**/*'],
22
22
  *Dir['\\{resources\\}/schemas/**/*'],
23
+ *Dir['docs/**/*'],
23
24
  ].reject { |f| File.lstat(f).ftype == 'directory' }
24
25
 
25
26
  spec.require_paths = ["lib"]
@@ -15,6 +15,8 @@ module JSI
15
15
  private
16
16
 
17
17
  def jsi_mutability_initialize
18
+ @child_node_by_token_map = method(:jsi_child_node_by_token_compute)
19
+ @child_node_map = jsi_memomap(key_by: Base::BY_TOKEN, &method(:jsi_child_node_compute))
18
20
  end
19
21
 
20
22
  def jsi_memomap_class
@@ -34,6 +36,8 @@ module JSI
34
36
  private
35
37
 
36
38
  def jsi_mutability_initialize
39
+ @child_node_by_token_map = jsi_memomap(&method(:jsi_child_node_by_token_compute))
40
+ @child_node_map = method(:jsi_child_node_compute)
37
41
  @jsi_node_content = @jsi_ptr.evaluate(@jsi_document)
38
42
  end
39
43