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.
- checksums.yaml +4 -4
- data/.simplecov +3 -1
- data/CHANGELOG.md +48 -0
- data/LICENSE.md +613 -0
- data/README.md +84 -45
- data/jsi.gemspec +11 -14
- data/lib/jsi.rb +31 -12
- data/lib/jsi/base.rb +310 -344
- data/lib/jsi/base/to_rb.rb +2 -0
- data/lib/jsi/jsi_coder.rb +91 -0
- data/lib/jsi/json-schema-fragments.rb +3 -135
- data/lib/jsi/json.rb +3 -0
- data/lib/jsi/json/node.rb +72 -197
- data/lib/jsi/json/pointer.rb +419 -0
- data/lib/jsi/metaschema.rb +7 -0
- data/lib/jsi/metaschema_node.rb +218 -0
- data/lib/jsi/pathed_node.rb +118 -0
- data/lib/jsi/schema.rb +168 -223
- data/lib/jsi/schema_classes.rb +158 -0
- data/lib/jsi/simple_wrap.rb +12 -0
- data/lib/jsi/typelike_modules.rb +71 -45
- data/lib/jsi/util.rb +47 -57
- data/lib/jsi/version.rb +1 -1
- data/lib/schemas/json-schema.org/draft-04/schema.rb +7 -0
- data/lib/schemas/json-schema.org/draft-06/schema.rb +7 -0
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +210 -84
- data/test/base_hash_test.rb +201 -58
- data/test/base_test.rb +212 -121
- data/test/jsi_coder_test.rb +85 -0
- data/test/jsi_json_arraynode_test.rb +26 -25
- data/test/jsi_json_hashnode_test.rb +40 -39
- data/test/jsi_json_node_test.rb +95 -126
- data/test/jsi_json_pointer_test.rb +102 -0
- data/test/jsi_typelike_as_json_test.rb +53 -0
- data/test/metaschema_node_test.rb +19 -0
- data/test/schema_module_test.rb +21 -0
- data/test/schema_test.rb +109 -97
- data/test/spreedly_openapi_test.rb +8 -0
- data/test/test_helper.rb +42 -8
- data/test/util_test.rb +14 -14
- metadata +54 -25
- data/LICENSE.txt +0 -21
- data/lib/jsi/schema_instance_json_coder.rb +0 -83
- data/lib/jsi/struct_json_coder.rb +0 -30
- data/test/schema_instance_json_coder_test.rb +0 -121
- 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
|
data/lib/jsi/schema.rb
CHANGED
@@ -1,262 +1,238 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module JSI
|
4
|
-
# JSI::Schema
|
5
|
-
#
|
6
|
-
#
|
7
|
-
|
8
|
-
|
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
|
-
|
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
|
-
|
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 [
|
52
|
-
#
|
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
|
60
|
-
|
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 =
|
89
|
+
node_for_id = self
|
64
90
|
path_from_id_node = []
|
65
91
|
done = false
|
66
92
|
|
67
93
|
while !done
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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.
|
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.
|
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
|
-
|
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
|
-
|
107
|
-
|
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
|
-
|
110
|
-
|
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 [
|
114
|
-
def
|
115
|
-
JSI
|
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
|
-
#
|
119
|
-
#
|
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
|
-
#
|
123
|
-
#
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
-
#
|
144
|
-
#
|
145
|
-
#
|
146
|
-
#
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
-
#
|
171
|
-
#
|
172
|
-
#
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
#
|
191
|
-
#
|
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
|
-
|
195
|
+
jsi_memoize(:described_object_property_names) do
|
197
196
|
Set.new.tap do |property_names|
|
198
|
-
if
|
199
|
-
property_names.merge(
|
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
|
-
|
205
|
-
|
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
|
-
|
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
|
234
|
-
::JSON::Validator.fully_validate(JSI::Typelike.as_json(
|
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
|
239
|
-
::JSON::Validator.validate(JSI::Typelike.as_json(
|
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
|
247
|
-
::JSON::Validator.validate!(JSI::Typelike.as_json(
|
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
|
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(
|
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(
|
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(
|
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
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
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
|