jsi 0.0.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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