jsi 0.0.4 → 0.4.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/.simplecov +3 -1
  3. data/CHANGELOG.md +48 -0
  4. data/LICENSE.md +613 -0
  5. data/README.md +84 -45
  6. data/jsi.gemspec +11 -14
  7. data/lib/jsi.rb +31 -12
  8. data/lib/jsi/base.rb +310 -344
  9. data/lib/jsi/base/to_rb.rb +2 -0
  10. data/lib/jsi/jsi_coder.rb +91 -0
  11. data/lib/jsi/json-schema-fragments.rb +3 -135
  12. data/lib/jsi/json.rb +3 -0
  13. data/lib/jsi/json/node.rb +72 -197
  14. data/lib/jsi/json/pointer.rb +419 -0
  15. data/lib/jsi/metaschema.rb +7 -0
  16. data/lib/jsi/metaschema_node.rb +218 -0
  17. data/lib/jsi/pathed_node.rb +118 -0
  18. data/lib/jsi/schema.rb +168 -223
  19. data/lib/jsi/schema_classes.rb +158 -0
  20. data/lib/jsi/simple_wrap.rb +12 -0
  21. data/lib/jsi/typelike_modules.rb +71 -45
  22. data/lib/jsi/util.rb +47 -57
  23. data/lib/jsi/version.rb +1 -1
  24. data/lib/schemas/json-schema.org/draft-04/schema.rb +7 -0
  25. data/lib/schemas/json-schema.org/draft-06/schema.rb +7 -0
  26. data/resources/icons/AGPL-3.0.png +0 -0
  27. data/test/base_array_test.rb +210 -84
  28. data/test/base_hash_test.rb +201 -58
  29. data/test/base_test.rb +212 -121
  30. data/test/jsi_coder_test.rb +85 -0
  31. data/test/jsi_json_arraynode_test.rb +26 -25
  32. data/test/jsi_json_hashnode_test.rb +40 -39
  33. data/test/jsi_json_node_test.rb +95 -126
  34. data/test/jsi_json_pointer_test.rb +102 -0
  35. data/test/jsi_typelike_as_json_test.rb +53 -0
  36. data/test/metaschema_node_test.rb +19 -0
  37. data/test/schema_module_test.rb +21 -0
  38. data/test/schema_test.rb +109 -97
  39. data/test/spreedly_openapi_test.rb +8 -0
  40. data/test/test_helper.rb +42 -8
  41. data/test/util_test.rb +14 -14
  42. metadata +54 -25
  43. data/LICENSE.txt +0 -21
  44. data/lib/jsi/schema_instance_json_coder.rb +0 -83
  45. data/lib/jsi/struct_json_coder.rb +0 -30
  46. data/test/schema_instance_json_coder_test.rb +0 -121
  47. data/test/struct_json_coder_test.rb +0 -130
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSI
4
+ # including class MUST define
5
+ # - #node_document [Object] returning the document
6
+ # - #node_ptr [JSI::JSON::Pointer] returning a pointer for the node path in the document
7
+ # - #document_root_node [JSI::PathedNode] returning a PathedNode pointing at the document root
8
+ # - #parent_node [JSI::PathedNode] returning the parent node of this PathedNode
9
+ # - #deref [JSI::PathedNode] following a $ref
10
+ #
11
+ # given these, this module represents the node in the document at the path.
12
+ #
13
+ # the node content (#node_content) is the result of evaluating the node document at the path.
14
+ module PathedNode
15
+ # @return [Object] the content of this node
16
+ def node_content
17
+ content = node_ptr.evaluate(node_document)
18
+ content
19
+ end
20
+
21
+ # @yield [JSI::JSON::Pointer] if a block is given (optional), this will yield a deref'd pointer
22
+ # @return [JSI::JSON::Pointer] our node_ptr, derefed against our node_document
23
+ def node_ptr_deref(&block)
24
+ node_ptr.deref(node_document, &block)
25
+ end
26
+ end
27
+
28
+ # module extending a {JSI::PathedNode} object when its node_content is Hash-like (responds to #to_hash)
29
+ module PathedHashNode
30
+ # yields each hash key and value of this node.
31
+ #
32
+ # each yielded key is the same as a key of the node content hash,
33
+ # and each yielded value is the result of self[key] (see #[]).
34
+ #
35
+ # returns an Enumerator if no block is given.
36
+ #
37
+ # @yield [Object, Object] each key and value of this hash node
38
+ # @return [self, Enumerator]
39
+ def each(&block)
40
+ return to_enum(__method__) { node_content_hash_pubsend(:size) } unless block_given?
41
+ if block.arity > 1
42
+ node_content_hash_pubsend(:each_key) { |k| yield k, self[k] }
43
+ else
44
+ node_content_hash_pubsend(:each_key) { |k| yield [k, self[k]] }
45
+ end
46
+ self
47
+ end
48
+
49
+ # @return [Hash] a hash in which each key is a key of the node_content hash and
50
+ # each value is the result of self[key] (see #[]).
51
+ def to_hash
52
+ {}.tap { |h| each_key { |k| h[k] = self[k] } }
53
+ end
54
+
55
+ include Hashlike
56
+
57
+ # @param method_name [String, Symbol]
58
+ # @param *a, &b are passed to the invocation of method_name
59
+ # @return [Object] the result of calling method method_name on the node_content or its #to_hash
60
+ def node_content_hash_pubsend(method_name, *a, &b)
61
+ if node_content.respond_to?(method_name)
62
+ node_content.public_send(method_name, *a, &b)
63
+ else
64
+ node_content.to_hash.public_send(method_name, *a, &b)
65
+ end
66
+ end
67
+
68
+ # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_hash)
69
+ SAFE_KEY_ONLY_METHODS.each do |method_name|
70
+ define_method(method_name) do |*a, &b|
71
+ node_content_hash_pubsend(method_name, *a, &b)
72
+ end
73
+ end
74
+ end
75
+
76
+ module PathedArrayNode
77
+ # yields each array element of this node.
78
+ #
79
+ # each yielded element is the result of self[index] for each index of our array (see #[]).
80
+ #
81
+ # returns an Enumerator if no block is given.
82
+ #
83
+ # @yield [Object] each element of this array node
84
+ # @return [self, Enumerator]
85
+ def each
86
+ return to_enum(__method__) { node_content_ary_pubsend(:size) } unless block_given?
87
+ node_content_ary_pubsend(:each_index) { |i| yield(self[i]) }
88
+ self
89
+ end
90
+
91
+ # @return [Array] an array, the same size as the node_content, in which the
92
+ # element at each index is the result of self[index] (see #[])
93
+ def to_ary
94
+ to_a
95
+ end
96
+
97
+ include Arraylike
98
+
99
+ # @param method_name [String, Symbol]
100
+ # @param *a, &b are passed to the invocation of method_name
101
+ # @return [Object] the result of calling method method_name on the node_content or its #to_ary
102
+ def node_content_ary_pubsend(method_name, *a, &b)
103
+ if node_content.respond_to?(method_name)
104
+ node_content.public_send(method_name, *a, &b)
105
+ else
106
+ node_content.to_ary.public_send(method_name, *a, &b)
107
+ end
108
+ end
109
+
110
+ # methods that don't look at the value; can skip the overhead of #[] (invoked by #to_a).
111
+ # we override these methods from Arraylike
112
+ SAFE_INDEX_ONLY_METHODS.each do |method_name|
113
+ define_method(method_name) do |*a, &b|
114
+ node_content_ary_pubsend(method_name, *a, &b)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -1,262 +1,238 @@
1
- require 'jsi/json/node'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module JSI
4
- # JSI::Schema represents a JSON Schema. initialized from a Hash-like schema
5
- # object, JSI::Schema is a relatively simple class to abstract useful methods
6
- # applied to a JSON Schema.
7
- class Schema
8
- include Memoize
4
+ # JSI::Schema is a module which extends instances which represent JSON schemas.
5
+ #
6
+ # the content of an instance which is a JSI::Schema (referred to in this context as schema_content) is
7
+ # expected to be a Hash (JSON object) or a Boolean.
8
+ module Schema
9
+ class Error < StandardError
10
+ end
11
+
12
+ # an exception raised when a thing is expected to be a JSI::Schema, but is not
13
+ class NotASchemaError < Error
14
+ end
15
+
16
+ # JSI::Schema::DescribesSchema: a schema which describes another schema. this module
17
+ # extends a JSI::Schema instance and indicates that JSIs which instantiate the schema
18
+ # are themselves also schemas.
19
+ #
20
+ # examples of a schema which describes a schema include the draft JSON Schema metaschemas and
21
+ # the OpenAPI schema definition which describes "A deterministic version of a JSON Schema object."
22
+ module DescribesSchema
23
+ end
9
24
 
10
25
  class << self
26
+ # @return [JSI::Schema] the default metaschema
27
+ def default_metaschema
28
+ JSI::JSONSchemaOrgDraft06.schema
29
+ end
30
+
31
+ # @return [Array<JSI::Schema>] supported metaschemas
32
+ def supported_metaschemas
33
+ [
34
+ JSI::JSONSchemaOrgDraft04.schema,
35
+ JSI::JSONSchemaOrgDraft06.schema,
36
+ ]
37
+ end
38
+
39
+ # instantiates a given schema object as a JSI::Schema.
40
+ #
41
+ # schemas are instantiated according to their '$schema' property if specified. otherwise their schema
42
+ # will be the {JSI::Schema.default_metaschema}.
43
+ #
44
+ # if the given schema_object is a JSI::Base but not already a JSI::Schema, an error
45
+ # will be raised. JSI::Base _should_ already extend a given instance with JSI::Schema
46
+ # when its schema describes a schema (by extending with JSI::Schema::DescribesSchema).
47
+ #
48
+ # @param schema_object [#to_hash, Boolean, JSI::Schema] an object to be instantiated as a schema.
49
+ # if it's already a schema, it is returned as-is.
50
+ # @return [JSI::Schema] a JSI::Schema representing the given schema_object
11
51
  def from_object(schema_object)
12
52
  if schema_object.is_a?(Schema)
13
53
  schema_object
54
+ elsif schema_object.is_a?(JSI::Base)
55
+ raise(NotASchemaError, "the given schema_object is a JSI::Base, but is not a JSI::Schema: #{schema_object.pretty_inspect.chomp}")
56
+ elsif schema_object.respond_to?(:to_hash)
57
+ schema_object = JSI.deep_stringify_symbol_keys(schema_object)
58
+ if schema_object.key?('$schema') && schema_object['$schema'].respond_to?(:to_str)
59
+ if schema_object['$schema'] == schema_object['$id'] || schema_object['$schema'] == schema_object['id']
60
+ MetaschemaNode.new(schema_object)
61
+ else
62
+ metaschema = supported_metaschemas.detect { |ms| schema_object['$schema'] == ms['$id'] || schema_object['$schema'] == ms['id'] }
63
+ unless metaschema
64
+ raise(NotImplementedError, "metaschema not supported: #{schema_object['$schema']}")
65
+ end
66
+ metaschema.new_jsi(schema_object)
67
+ end
68
+ else
69
+ default_metaschema.new_jsi(schema_object)
70
+ end
71
+ elsif [true, false].include?(schema_object)
72
+ default_metaschema.new_jsi(schema_object)
14
73
  else
15
- new(schema_object)
74
+ raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}")
16
75
  end
17
76
  end
18
- end
19
-
20
- # initializes a schema from a given JSI::Base, JSI::JSON::Node, or hash.
21
- # @param schema_object [JSI::Base, #to_hash] the schema
22
- def initialize(schema_object)
23
- if schema_object.is_a?(JSI::Schema)
24
- raise(TypeError, "will not instantiate Schema from another Schema: #{schema_object.pretty_inspect.chomp}")
25
- elsif schema_object.is_a?(JSI::Base)
26
- @schema_jsi = JSI.deep_stringify_symbol_keys(schema_object.deref)
27
- @schema_node = @schema_jsi.instance
28
- elsif schema_object.is_a?(JSI::JSON::HashNode)
29
- @schema_jsi = nil
30
- @schema_node = JSI.deep_stringify_symbol_keys(schema_object.deref)
31
- elsif schema_object.respond_to?(:to_hash)
32
- @schema_jsi = nil
33
- @schema_node = JSI::JSON::Node.new_doc(JSI.deep_stringify_symbol_keys(schema_object))
34
- else
35
- raise(TypeError, "cannot instantiate Schema from: #{schema_object.pretty_inspect.chomp}")
36
- end
37
- end
38
77
 
39
- # @return [JSI::JSON::Node] a JSI::JSON::Node for the schema
40
- attr_reader :schema_node
41
-
42
- # @return [JSI::Base, nil] a JSI for this schema, if a metaschema is known; otherwise nil
43
- attr_reader :schema_jsi
44
-
45
- # @return [JSI::Base, JSI::JSON::Node] either a JSI::Base subclass or a
46
- # JSI::JSON::Node for the schema
47
- def schema_object
48
- @schema_jsi || @schema_node
78
+ alias_method :new, :from_object
49
79
  end
50
80
 
51
- # @return [JSI::Base, JSI::JSON::Node, Object] property value from the schema_object
52
- # @param property_name [String, Object] property name to access from the schema_object
53
- def [](property_name)
54
- schema_object[property_name]
55
- end
56
-
57
- # @return [String] an absolute id for the schema, with a json pointer fragment
81
+ # @return [String, nil] an absolute id for the schema, with a json pointer fragment. nil if
82
+ # no parent of this schema defines an id.
58
83
  def schema_id
59
- @schema_id ||= begin
60
- # start from schema_node and ascend parents looking for an 'id' property.
84
+ return @schema_id if instance_variable_defined?(:@schema_id)
85
+ @schema_id = begin
86
+ # start from self and ascend parents looking for an 'id' property.
61
87
  # append a fragment to that id (appending to an existing fragment if there
62
88
  # is one) consisting of the path from that parent to our schema_node.
63
- node_for_id = schema_node
89
+ node_for_id = self
64
90
  path_from_id_node = []
65
91
  done = false
66
92
 
67
93
  while !done
68
- # TODO: track what parents are schemas. somehow.
69
- # look at 'id' if node_for_id is a schema, or the document root.
70
- # decide whether to look at '$id' for all parent nodes or also just schemas.
71
- if node_for_id.respond_to?(:to_hash)
72
- if node_for_id.path.empty? || node_for_id.object_id == schema_node.object_id
73
- # I'm only looking at 'id' for the document root and the schema node
74
- # until I track what parents are schemas.
75
- parent_id = node_for_id['$id'] || node_for_id['id']
76
- else
77
- # will look at '$id' everywhere since it is less likely to show up outside schemas than
78
- # 'id', but it will be better to only look at parents that are schemas for this too.
79
- parent_id = node_for_id['$id']
80
- end
94
+ node_content_for_id = node_for_id.node_content
95
+ if node_for_id.is_a?(JSI::Schema) && node_content_for_id.respond_to?(:to_hash)
96
+ parent_id = node_content_for_id.key?('$id') && node_content_for_id['$id'].respond_to?(:to_str) ? node_content_for_id['$id'].to_str :
97
+ node_content_for_id.key?('id') && node_content_for_id['id'].respond_to?(:to_str) ? node_content_for_id['id'].to_str : nil
81
98
  end
82
99
 
83
- if parent_id || node_for_id.path.empty?
100
+ if parent_id || node_for_id.node_ptr.root?
84
101
  done = true
85
102
  else
86
- path_from_id_node.unshift(node_for_id.path.last)
103
+ path_from_id_node.unshift(node_for_id.node_ptr.reference_tokens.last)
87
104
  node_for_id = node_for_id.parent_node
88
105
  end
89
106
  end
90
107
  if parent_id
91
108
  parent_auri = Addressable::URI.parse(parent_id)
109
+ if parent_auri.fragment
110
+ # add onto the fragment
111
+ parent_id_path = JSI::JSON::Pointer.from_fragment(parent_auri.fragment).reference_tokens
112
+ path_from_id_node = parent_id_path + path_from_id_node
113
+ parent_auri.fragment = nil
114
+ #else: no fragment so parent_id good as is
115
+ end
116
+
117
+ schema_id = parent_auri.merge(fragment: JSI::JSON::Pointer.new(path_from_id_node).fragment).to_s
118
+
119
+ schema_id
92
120
  else
93
- node_for_id = schema_node.document_node
94
- validator = ::JSON::Validator.new(node_for_id.content, nil)
95
- # TODO not good instance_exec'ing into another library's ivars
96
- parent_auri = validator.instance_exec { @base_schema }.uri
97
- end
98
- if parent_auri.fragment
99
- # add onto the fragment
100
- parent_id_path = ::JSON::Schema::Pointer.new(:fragment, '#' + parent_auri.fragment).reference_tokens
101
- path_from_id_node = parent_id_path + path_from_id_node
102
- parent_auri.fragment = nil
103
- #else: no fragment so parent_id good as is
121
+ nil
104
122
  end
123
+ end
124
+ end
105
125
 
106
- fragment = ::JSON::Schema::Pointer.new(:reference_tokens, path_from_id_node).fragment
107
- schema_id = parent_auri.to_s + fragment
126
+ # @return [Module] a module representing this schema. see {JSI::SchemaClasses.module_for_schema}.
127
+ def jsi_schema_module
128
+ JSI::SchemaClasses.module_for_schema(self)
129
+ end
108
130
 
109
- schema_id
110
- end
131
+ # @return [Class < JSI::Base] a JSI class for this one schema
132
+ def jsi_schema_class
133
+ JSI.class_for_schemas(Set[self])
134
+ end
135
+
136
+ # instantiates the given other_instance as a JSI::Base class for schemas matched from this schema to the
137
+ # other_instance.
138
+ #
139
+ # any parameters are passed to JSI::Base#initialize, but none are normally used.
140
+ #
141
+ # @return [JSI::Base] a JSI whose instance is the given instance and whose schemas are matched from this
142
+ # schema.
143
+ def new_jsi(other_instance, *a, &b)
144
+ JSI.class_for_schemas(match_to_instance(other_instance)).new(other_instance, *a, &b)
111
145
  end
112
146
 
113
- # @return [Class subclassing JSI::Base] shortcut for JSI.class_for_schema(schema)
114
- def schema_class
115
- JSI.class_for_schema(self)
147
+ # @return [Boolean] does this schema itself describe a schema?
148
+ def describes_schema?
149
+ is_a?(JSI::Schema::DescribesSchema)
116
150
  end
117
151
 
118
- # if this schema is a oneOf, allOf, anyOf schema, #match_to_instance finds
119
- # one of the subschemas that matches the given instance and returns it. if
120
- # there are no matching *Of schemas, this schema is returned.
152
+ # checks this schema for applicators ($ref, allOf, etc.) which should be applied to the given instance.
153
+ # returns these as a Set of {JSI::Schema}s.
121
154
  #
122
- # @param instance [Object] the instance to which to attempt to match *Of subschemas
123
- # @return [JSI::Schema] a matched subschema, or this schema (self)
124
- def match_to_instance(instance)
125
- # matching oneOf is good here. one schema for one instance.
126
- # matching anyOf is okay. there could be more than one schema matched. it's often just one. if more
127
- # than one is a match, the problems of allOf occur.
128
- # matching allOf is questionable. all of the schemas must be matched but we just return the first match.
129
- # there isn't really a better answer with the current implementation. merging the schemas together
130
- # is a thought but is not practical.
131
- instance = instance.deref if instance.is_a?(JSI::JSON::Node)
132
- %w(oneOf allOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |someof_key|
133
- schema_node[someof_key].map(&:deref).map do |someof_node|
134
- someof_schema = self.class.new(someof_node)
135
- if someof_schema.validate(instance)
136
- return someof_schema.match_to_instance(instance)
137
- end
138
- end
139
- end
140
- return self
155
+ # the returned set will contain this schema itself, unless this schema contains a $ref keyword.
156
+ #
157
+ # @param other_instance [Object] the instance to check any applicators against
158
+ # @return [Set<JSI::Schema>] matched applicator schemas
159
+ def match_to_instance(other_instance)
160
+ node_ptr.schema_match_ptrs_to_instance(node_document, other_instance).map do |ptr|
161
+ ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
162
+ end.to_set
141
163
  end
142
164
 
143
- # @param property_name_ [String] the property for which to find a subschema
144
- # @return [JSI::Schema, nil] a subschema from `properties`,
145
- # `patternProperties`, or `additionalProperties` for the given
146
- # property_name
147
- def subschema_for_property(property_name_)
148
- memoize(:subschema_for_property, property_name_) do |property_name|
149
- if schema_object['properties'].respond_to?(:to_hash) && schema_object['properties'][property_name].respond_to?(:to_hash)
150
- self.class.new(schema_object['properties'][property_name])
151
- else
152
- if schema_object['patternProperties'].respond_to?(:to_hash)
153
- _, pattern_schema_object = schema_object['patternProperties'].detect do |pattern, _|
154
- property_name.to_s =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
155
- end
156
- end
157
- if pattern_schema_object
158
- self.class.new(pattern_schema_object)
159
- else
160
- if schema_object['additionalProperties'].respond_to?(:to_hash)
161
- self.class.new(schema_object['additionalProperties'])
162
- else
163
- nil
164
- end
165
- end
166
- end
165
+ # returns a set of subschemas of this schema for the given property name, from keywords
166
+ # `properties`, `patternProperties`, and `additionalProperties`.
167
+ #
168
+ # @param property_name [String] the property name for which to find subschemas
169
+ # @return [Set<JSI::Schema>] subschemas of this schema for the given property_name
170
+ def subschemas_for_property_name(property_name)
171
+ jsi_memoize(:subschemas_for_property_name, property_name) do |property_name|
172
+ node_ptr.schema_subschema_ptrs_for_property_name(node_document, property_name).map do |ptr|
173
+ ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
174
+ end.to_set
167
175
  end
168
176
  end
169
177
 
170
- # @param index_ [Integer] the index for which to find a subschema
171
- # @return [JSI::Schema, nil] a subschema from `items` or
172
- # `additionalItems` for the given index
173
- def subschema_for_index(index_)
174
- memoize(:subschema_for_index, index_) do |index|
175
- if schema_object['items'].respond_to?(:to_ary)
176
- if index < schema_object['items'].size
177
- self.class.new(schema_object['items'][index])
178
- elsif schema_object['additionalItems'].respond_to?(:to_hash)
179
- self.class.new(schema_object['additionalItems'])
180
- end
181
- elsif schema_object['items'].respond_to?(:to_hash)
182
- self.class.new(schema_object['items'])
183
- else
184
- nil
185
- end
178
+ # returns a set of subschemas of this schema for the given array index, from keywords
179
+ # `items` and `additionalItems`.
180
+ #
181
+ # @param index [Integer] the array index for which to find subschemas
182
+ # @return [Set<JSI::Schema>] subschemas of this schema for the given array index
183
+ def subschemas_for_index(index)
184
+ jsi_memoize(:subschemas_for_index, index) do |index|
185
+ node_ptr.schema_subschema_ptrs_for_index(node_document, index).map do |ptr|
186
+ ptr.evaluate(document_root_node).tap { |subschema| jsi_ensure_subschema_is_schema(subschema, ptr) }
187
+ end.to_set
186
188
  end
187
189
  end
188
190
 
189
- # @return [Set] any object property names this schema indicates may be
190
- # present on its instances. this includes, if present: keys of this
191
- # schema's "properties" object; entries of this schema's array of
192
- # "required" property keys. if this schema has oneOf/allOf/anyOf
193
- # subschemas, those schemas are checked (recursively) for their
194
- # described object property names.
191
+ # @return [Set] any object property names this schema indicates may be present on its instances.
192
+ # this includes any keys of this schema's "properties" object and any entries of this schema's
193
+ # array of "required" property keys.
195
194
  def described_object_property_names
196
- memoize(:described_object_property_names) do
195
+ jsi_memoize(:described_object_property_names) do
197
196
  Set.new.tap do |property_names|
198
- if schema_node['properties'].respond_to?(:to_hash)
199
- property_names.merge(schema_node['properties'].keys)
200
- end
201
- if schema_node['required'].respond_to?(:to_ary)
202
- property_names.merge(schema_node['required'].to_ary)
197
+ if node_content.respond_to?(:to_hash) && node_content['properties'].respond_to?(:to_hash)
198
+ property_names.merge(node_content['properties'].keys)
203
199
  end
204
- # we _could_ look at the properties of 'default' and each 'enum' but ... nah.
205
- # we should look at dependencies (TODO).
206
- %w(oneOf allOf anyOf).select { |k| schema_node[k].respond_to?(:to_ary) }.each do |schemas_key|
207
- schema_node[schemas_key].map(&:deref).map do |someof_node|
208
- property_names.merge(self.class.new(someof_node).described_object_property_names)
209
- end
200
+ if node_content.respond_to?(:to_hash) && node_content['required'].respond_to?(:to_ary)
201
+ property_names.merge(node_content['required'].to_ary)
210
202
  end
211
203
  end
212
204
  end
213
205
  end
214
206
 
215
- def default_value
216
- if schema_node.key?('default')
217
- if schema_node['default'].respond_to?(:to_ary) || schema_node['default'].respond_to?(:to_hash)
218
- schema_class.new(schema_node['default'])
219
- else
220
- schema_node['default']
221
- end
222
- else
223
- nil
224
- end
225
- end
226
-
227
- def default_value?
228
- schema_node.key?('default')
229
- end
230
-
231
- # @return [Array<String>] array of schema validation error messages for
207
+ # @return [Array] array of schema validation errors for
232
208
  # the given instance against this schema
233
- def fully_validate(instance)
234
- ::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.document), JSI::Typelike.as_json(instance), fragment: schema_node.fragment)
209
+ def fully_validate_instance(other_instance, errors_as_objects: false)
210
+ ::JSON::Validator.fully_validate(JSI::Typelike.as_json(node_document), JSI::Typelike.as_json(other_instance), fragment: node_ptr.fragment, errors_as_objects: errors_as_objects)
235
211
  end
236
212
 
237
213
  # @return [true, false] whether the given instance validates against this schema
238
- def validate(instance)
239
- ::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.document), JSI::Typelike.as_json(instance), fragment: schema_node.fragment)
214
+ def validate_instance(other_instance)
215
+ ::JSON::Validator.validate(JSI::Typelike.as_json(node_document), JSI::Typelike.as_json(other_instance), fragment: node_ptr.fragment)
240
216
  end
241
217
 
242
218
  # @return [true] if this method does not raise, it returns true to
243
219
  # indicate the instance is valid against this schema
244
220
  # @raise [::JSON::Schema::ValidationError] raises if the instance has
245
221
  # validation errors against this schema
246
- def validate!(instance)
247
- ::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.document), JSI::Typelike.as_json(instance), fragment: schema_node.fragment)
222
+ def validate_instance!(other_instance)
223
+ ::JSON::Validator.validate!(JSI::Typelike.as_json(node_document), JSI::Typelike.as_json(other_instance), fragment: node_ptr.fragment)
248
224
  end
249
225
 
250
- # @return [Array<String>] array of schema validation error messages for
226
+ # @return [Array] array of schema validation errors for
251
227
  # this schema, validated against its metaschema. a default metaschema
252
228
  # is assumed if the schema does not specify a $schema.
253
- def fully_validate_schema
254
- ::JSON::Validator.fully_validate(JSI::Typelike.as_json(schema_node.document), [], fragment: schema_node.fragment, validate_schema: true, list: true)
229
+ def fully_validate_schema(errors_as_objects: false)
230
+ ::JSON::Validator.fully_validate(JSI::Typelike.as_json(node_document), [], fragment: node_ptr.fragment, validate_schema: true, list: true, errors_as_objects: errors_as_objects)
255
231
  end
256
232
 
257
233
  # @return [true, false] whether this schema validates against its metaschema
258
234
  def validate_schema
259
- ::JSON::Validator.validate(JSI::Typelike.as_json(schema_node.document), [], fragment: schema_node.fragment, validate_schema: true, list: true)
235
+ ::JSON::Validator.validate(JSI::Typelike.as_json(node_document), [], fragment: node_ptr.fragment, validate_schema: true, list: true)
260
236
  end
261
237
 
262
238
  # @return [true] if this method does not raise, it returns true to
@@ -264,45 +240,14 @@ module JSI
264
240
  # @raise [::JSON::Schema::ValidationError] raises if this schema has
265
241
  # validation errors against its metaschema
266
242
  def validate_schema!
267
- ::JSON::Validator.validate!(JSI::Typelike.as_json(schema_node.document), [], fragment: schema_node.fragment, validate_schema: true, list: true)
268
- end
269
-
270
- # @return [String] a string for #instance and #pretty_print including the schema_id
271
- def object_group_text
272
- "schema_id=#{schema_id}"
273
- end
274
-
275
- # @return [String] a string representing this Schema
276
- def inspect
277
- "\#<#{self.class.inspect} #{object_group_text} #{schema_object.inspect}>"
243
+ ::JSON::Validator.validate!(JSI::Typelike.as_json(node_document), [], fragment: node_ptr.fragment, validate_schema: true, list: true)
278
244
  end
279
- alias_method :to_s, :inspect
280
245
 
281
- # pretty-prints a representation this Schema to the given printer
282
- # @return [void]
283
- def pretty_print(q)
284
- q.instance_exec(self) do |obj|
285
- text "\#<#{obj.class.inspect} #{obj.object_group_text}"
286
- group_sub {
287
- nest(2) {
288
- breakable ' '
289
- pp obj.schema_object
290
- }
291
- }
292
- breakable ''
293
- text '>'
246
+ private
247
+ def jsi_ensure_subschema_is_schema(subschema, ptr)
248
+ unless subschema.is_a?(JSI::Schema)
249
+ raise(NotASchemaError, "subschema not a schema at ptr #{ptr.inspect}: #{subschema.pretty_inspect.chomp}")
294
250
  end
295
251
  end
296
-
297
- # @return [Object] returns a jsonifiable representation of this schema
298
- def as_json(*opt)
299
- Typelike.as_json(schema_object, *opt)
300
- end
301
-
302
- # @return [Object] an opaque fingerprint of this Schema for FingerprintHash
303
- def fingerprint
304
- {class: self.class, schema_node: schema_node}
305
- end
306
- include FingerprintHash
307
252
  end
308
253
  end