jsi 0.4.0 → 0.6.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +15 -0
  4. data/README.md +105 -38
  5. data/lib/jsi/base.rb +349 -155
  6. data/lib/jsi/jsi_coder.rb +5 -4
  7. data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
  8. data/lib/jsi/metaschema_node.rb +156 -129
  9. data/lib/jsi/pathed_node.rb +47 -49
  10. data/lib/jsi/ptr.rb +292 -0
  11. data/lib/jsi/schema/application/child_application/contains.rb +16 -0
  12. data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
  13. data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
  14. data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
  15. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  16. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  17. data/lib/jsi/schema/application/child_application.rb +40 -0
  18. data/lib/jsi/schema/application/draft04.rb +8 -0
  19. data/lib/jsi/schema/application/draft06.rb +8 -0
  20. data/lib/jsi/schema/application/draft07.rb +8 -0
  21. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  22. data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
  23. data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
  24. data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
  25. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  26. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  27. data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
  28. data/lib/jsi/schema/application/inplace_application.rb +46 -0
  29. data/lib/jsi/schema/application.rb +12 -0
  30. data/lib/jsi/schema/draft04.rb +14 -0
  31. data/lib/jsi/schema/draft06.rb +14 -0
  32. data/lib/jsi/schema/draft07.rb +14 -0
  33. data/lib/jsi/schema/issue.rb +36 -0
  34. data/lib/jsi/schema/ref.rb +159 -0
  35. data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
  36. data/lib/jsi/schema/validation/array.rb +69 -0
  37. data/lib/jsi/schema/validation/const.rb +20 -0
  38. data/lib/jsi/schema/validation/contains.rb +25 -0
  39. data/lib/jsi/schema/validation/core.rb +39 -0
  40. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  41. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  42. data/lib/jsi/schema/validation/draft04.rb +112 -0
  43. data/lib/jsi/schema/validation/draft06.rb +122 -0
  44. data/lib/jsi/schema/validation/draft07.rb +159 -0
  45. data/lib/jsi/schema/validation/enum.rb +25 -0
  46. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  47. data/lib/jsi/schema/validation/items.rb +54 -0
  48. data/lib/jsi/schema/validation/not.rb +20 -0
  49. data/lib/jsi/schema/validation/numeric.rb +121 -0
  50. data/lib/jsi/schema/validation/object.rb +45 -0
  51. data/lib/jsi/schema/validation/pattern.rb +34 -0
  52. data/lib/jsi/schema/validation/properties.rb +101 -0
  53. data/lib/jsi/schema/validation/property_names.rb +32 -0
  54. data/lib/jsi/schema/validation/ref.rb +40 -0
  55. data/lib/jsi/schema/validation/required.rb +27 -0
  56. data/lib/jsi/schema/validation/someof.rb +90 -0
  57. data/lib/jsi/schema/validation/string.rb +47 -0
  58. data/lib/jsi/schema/validation/type.rb +49 -0
  59. data/lib/jsi/schema/validation.rb +51 -0
  60. data/lib/jsi/schema.rb +486 -133
  61. data/lib/jsi/schema_classes.rb +157 -42
  62. data/lib/jsi/schema_registry.rb +141 -0
  63. data/lib/jsi/schema_set.rb +141 -0
  64. data/lib/jsi/simple_wrap.rb +2 -2
  65. data/lib/jsi/typelike_modules.rb +52 -37
  66. data/lib/jsi/util/attr_struct.rb +106 -0
  67. data/lib/jsi/util.rb +141 -25
  68. data/lib/jsi/validation/error.rb +34 -0
  69. data/lib/jsi/validation/result.rb +210 -0
  70. data/lib/jsi/validation.rb +15 -0
  71. data/lib/jsi/version.rb +3 -1
  72. data/lib/jsi.rb +55 -9
  73. data/lib/schemas/json-schema.org/draft-04/schema.rb +8 -3
  74. data/lib/schemas/json-schema.org/draft-06/schema.rb +8 -3
  75. data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
  76. data/readme.rb +138 -0
  77. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  78. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  79. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  80. metadata +69 -118
  81. data/.simplecov +0 -3
  82. data/Rakefile.rb +0 -9
  83. data/jsi.gemspec +0 -28
  84. data/lib/jsi/base/to_rb.rb +0 -128
  85. data/lib/jsi/json/node.rb +0 -203
  86. data/lib/jsi/json/pointer.rb +0 -419
  87. data/lib/jsi/json-schema-fragments.rb +0 -61
  88. data/lib/jsi/json.rb +0 -10
  89. data/resources/icons/AGPL-3.0.png +0 -0
  90. data/test/base_array_test.rb +0 -323
  91. data/test/base_hash_test.rb +0 -337
  92. data/test/base_test.rb +0 -486
  93. data/test/jsi_coder_test.rb +0 -85
  94. data/test/jsi_json_arraynode_test.rb +0 -150
  95. data/test/jsi_json_hashnode_test.rb +0 -132
  96. data/test/jsi_json_node_test.rb +0 -257
  97. data/test/jsi_json_pointer_test.rb +0 -102
  98. data/test/jsi_test.rb +0 -11
  99. data/test/jsi_typelike_as_json_test.rb +0 -53
  100. data/test/metaschema_node_test.rb +0 -19
  101. data/test/schema_module_test.rb +0 -21
  102. data/test/schema_test.rb +0 -208
  103. data/test/spreedly_openapi_test.rb +0 -8
  104. data/test/test_helper.rb +0 -97
  105. data/test/util_test.rb +0 -62
@@ -3,37 +3,73 @@
3
3
  module JSI
4
4
  # JSI Schema Modules are extended with JSI::SchemaModule
5
5
  module SchemaModule
6
- # @return [String] absolute schema_id of the schema this module represents.
7
- # see {Schema#schema_id}.
8
- def schema_id
9
- schema.schema_id
6
+ # @!method schema
7
+ # the schema of which this is the JSI Schema Module
8
+ # @return [Schema]
9
+ # note: defined on JSI Schema Module by JSI::SchemaClasses.module_for_schema
10
+
11
+
12
+ # a URI which refers to the schema. see {Schema#schema_uri}.
13
+ # @return (see Schema#schema_uri)
14
+ def schema_uri
15
+ schema.schema_uri
10
16
  end
11
17
 
12
18
  # @return [String]
13
19
  def inspect
14
- uri = schema.schema_id || schema.node_ptr.uri
15
- if name
16
- "#{name} (#{uri})"
20
+ if name_from_ancestor
21
+ "#{name_from_ancestor} (JSI Schema Module)"
17
22
  else
18
- "(JSI Schema Module: #{uri})"
23
+ "(JSI Schema Module: #{schema.schema_uri || schema.jsi_ptr.uri})"
19
24
  end
20
25
  end
21
26
 
22
27
  # invokes {JSI::Schema#new_jsi} on this module's schema, passing the given instance.
28
+ #
29
+ # @param (see JSI::Schema#new_jsi)
23
30
  # @return [JSI::Base] a JSI whose instance is the given instance
24
- def new_jsi(instance, *a, &b)
25
- schema.new_jsi(instance, *a, &b)
31
+ def new_jsi(instance, **kw, &b)
32
+ schema.new_jsi(instance, **kw, &b)
26
33
  end
27
34
  end
28
35
 
29
- # this module is just a namespace for schema classes.
36
+ # a module to extend the JSI Schema Module of a schema which describes other schemas
37
+ module DescribesSchemaModule
38
+ # instantiates the given schema content as a JSI Schema.
39
+ #
40
+ # see {JSI::Schema::DescribesSchema#new_schema}
41
+ #
42
+ # @param (see JSI::Schema::DescribesSchema#new_schema)
43
+ # @return [JSI::Base, JSI::Schema] a JSI whose instance is the given schema_content and whose schemas
44
+ # consist of this module's schema.
45
+ def new_schema(schema_content, **kw)
46
+ schema.new_schema(schema_content, **kw)
47
+ end
48
+
49
+ # instantiates a given schema object as a JSI Schema and returns its JSI Schema Module.
50
+ #
51
+ # shortcut to chain {JSI::Schema::DescribesSchema#new_schema} + {Schema#jsi_schema_module}.
52
+ #
53
+ # @param (see JSI::Schema::DescribesSchema#new_schema)
54
+ # @return [Module, JSI::SchemaModule] the JSI Schema Module of the schema
55
+ def new_schema_module(schema_content, **kw)
56
+ schema.new_schema(schema_content, **kw).jsi_schema_module
57
+ end
58
+ end
59
+
60
+ # this module is a namespace for building schema classes and schema modules.
30
61
  module SchemaClasses
62
+ extend Util::Memoize
63
+
31
64
  class << self
32
- include Util::Memoize
65
+ # a JSI Schema Class which represents the given schemas.
66
+ # an instance of the class is a JSON Schema instance described by all of the given schemas.
67
+ # @private
68
+ # @param schemas [Enumerable<JSI::Schema>] schemas which the class will represent
69
+ # @return [Class subclassing JSI::Base]
70
+ def class_for_schemas(schemas)
71
+ schemas = SchemaSet.ensure_schema_set(schemas)
33
72
 
34
- # see {JSI.class_for_schemas}
35
- def class_for_schemas(schema_objects)
36
- schemas = schema_objects.map { |schema_object| JSI::Schema.from_object(schema_object) }.to_set
37
73
  jsi_memoize(:class_for_schemas, schemas) do |schemas|
38
74
  Class.new(Base).instance_exec(schemas) do |schemas|
39
75
  define_singleton_method(:jsi_class_schemas) { schemas }
@@ -47,13 +83,28 @@ module JSI
47
83
  end
48
84
  end
49
85
 
50
- # a module for the given schema, with accessor methods for any object property names the schema
51
- # identifies (see {JSI::Schema#described_object_property_names}).
52
- #
53
- # defines a singleton method #schema to access the {JSI::Schema} this module represents, and extends
54
- # the module with {JSI::SchemaModule}.
55
- def module_for_schema(schema_object)
56
- schema = JSI::Schema.from_object(schema_object)
86
+ # @private
87
+ # a subclass of MetaschemaNode::BootstrapSchema with the given modules included
88
+ # @param modules [Set<Module>] metaschema instance modules
89
+ # @return [Class]
90
+ def bootstrap_schema_class(modules)
91
+ modules = Util.ensure_module_set(modules)
92
+ jsi_memoize(__method__, modules) do |modules|
93
+ Class.new(MetaschemaNode::BootstrapSchema).instance_exec(modules) do |modules|
94
+ define_singleton_method(:metaschema_instance_modules) { modules }
95
+ define_method(:metaschema_instance_modules) { modules }
96
+ modules.each { |mod| include(mod) }
97
+
98
+ self
99
+ end
100
+ end
101
+ end
102
+
103
+ # see {Schema#jsi_schema_module}
104
+ # @api private
105
+ # @return [Module]
106
+ def module_for_schema(schema)
107
+ Schema.ensure_schema(schema)
57
108
  jsi_memoize(:module_for_schema, schema) do |schema|
58
109
  Module.new.tap do |m|
59
110
  m.module_eval do
@@ -61,41 +112,67 @@ module JSI
61
112
 
62
113
  extend SchemaModule
63
114
 
64
- include JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [JSI::Base, JSI::PathedArrayNode, JSI::PathedHashNode])
115
+ schema.jsi_schema_instance_modules.each do |mod|
116
+ include(mod)
117
+ end
118
+
119
+ accessor_module = JSI::SchemaClasses.accessor_module_for_schema(schema,
120
+ conflicting_modules: Set[JSI::Base, JSI::PathedArrayNode, JSI::PathedHashNode] +
121
+ schema.jsi_schema_instance_modules,
122
+ )
123
+ include accessor_module
124
+
125
+ define_singleton_method(:jsi_property_accessors) { accessor_module.jsi_property_accessors }
126
+
127
+ if schema.describes_schema?
128
+ extend DescribesSchemaModule
129
+ end
65
130
 
66
131
  @possibly_schema_node = schema
67
132
  extend(SchemaModulePossibly)
68
133
  schema.jsi_schemas.each do |schema_schema|
69
- extend(JSI::SchemaClasses.accessor_module_for_schema(schema_schema, conflicting_modules: [Module, SchemaModule, SchemaModulePossibly]))
134
+ extend JSI::SchemaClasses.accessor_module_for_schema(schema_schema,
135
+ conflicting_modules: Set[Module, SchemaModule, SchemaModulePossibly],
136
+ setters: false,
137
+ )
70
138
  end
71
139
  end
72
140
  end
73
141
  end
74
142
  end
75
143
 
144
+ # a module of accessors for described property names of the given schema.
145
+ # getters are always defined. setters are defined by default.
76
146
  # @param schema [JSI::Schema] a schema for which to define accessors for any described property names
77
147
  # @param conflicting_modules [Enumerable<Module>] an array of modules (or classes) which
78
148
  # may be used alongside the accessor module. methods defined by any conflicting_module
79
149
  # will not be defined as accessors.
80
- # @return [Module] a module of accessors (setters and getters) for described property names of the given
81
- # schema
82
- def accessor_module_for_schema(schema, conflicting_modules: )
83
- unless schema.is_a?(JSI::Schema)
84
- raise(JSI::Schema::NotASchemaError, "not a schema: #{schema.pretty_inspect.chomp}")
85
- end
86
- jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules) do |schema, conflicting_modules|
150
+ # @param setters [Boolean] whether to define setter methods
151
+ # @return [Module]
152
+ def accessor_module_for_schema(schema, conflicting_modules: , setters: true)
153
+ Schema.ensure_schema(schema)
154
+ jsi_memoize(:accessor_module_for_schema, schema, conflicting_modules, setters) do |schema, conflicting_modules, setters|
87
155
  Module.new.tap do |m|
88
156
  m.module_eval do
89
157
  conflicting_instance_methods = (conflicting_modules + [m]).map do |mod|
90
158
  mod.instance_methods + mod.private_instance_methods
91
159
  end.inject(Set.new, &:|)
92
- accessors_to_define = schema.described_object_property_names.map(&:to_s) - conflicting_instance_methods.map(&:to_s)
160
+
161
+ accessors_to_define = schema.described_object_property_names.select do |name|
162
+ # must not conflict with any method on a conflicting module
163
+ Util.ok_ruby_method_name?(name) && !conflicting_instance_methods.any? { |mn| mn.to_s == name }
164
+ end.to_set.freeze
165
+
166
+ define_singleton_method(:jsi_property_accessors) { accessors_to_define }
167
+
93
168
  accessors_to_define.each do |property_name|
94
- define_method(property_name) do
95
- self[property_name]
169
+ define_method(property_name) do |*a|
170
+ self[property_name, *a]
96
171
  end
97
- define_method("#{property_name}=") do |value|
98
- self[property_name] = value
172
+ if setters
173
+ define_method("#{property_name}=") do |value|
174
+ self[property_name] = value
175
+ end
99
176
  end
100
177
  end
101
178
  end
@@ -105,17 +182,43 @@ module JSI
105
182
  end
106
183
  end
107
184
 
108
- # a JSI::Schema module and a JSI::NotASchemaModule are both a SchemaModulePossibly.
185
+ # a JSI Schema module and a JSI::NotASchemaModule are both a SchemaModulePossibly.
109
186
  # this module provides a #[] method.
110
187
  module SchemaModulePossibly
111
188
  attr_reader :possibly_schema_node
112
189
 
190
+ # a name relative to a named schema module of an ancestor schema.
191
+ # for example, if `Foos = JSI::JSONSchemaOrgDraft07.new_schema_module({'items' => {}})`
192
+ # then the module `Foos.items` will have a name_from_ancestor of `"Foos.items"`
193
+ # @return [String, nil]
194
+ def name_from_ancestor
195
+ schema_ancestors = [possibly_schema_node] + possibly_schema_node.jsi_parent_nodes
196
+ named_parent_schema = schema_ancestors.detect { |jsi| jsi.is_a?(JSI::Schema) && jsi.jsi_schema_module.name }
197
+
198
+ return nil unless named_parent_schema
199
+
200
+ tokens = possibly_schema_node.jsi_ptr.ptr_relative_to(named_parent_schema.jsi_ptr).tokens
201
+ name = named_parent_schema.jsi_schema_module.name
202
+ parent = named_parent_schema
203
+ tokens.each do |token|
204
+ if parent.jsi_schemas.any? { |s| s.jsi_schema_module.jsi_property_accessors.include?(token) }
205
+ name += ".#{token}"
206
+ elsif [String, Numeric, TrueClass, FalseClass, NilClass].any? { |m| token.is_a?(m) }
207
+ name += "[#{token.inspect}]"
208
+ else
209
+ return nil
210
+ end
211
+ parent = parent[token]
212
+ end
213
+ name
214
+ end
215
+
113
216
  # subscripting a JSI schema module or a NotASchemaModule will subscript the node, and
114
- # if the result is a JSI::Schema, return a JSI::Schema class; if it is a PathedNode,
217
+ # if the result is a JSI::Schema, return the JSI Schema module of that schema; if it is a PathedNode,
115
218
  # return a NotASchemaModule; or if it is another value (a basic type), return that value.
116
219
  #
117
220
  # @param token [Object]
118
- # @return [Class, NotASchemaModule, Object]
221
+ # @return [Module, NotASchemaModule, Object]
119
222
  def [](token)
120
223
  sub = @possibly_schema_node[token]
121
224
  if sub.is_a?(JSI::Schema)
@@ -128,9 +231,12 @@ module JSI
128
231
  end
129
232
  end
130
233
 
131
- # a schema module is a module which represents a schema. a NotASchemaModule represents
234
+ # a JSI Schema Module is a module which represents a schema. a NotASchemaModule represents
132
235
  # a node in a schema's document which is not a schema, such as the 'properties'
133
- # node (which contains schemas but is not a schema).
236
+ # object (which contains schemas but is not a schema).
237
+ #
238
+ # instances of this class act as a stand-in to allow users to subscript or call property accessors on
239
+ # schema modules to refer to their subschemas' schema modules.
134
240
  #
135
241
  # a NotASchemaModule is extended with the module_for_schema of the node's schema.
136
242
  #
@@ -149,10 +255,19 @@ module JSI
149
255
  end
150
256
  @possibly_schema_node = node
151
257
  node.jsi_schemas.each do |schema|
152
- extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly]))
258
+ extend(JSI::SchemaClasses.accessor_module_for_schema(schema, conflicting_modules: [NotASchemaModule, SchemaModulePossibly], setters: false))
153
259
  end
154
260
  end
155
261
 
156
262
  include SchemaModulePossibly
263
+
264
+ # @return [String]
265
+ def inspect
266
+ if name_from_ancestor
267
+ "#{name_from_ancestor} (JSI wrapper for Schema Module)"
268
+ else
269
+ "(JSI wrapper for Schema Module: #{@possibly_schema_node.jsi_ptr.uri})"
270
+ end
271
+ end
157
272
  end
158
273
  end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ class SchemaRegistry
5
+ # an exception raised when an attempt is made to register a resource using a URI which is already
6
+ # registered with another resource
7
+ class Collision < StandardError
8
+ end
9
+
10
+ # an exception raised when an attempt is made to access (register or find) a resource of the
11
+ # registry using a URI which is not absolute (it is a relative URI or it contains a fragment)
12
+ class NonAbsoluteURI < StandardError
13
+ end
14
+
15
+ # an exception raised when a URI we are looking for has not been registered
16
+ class ResourceNotFound < StandardError
17
+ end
18
+
19
+ def initialize
20
+ @resources = {}
21
+ @autoload_uris = {}
22
+ @resources_mutex = Mutex.new
23
+ end
24
+
25
+ # registers the given resource and/or schema resources it contains in the registry.
26
+ #
27
+ # each child of the resource (including the resource itself) is registered if it is a schema that
28
+ # has an absolute URI (generally defined by the '$id' keyword).
29
+ #
30
+ # the given resource itself will be registered, whether or not it is a schema, if it is the root
31
+ # of its document and was instantiated with the option `uri` specified.
32
+ #
33
+ # @param resource [JSI::Base] a JSI containing resources to register
34
+ # @return [void]
35
+ def register(resource)
36
+ unless resource.is_a?(JSI::Base)
37
+ raise(ArgumentError, "resource must be a JSI::Base. got: #{resource.pretty_inspect.chomp}")
38
+ end
39
+ unless resource.is_a?(JSI::Schema) || resource.jsi_ptr.root?
40
+ # unsure, should this be allowed? the given JSI is not a "resource" as we define it, but
41
+ # if this check is removed it will just register any resources (schemas) below the given JSI.
42
+ raise(ArgumentError, "undefined behavior: registration of a JSI which is not a schema and is not at the root of a document")
43
+ end
44
+
45
+ # allow for registration of resources at the root of a document whether or not they are schemas.
46
+ # jsi_schema_base_uri at the root comes from the `uri` parameter to new_jsi / new_schema.
47
+ if resource.jsi_schema_base_uri && resource.jsi_ptr.root?
48
+ register_single(resource.jsi_schema_base_uri, resource)
49
+ end
50
+
51
+ resource.jsi_each_child_node do |node|
52
+ if node.is_a?(JSI::Schema) && node.schema_absolute_uri
53
+ register_single(node.schema_absolute_uri, node)
54
+ end
55
+ end
56
+
57
+ nil
58
+ end
59
+
60
+ # takes a URI identifying a resource to be loaded by the given block
61
+ # when a reference to the URI is followed.
62
+ #
63
+ # for example:
64
+ #
65
+ # JSI.schema_registry.autoload_uri('http://example.com/schema.json') do
66
+ # JSI.new_schema({
67
+ # '$schema' => 'http://json-schema.org/draft-07/schema#',
68
+ # '$id' => 'http://example.com/schema.json',
69
+ # 'title' => 'my schema',
70
+ # })
71
+ # end
72
+ #
73
+ # the block would normally load JSON from the filesystem or similar.
74
+ #
75
+ # @param uri [Addressable::URI]
76
+ # @yieldreturn [JSI::Base] a JSI instance containing the resource identified by the given uri
77
+ # @return [void]
78
+ def autoload_uri(uri, &block)
79
+ uri = Addressable::URI.parse(uri)
80
+ ensure_uri_absolute(uri)
81
+ @autoload_uris[uri] = block
82
+ nil
83
+ end
84
+
85
+ # @param uri [Addressable::URI, #to_str]
86
+ # @return [JSI::Base]
87
+ # @raise [JSI::SchemaRegistry::ResourceNotFound]
88
+ def find(uri)
89
+ uri = Addressable::URI.parse(uri)
90
+ ensure_uri_absolute(uri)
91
+ if @autoload_uris.key?(uri) && !@resources.key?(uri)
92
+ register(@autoload_uris[uri].call)
93
+ end
94
+ registered_uris = @resources.keys
95
+ if !registered_uris.include?(uri)
96
+ raise(ResourceNotFound, "URI #{uri} is not registered. registered URIs:\n#{registered_uris.join("\n")}")
97
+ end
98
+ @resources[uri]
99
+ end
100
+
101
+ def dup
102
+ self.class.new.tap do |reg|
103
+ @resources.each do |uri, resource|
104
+ reg.register_single(uri, resource)
105
+ end
106
+ @autoload_uris.each do |uri, autoload|
107
+ reg.autoload_uri(uri, &autoload)
108
+ end
109
+ end
110
+ end
111
+
112
+ protected
113
+ # @param uri [Addressable::URI]
114
+ # @param resource [JSI::Base]
115
+ # @return [void]
116
+ def register_single(uri, resource)
117
+ @resources_mutex.synchronize do
118
+ ensure_uri_absolute(uri)
119
+ if @resources.key?(uri)
120
+ if @resources[uri] != resource
121
+ raise(Collision, "URI collision on #{uri}.\nexisting:\n#{@resources[uri].pretty_inspect.chomp}\nnew:\n#{resource.pretty_inspect.chomp}")
122
+ end
123
+ else
124
+ @resources[uri] = resource
125
+ end
126
+ end
127
+ nil
128
+ end
129
+
130
+ private
131
+
132
+ def ensure_uri_absolute(uri)
133
+ if uri.fragment
134
+ raise(NonAbsoluteURI, "#{self.class} only registers absolute URIs. cannot access URI with fragment: #{uri}")
135
+ end
136
+ if uri.relative?
137
+ raise(NonAbsoluteURI, "#{self.class} only registers absolute URIs. cannot access relative URI: #{uri}")
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ # a Set of JSI Schemas. always frozen.
5
+ #
6
+ # any schema instance is described by a set of schemas.
7
+ class SchemaSet < ::Set
8
+ class << self
9
+ # builds a SchemaSet from a mutable Set which is added to by the given block
10
+ #
11
+ # @yield [Set] a Set to which the block may add schemas
12
+ # @return [SchemaSet]
13
+ def build
14
+ mutable_set = Set.new
15
+ yield mutable_set
16
+ new(mutable_set)
17
+ end
18
+
19
+ # ensures the given param becomes a SchemaSet. returns the param if it is already SchemaSet, otherwise
20
+ # initializes a SchemaSet from it.
21
+ #
22
+ # @param schemas [SchemaSet, Enumerable] the object to ensure becomes a SchemaSet
23
+ # @return [SchemaSet] the given SchemaSet, or a SchemaSet initialized from the given Enumerable
24
+ # @raise [ArgumentError] when the schemas param is not an Enumerable
25
+ # @raise [Schema::NotASchemaError] when the schemas param contains objects which are not Schemas
26
+ def ensure_schema_set(schemas)
27
+ if schemas.is_a?(SchemaSet)
28
+ schemas
29
+ else
30
+ new(schemas)
31
+ end
32
+ end
33
+ end
34
+
35
+ # initializes a SchemaSet from the given enum and freezes it.
36
+ #
37
+ # if a block is given, each element of the enum is passed to it, and the result must be a Schema.
38
+ # if no block is given, the enum must contain only Schemas.
39
+ #
40
+ # @param enum [#each] the schemas to be included in the SchemaSet, or items to be passed to the block
41
+ # @yieldparam yields each element of enum for preprocessing into a Schema
42
+ # @yieldreturn [JSI::Schema]
43
+ # @raise [JSI::Schema::NotASchemaError]
44
+ def initialize(enum, &block)
45
+ super
46
+
47
+ not_schemas = reject { |s| s.is_a?(Schema) }
48
+ if !not_schemas.empty?
49
+ raise(Schema::NotASchemaError, [
50
+ "JSI::SchemaSet initialized with non-schema objects:",
51
+ *not_schemas.map { |ns| ns.pretty_inspect.chomp },
52
+ ].join("\n"))
53
+ end
54
+
55
+ freeze
56
+ end
57
+
58
+ # instantiates the given instance as a JSI. its schemas are inplace applicators matched from the schemas
59
+ # in this SchemaSet which apply to the given instance.
60
+ #
61
+ # @param instance [Object] the JSON Schema instance to be represented as a JSI
62
+ # @param uri [nil, #to_str, Addressable::URI] for an instance document containing schemas, this is
63
+ # the URI of the document, whether or not the document is itself a schema.
64
+ # in the normal case where the document does not contain any schemas, `uri` has no effect.
65
+ # schemas within the document use this uri as the base URI to resolve relative URIs.
66
+ # the resulting JSI may be registered with a {SchemaRegistry} (see {JSI.schema_registry}).
67
+ # @return [JSI::Base subclass] a JSI whose instance is the given instance and whose schemas are inplace
68
+ # applicators matched to the instance from the schemas in this set.
69
+ def new_jsi(instance,
70
+ uri: nil
71
+ )
72
+ applied_schemas = inplace_applicator_schemas(instance)
73
+
74
+ jsi = JSI::SchemaClasses.class_for_schemas(applied_schemas).new(instance,
75
+ jsi_schema_base_uri: uri,
76
+ )
77
+
78
+ jsi
79
+ end
80
+
81
+ # a set of inplace applicator schemas of each schema in this set which apply to the given instance.
82
+ # (see {Schema::Application::InplaceApplication#inplace_applicator_schemas})
83
+ #
84
+ # @param instance (see Schema::Application::InplaceApplication#inplace_applicator_schemas)
85
+ # @return [JSI::SchemaSet]
86
+ def inplace_applicator_schemas(instance)
87
+ SchemaSet.new(each_inplace_applicator_schema(instance))
88
+ end
89
+
90
+ # yields each inplace applicator schema which applies to the given instance.
91
+ #
92
+ # @param instance (see Schema::Application::InplaceApplication#inplace_applicator_schemas)
93
+ # @yield [JSI::Schema]
94
+ # @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
95
+ def each_inplace_applicator_schema(instance, &block)
96
+ return to_enum(__method__, instance) unless block
97
+
98
+ each do |schema|
99
+ schema.each_inplace_applicator_schema(instance, &block)
100
+ end
101
+
102
+ nil
103
+ end
104
+
105
+ # validates the given instance against our schemas
106
+ #
107
+ # @param instance [Object] the instance to validate against our schemas
108
+ # @return [JSI::Validation::Result]
109
+ def instance_validate(instance)
110
+ results = map { |schema| schema.instance_validate(instance) }
111
+ results.inject(Validation::FullResult.new, &:merge).freeze
112
+ end
113
+
114
+ # whether the given instance is valid against our schemas
115
+ # @param instance [Object] the instance to validate against our schemas
116
+ # @return [Boolean]
117
+ def instance_valid?(instance)
118
+ all? { |schema| schema.instance_valid?(instance) }
119
+ end
120
+
121
+ # @return [String]
122
+ def inspect
123
+ "#{self.class}[#{map(&:inspect).join(", ")}]"
124
+ end
125
+
126
+ def pretty_print(q)
127
+ q.text self.class.to_s
128
+ q.text '['
129
+ q.group_sub {
130
+ q.nest(2) {
131
+ q.breakable('')
132
+ q.seplist(self, nil, :each) { |e|
133
+ q.pp e
134
+ }
135
+ }
136
+ }
137
+ q.breakable ''
138
+ q.text ']'
139
+ end
140
+ end
141
+ end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- SimpleWrap = JSI::Schema.new({
4
+ SimpleWrap = JSI::JSONSchemaOrgDraft06.new_schema_module({
5
5
  "additionalProperties": {"$ref": "#"},
6
6
  "items": {"$ref": "#"}
7
- }).jsi_schema_module
7
+ })
8
8
 
9
9
  # SimpleWrap is a JSI schema module which recursively wraps nested structures
10
10
  module SimpleWrap