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.
- checksums.yaml +4 -4
- data/.yardopts +1 -1
- data/CHANGELOG.md +15 -0
- data/README.md +105 -38
- data/lib/jsi/base.rb +349 -155
- data/lib/jsi/jsi_coder.rb +5 -4
- data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
- data/lib/jsi/metaschema_node.rb +156 -129
- data/lib/jsi/pathed_node.rb +47 -49
- data/lib/jsi/ptr.rb +292 -0
- data/lib/jsi/schema/application/child_application/contains.rb +16 -0
- data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
- data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
- data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
- data/lib/jsi/schema/application/child_application/items.rb +18 -0
- data/lib/jsi/schema/application/child_application/properties.rb +25 -0
- data/lib/jsi/schema/application/child_application.rb +40 -0
- data/lib/jsi/schema/application/draft04.rb +8 -0
- data/lib/jsi/schema/application/draft06.rb +8 -0
- data/lib/jsi/schema/application/draft07.rb +8 -0
- data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
- data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
- data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
- data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
- data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
- data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
- data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
- data/lib/jsi/schema/application/inplace_application.rb +46 -0
- data/lib/jsi/schema/application.rb +12 -0
- data/lib/jsi/schema/draft04.rb +14 -0
- data/lib/jsi/schema/draft06.rb +14 -0
- data/lib/jsi/schema/draft07.rb +14 -0
- data/lib/jsi/schema/issue.rb +36 -0
- data/lib/jsi/schema/ref.rb +159 -0
- data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
- data/lib/jsi/schema/validation/array.rb +69 -0
- data/lib/jsi/schema/validation/const.rb +20 -0
- data/lib/jsi/schema/validation/contains.rb +25 -0
- data/lib/jsi/schema/validation/core.rb +39 -0
- data/lib/jsi/schema/validation/dependencies.rb +49 -0
- data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
- data/lib/jsi/schema/validation/draft04.rb +112 -0
- data/lib/jsi/schema/validation/draft06.rb +122 -0
- data/lib/jsi/schema/validation/draft07.rb +159 -0
- data/lib/jsi/schema/validation/enum.rb +25 -0
- data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
- data/lib/jsi/schema/validation/items.rb +54 -0
- data/lib/jsi/schema/validation/not.rb +20 -0
- data/lib/jsi/schema/validation/numeric.rb +121 -0
- data/lib/jsi/schema/validation/object.rb +45 -0
- data/lib/jsi/schema/validation/pattern.rb +34 -0
- data/lib/jsi/schema/validation/properties.rb +101 -0
- data/lib/jsi/schema/validation/property_names.rb +32 -0
- data/lib/jsi/schema/validation/ref.rb +40 -0
- data/lib/jsi/schema/validation/required.rb +27 -0
- data/lib/jsi/schema/validation/someof.rb +90 -0
- data/lib/jsi/schema/validation/string.rb +47 -0
- data/lib/jsi/schema/validation/type.rb +49 -0
- data/lib/jsi/schema/validation.rb +51 -0
- data/lib/jsi/schema.rb +486 -133
- data/lib/jsi/schema_classes.rb +157 -42
- data/lib/jsi/schema_registry.rb +141 -0
- data/lib/jsi/schema_set.rb +141 -0
- data/lib/jsi/simple_wrap.rb +2 -2
- data/lib/jsi/typelike_modules.rb +52 -37
- data/lib/jsi/util/attr_struct.rb +106 -0
- data/lib/jsi/util.rb +141 -25
- data/lib/jsi/validation/error.rb +34 -0
- data/lib/jsi/validation/result.rb +210 -0
- data/lib/jsi/validation.rb +15 -0
- data/lib/jsi/version.rb +3 -1
- data/lib/jsi.rb +55 -9
- data/lib/schemas/json-schema.org/draft-04/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-06/schema.rb +8 -3
- data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
- data/readme.rb +138 -0
- data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
- data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
- data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
- metadata +69 -118
- data/.simplecov +0 -3
- data/Rakefile.rb +0 -9
- data/jsi.gemspec +0 -28
- data/lib/jsi/base/to_rb.rb +0 -128
- data/lib/jsi/json/node.rb +0 -203
- data/lib/jsi/json/pointer.rb +0 -419
- data/lib/jsi/json-schema-fragments.rb +0 -61
- data/lib/jsi/json.rb +0 -10
- data/resources/icons/AGPL-3.0.png +0 -0
- data/test/base_array_test.rb +0 -323
- data/test/base_hash_test.rb +0 -337
- data/test/base_test.rb +0 -486
- data/test/jsi_coder_test.rb +0 -85
- data/test/jsi_json_arraynode_test.rb +0 -150
- data/test/jsi_json_hashnode_test.rb +0 -132
- data/test/jsi_json_node_test.rb +0 -257
- data/test/jsi_json_pointer_test.rb +0 -102
- data/test/jsi_test.rb +0 -11
- data/test/jsi_typelike_as_json_test.rb +0 -53
- data/test/metaschema_node_test.rb +0 -19
- data/test/schema_module_test.rb +0 -21
- data/test/schema_test.rb +0 -208
- data/test/spreedly_openapi_test.rb +0 -8
- data/test/test_helper.rb +0 -97
- data/test/util_test.rb +0 -62
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication::Dependencies
|
|
5
|
+
# @private
|
|
6
|
+
def internal_applicate_dependencies(instance, visited_refs, &block)
|
|
7
|
+
if schema_content.key?('dependencies')
|
|
8
|
+
value = schema_content['dependencies']
|
|
9
|
+
# This keyword's value MUST be an object. Each property specifies a dependency. Each dependency
|
|
10
|
+
# value MUST be an array or a valid JSON Schema.
|
|
11
|
+
if value.respond_to?(:to_hash)
|
|
12
|
+
value.each_pair do |property_name, dependency|
|
|
13
|
+
if dependency.respond_to?(:to_ary)
|
|
14
|
+
# noop: array-form dependencies has no inplace applicator schema
|
|
15
|
+
else
|
|
16
|
+
# If the dependency value is a subschema, and the dependency key is a
|
|
17
|
+
# property in the instance, the entire instance must validate against
|
|
18
|
+
# the dependency value.
|
|
19
|
+
if instance.respond_to?(:to_hash) && instance.key?(property_name)
|
|
20
|
+
subschema(['dependencies', property_name]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication::Draft04
|
|
5
|
+
include Schema::Application::InplaceApplication
|
|
6
|
+
include Schema::Application::InplaceApplication::Ref
|
|
7
|
+
include Schema::Application::InplaceApplication::Dependencies
|
|
8
|
+
include Schema::Application::InplaceApplication::SomeOf
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
def internal_inplace_applicate_keywords(instance, visited_refs, &block)
|
|
12
|
+
internal_applicate_ref(instance, visited_refs, throw_done: true, &block)
|
|
13
|
+
|
|
14
|
+
# self is the first applicator schema if $ref has not short-circuited it
|
|
15
|
+
yield self
|
|
16
|
+
|
|
17
|
+
# 5.4.5. dependencies
|
|
18
|
+
internal_applicate_dependencies(instance, visited_refs, &block)
|
|
19
|
+
|
|
20
|
+
# 5.5.3. allOf
|
|
21
|
+
# 5.5.4. anyOf
|
|
22
|
+
# 5.5.5. oneOf
|
|
23
|
+
internal_applicate_someOf(instance, visited_refs, &block)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication::Draft06
|
|
5
|
+
include Schema::Application::InplaceApplication
|
|
6
|
+
include Schema::Application::InplaceApplication::Ref
|
|
7
|
+
include Schema::Application::InplaceApplication::Dependencies
|
|
8
|
+
include Schema::Application::InplaceApplication::SomeOf
|
|
9
|
+
|
|
10
|
+
# @private
|
|
11
|
+
def internal_inplace_applicate_keywords(instance, visited_refs, &block)
|
|
12
|
+
# json-schema 8. Schema references with $ref
|
|
13
|
+
internal_applicate_ref(instance, visited_refs, throw_done: true, &block)
|
|
14
|
+
|
|
15
|
+
# self is the first applicator schema if $ref has not short-circuited it
|
|
16
|
+
yield self
|
|
17
|
+
|
|
18
|
+
# json-schema-validation 6.21. dependencies
|
|
19
|
+
internal_applicate_dependencies(instance, visited_refs, &block)
|
|
20
|
+
|
|
21
|
+
# json-schema-validation 6.26. allOf
|
|
22
|
+
# json-schema-validation 6.27. anyOf
|
|
23
|
+
# json-schema-validation 6.28. oneOf
|
|
24
|
+
internal_applicate_someOf(instance, visited_refs, &block)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication::Draft07
|
|
5
|
+
include Schema::Application::InplaceApplication
|
|
6
|
+
include Schema::Application::InplaceApplication::Ref
|
|
7
|
+
include Schema::Application::InplaceApplication::Dependencies
|
|
8
|
+
include Schema::Application::InplaceApplication::IfThenElse
|
|
9
|
+
include Schema::Application::InplaceApplication::SomeOf
|
|
10
|
+
|
|
11
|
+
# @private
|
|
12
|
+
def internal_inplace_applicate_keywords(instance, visited_refs, &block)
|
|
13
|
+
# json-schema 8. Schema references with $ref
|
|
14
|
+
internal_applicate_ref(instance, visited_refs, throw_done: true, &block)
|
|
15
|
+
|
|
16
|
+
# self is the first applicator schema if $ref has not short-circuited it
|
|
17
|
+
block.call(self)
|
|
18
|
+
|
|
19
|
+
# 6.5.7. dependencies
|
|
20
|
+
internal_applicate_dependencies(instance, visited_refs, &block)
|
|
21
|
+
|
|
22
|
+
# 6.6.1. if
|
|
23
|
+
# 6.6.2. then
|
|
24
|
+
# 6.6.3. else
|
|
25
|
+
internal_applicate_ifthenelse(instance, visited_refs, &block)
|
|
26
|
+
|
|
27
|
+
# 6.7.1. allOf
|
|
28
|
+
# 6.7.2. anyOf
|
|
29
|
+
# 6.7.3. oneOf
|
|
30
|
+
internal_applicate_someOf(instance, visited_refs, &block)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication::IfThenElse
|
|
5
|
+
# @private
|
|
6
|
+
def internal_applicate_ifthenelse(instance, visited_refs, &block)
|
|
7
|
+
if schema_content.key?('if')
|
|
8
|
+
if subschema(['if']).instance_valid?(instance)
|
|
9
|
+
if schema_content.key?('then')
|
|
10
|
+
subschema(['then']).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
|
|
11
|
+
end
|
|
12
|
+
else
|
|
13
|
+
if schema_content.key?('else')
|
|
14
|
+
subschema(['else']).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication::Ref
|
|
5
|
+
# @private
|
|
6
|
+
def internal_applicate_ref(instance, visited_refs, throw_done: false, &block)
|
|
7
|
+
if schema_content['$ref'].respond_to?(:to_str)
|
|
8
|
+
ref = jsi_memoize(:ref) { Schema::Ref.new(schema_content['$ref'], self) }
|
|
9
|
+
unless visited_refs.include?(ref)
|
|
10
|
+
ref.deref_schema.each_inplace_applicator_schema(instance, visited_refs: visited_refs + [ref], &block)
|
|
11
|
+
if throw_done
|
|
12
|
+
throw(:jsi_application_done)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication::SomeOf
|
|
5
|
+
# @private
|
|
6
|
+
def internal_applicate_someOf(instance, visited_refs, &block)
|
|
7
|
+
if schema_content['allOf'].respond_to?(:to_ary)
|
|
8
|
+
schema_content['allOf'].each_index do |i|
|
|
9
|
+
subschema(['allOf', i]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
if schema_content['anyOf'].respond_to?(:to_ary)
|
|
13
|
+
schema_content['anyOf'].each_index do |i|
|
|
14
|
+
if subschema(['anyOf', i]).instance_valid?(instance)
|
|
15
|
+
subschema(['anyOf', i]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
if schema_content['oneOf'].respond_to?(:to_ary)
|
|
20
|
+
one_i = schema_content['oneOf'].each_index.detect do |i|
|
|
21
|
+
subschema(['oneOf', i]).instance_valid?(instance)
|
|
22
|
+
end
|
|
23
|
+
if one_i
|
|
24
|
+
subschema(['oneOf', one_i]).each_inplace_applicator_schema(instance, visited_refs: visited_refs, &block)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application::InplaceApplication
|
|
5
|
+
autoload :Draft04, 'jsi/schema/application/inplace_application/draft04'
|
|
6
|
+
autoload :Draft06, 'jsi/schema/application/inplace_application/draft06'
|
|
7
|
+
autoload :Draft07, 'jsi/schema/application/inplace_application/draft07'
|
|
8
|
+
|
|
9
|
+
autoload :Ref, 'jsi/schema/application/inplace_application/ref'
|
|
10
|
+
autoload :SomeOf, 'jsi/schema/application/inplace_application/someof'
|
|
11
|
+
autoload :IfThenElse, 'jsi/schema/application/inplace_application/ifthenelse'
|
|
12
|
+
autoload :Dependencies, 'jsi/schema/application/inplace_application/dependencies'
|
|
13
|
+
|
|
14
|
+
# a set of inplace applicator schemas of this schema (from $ref, allOf, etc.) which apply to the
|
|
15
|
+
# given instance.
|
|
16
|
+
#
|
|
17
|
+
# the returned set will contain this schema itself, unless this schema contains a $ref keyword.
|
|
18
|
+
#
|
|
19
|
+
# @param instance [Object] the instance to check any applicators against
|
|
20
|
+
# @return [JSI::SchemaSet] matched applicator schemas
|
|
21
|
+
def inplace_applicator_schemas(instance)
|
|
22
|
+
SchemaSet.new(each_inplace_applicator_schema(instance))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# yields each inplace applicator schema which applies to the given instance.
|
|
26
|
+
#
|
|
27
|
+
# @param instance (see #inplace_applicator_schemas)
|
|
28
|
+
# @param visited_refs [Enumerable<JSI::Schema::Ref>]
|
|
29
|
+
# @yield [JSI::Schema]
|
|
30
|
+
# @return [nil, Enumerator] an Enumerator if invoked without a block; otherwise nil
|
|
31
|
+
def each_inplace_applicator_schema(instance, visited_refs: [], &block)
|
|
32
|
+
return to_enum(__method__, instance, visited_refs: visited_refs) unless block
|
|
33
|
+
|
|
34
|
+
catch(:jsi_application_done) do
|
|
35
|
+
if schema_content.respond_to?(:to_hash)
|
|
36
|
+
internal_inplace_applicate_keywords(instance, visited_refs, &block)
|
|
37
|
+
else
|
|
38
|
+
# self is the only applicator schema if there are no keywords
|
|
39
|
+
yield self
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Application
|
|
5
|
+
autoload :InplaceApplication, 'jsi/schema/application/inplace_application'
|
|
6
|
+
autoload :ChildApplication, 'jsi/schema/application/child_application'
|
|
7
|
+
|
|
8
|
+
autoload :Draft04, 'jsi/schema/application/draft04'
|
|
9
|
+
autoload :Draft06, 'jsi/schema/application/draft06'
|
|
10
|
+
autoload :Draft07, 'jsi/schema/application/draft07'
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema
|
|
5
|
+
module Draft04
|
|
6
|
+
include Schema
|
|
7
|
+
include OldId
|
|
8
|
+
include IdWithAnchor
|
|
9
|
+
include IntegerDisallows0Fraction
|
|
10
|
+
include Schema::Application::Draft04
|
|
11
|
+
include Schema::Validation::Draft04
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema
|
|
5
|
+
module Draft06
|
|
6
|
+
include Schema
|
|
7
|
+
include BigMoneyId
|
|
8
|
+
include IdWithAnchor
|
|
9
|
+
include IntegerAllows0Fraction
|
|
10
|
+
include Schema::Application::Draft06
|
|
11
|
+
include Schema::Validation::Draft06
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema
|
|
5
|
+
module Draft07
|
|
6
|
+
include Schema
|
|
7
|
+
include BigMoneyId
|
|
8
|
+
include IdWithAnchor
|
|
9
|
+
include IntegerAllows0Fraction
|
|
10
|
+
include Schema::Application::Draft07
|
|
11
|
+
include Schema::Validation::Draft07
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema
|
|
5
|
+
Issue = Util::AttrStruct[*%w(
|
|
6
|
+
level
|
|
7
|
+
message
|
|
8
|
+
keyword
|
|
9
|
+
schema
|
|
10
|
+
)]
|
|
11
|
+
|
|
12
|
+
# an issue or problem with a schema.
|
|
13
|
+
#
|
|
14
|
+
# when the `level` is `:error`, the schema is invalid according to its specification,
|
|
15
|
+
# violating some "MUST" or "MUST NOT".
|
|
16
|
+
#
|
|
17
|
+
# when the `level` is `:warning`, the issue does not mean the schema is invalid, but contains something
|
|
18
|
+
# that does not make sense. for example, specifying `additionalItems` without an adjacent `items` has
|
|
19
|
+
# no effect (in specifications which define `additionalItems`), but is not an invalid schema.
|
|
20
|
+
#
|
|
21
|
+
# @!attribute level
|
|
22
|
+
# :error or :warning
|
|
23
|
+
# @return [Symbol]
|
|
24
|
+
# @!attribute message
|
|
25
|
+
# a message describing the issue
|
|
26
|
+
# @return [String]
|
|
27
|
+
# @!attribute keyword
|
|
28
|
+
# the keyword of the schema that has an issue
|
|
29
|
+
# @return [String]
|
|
30
|
+
# @!attribute schema
|
|
31
|
+
# the schema that has an issue
|
|
32
|
+
# @return [JSI::Schema]
|
|
33
|
+
class Issue
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
# JSI::Schema::Ref is a reference to another schema (the result of #deref_schema), resolved using a ref URI
|
|
5
|
+
# from a ref schema (the ref URI typically the contents of the ref_schema's "$ref" keyword)
|
|
6
|
+
class Schema::Ref
|
|
7
|
+
# @param ref [String] a reference URI
|
|
8
|
+
# @param ref_schema [JSI::Schema] a schema from which the reference originated
|
|
9
|
+
def initialize(ref, ref_schema = nil)
|
|
10
|
+
raise(ArgumentError, "ref is not a string") unless ref.respond_to?(:to_str)
|
|
11
|
+
@ref = ref
|
|
12
|
+
@ref_uri = Addressable::URI.parse(ref)
|
|
13
|
+
@ref_schema = ref_schema ? Schema.ensure_schema(ref_schema) : nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
attr_reader :ref
|
|
17
|
+
|
|
18
|
+
attr_reader :ref_uri
|
|
19
|
+
|
|
20
|
+
attr_reader :ref_schema
|
|
21
|
+
|
|
22
|
+
# finds the schema this ref points to
|
|
23
|
+
# @return [JSI::Schema]
|
|
24
|
+
# @raise [JSI::Schema::NotASchemaError] when the thing this ref points to is not a schema
|
|
25
|
+
# @raise [JSI::Schema::ReferenceError] when this reference cannot be resolved
|
|
26
|
+
def deref_schema
|
|
27
|
+
return @deref_schema if instance_variable_defined?(:@deref_schema)
|
|
28
|
+
|
|
29
|
+
schema_resource_root = nil
|
|
30
|
+
check_schema_resource_root = -> {
|
|
31
|
+
unless schema_resource_root
|
|
32
|
+
raise(Schema::ReferenceError, [
|
|
33
|
+
"cannot find schema by ref: #{ref}",
|
|
34
|
+
("from: #{ref_schema.pretty_inspect.chomp}" if ref_schema),
|
|
35
|
+
].compact.join("\n"))
|
|
36
|
+
end
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ref_uri_nofrag = ref_uri.merge(fragment: nil)
|
|
40
|
+
|
|
41
|
+
if ref_uri_nofrag.empty?
|
|
42
|
+
unless ref_schema
|
|
43
|
+
raise(Schema::ReferenceError, [
|
|
44
|
+
"cannot find schema by ref: #{ref}",
|
|
45
|
+
"with no ref schema",
|
|
46
|
+
].join("\n"))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# the URI only consists of a fragment (or is empty).
|
|
50
|
+
# for a fragment pointer, resolve using Schema#resource_root_subschema on the ref_schema.
|
|
51
|
+
# for a fragment anchor, bootstrap does not support anchors; otherwise use the ref_schema's schema_resource_root.
|
|
52
|
+
schema_resource_root = ref_schema.is_a?(MetaschemaNode::BootstrapSchema) ? nil : ref_schema.schema_resource_root
|
|
53
|
+
resolve_fragment_ptr = ref_schema.method(:resource_root_subschema)
|
|
54
|
+
else
|
|
55
|
+
# find the schema_resource_root from the non-fragment URI. we will resolve any fragment, either pointer or anchor, from there.
|
|
56
|
+
schema_resource_root = nil
|
|
57
|
+
|
|
58
|
+
if ref_uri_nofrag.absolute?
|
|
59
|
+
ref_abs_uri = ref_uri_nofrag
|
|
60
|
+
elsif ref_schema && ref_schema.jsi_resource_ancestor_uri
|
|
61
|
+
ref_abs_uri = ref_schema.jsi_resource_ancestor_uri.join(ref_uri_nofrag)
|
|
62
|
+
else
|
|
63
|
+
ref_abs_uri = nil
|
|
64
|
+
end
|
|
65
|
+
if ref_abs_uri
|
|
66
|
+
schema_resource_root = JSI.schema_registry.find(ref_abs_uri)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
unless schema_resource_root
|
|
70
|
+
# HAX for how google does refs and ids
|
|
71
|
+
if ref_schema && ref_schema.jsi_document.respond_to?(:to_hash) && ref_schema.jsi_document['schemas'].respond_to?(:to_hash)
|
|
72
|
+
ref_schema.jsi_document['schemas'].each_key do |k|
|
|
73
|
+
if Addressable::URI.parse(ref_schema.jsi_document['schemas'][k]['id']) == ref_uri_nofrag
|
|
74
|
+
schema_resource_root = ref_schema.resource_root_subschema(['schemas', k])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
check_schema_resource_root.call
|
|
81
|
+
|
|
82
|
+
if schema_resource_root.is_a?(Schema)
|
|
83
|
+
resolve_fragment_ptr = schema_resource_root.method(:resource_root_subschema)
|
|
84
|
+
else
|
|
85
|
+
# Note: Schema#resource_root_subschema will reinstantiate nonschemas as schemas.
|
|
86
|
+
# not implemented for remote refs when the schema_resource_root is not a schema.
|
|
87
|
+
resolve_fragment_ptr = -> (ptr) { ptr.evaluate(schema_resource_root) }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
fragment = ref_uri.fragment
|
|
92
|
+
|
|
93
|
+
if fragment
|
|
94
|
+
begin
|
|
95
|
+
ptr_from_fragment = Ptr.from_fragment(fragment)
|
|
96
|
+
rescue Ptr::PointerSyntaxError
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if ptr_from_fragment
|
|
101
|
+
begin
|
|
102
|
+
result_schema = resolve_fragment_ptr.call(ptr_from_fragment)
|
|
103
|
+
rescue Ptr::ResolutionError
|
|
104
|
+
raise(Schema::ReferenceError, [
|
|
105
|
+
"could not resolve pointer: #{ptr_from_fragment.pointer.inspect}",
|
|
106
|
+
("from: #{ref_schema.pretty_inspect.chomp}" if ref_schema),
|
|
107
|
+
("in schema resource root: #{schema_resource_root.pretty_inspect.chomp}" if schema_resource_root),
|
|
108
|
+
].compact.join("\n"))
|
|
109
|
+
end
|
|
110
|
+
elsif fragment.nil?
|
|
111
|
+
check_schema_resource_root.call
|
|
112
|
+
result_schema = schema_resource_root
|
|
113
|
+
else
|
|
114
|
+
check_schema_resource_root.call
|
|
115
|
+
|
|
116
|
+
# find an anchor that resembles the fragment
|
|
117
|
+
result_schemas = schema_resource_root.jsi_anchor_subschemas(fragment)
|
|
118
|
+
|
|
119
|
+
if result_schemas.size == 1
|
|
120
|
+
result_schema = result_schemas.first
|
|
121
|
+
elsif result_schemas.size == 0
|
|
122
|
+
raise(Schema::ReferenceError, [
|
|
123
|
+
"could not find schema by fragment: #{fragment.inspect}",
|
|
124
|
+
"in schema resource root: #{schema_resource_root.pretty_inspect.chomp}",
|
|
125
|
+
].join("\n"))
|
|
126
|
+
else
|
|
127
|
+
raise(Schema::ReferenceError, [
|
|
128
|
+
"found multiple schemas for plain name fragment #{fragment.inspect}:",
|
|
129
|
+
*result_schemas.map { |s| s.pretty_inspect.chomp },
|
|
130
|
+
].join("\n"))
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
Schema.ensure_schema(result_schema, msg: "object identified by uri #{ref} is not a schema:")
|
|
135
|
+
return @deref_schema = result_schema
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# @return [String]
|
|
139
|
+
def inspect
|
|
140
|
+
%Q(\#<#{self.class.name} #{ref}>)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# pretty-prints a representation of self to the given printer
|
|
144
|
+
# @return [void]
|
|
145
|
+
def pretty_print(q)
|
|
146
|
+
q.text '#<'
|
|
147
|
+
q.text self.class.name
|
|
148
|
+
q.text ' '
|
|
149
|
+
q.text ref
|
|
150
|
+
q.text '>'
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# @private
|
|
154
|
+
def jsi_fingerprint
|
|
155
|
+
{class: self.class, ref: ref, ref_schema: ref_schema}
|
|
156
|
+
end
|
|
157
|
+
include Util::FingerprintHash
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
# a node in a document which may contain a schema somewhere within is extended with SchemaAncestorNode, for
|
|
5
|
+
# tracking things necessary for a schema to function correctly
|
|
6
|
+
module Schema::SchemaAncestorNode
|
|
7
|
+
# the base URI used to resolve the ids of schemas at or below this JSI.
|
|
8
|
+
# this is always an absolute URI (with no fragment).
|
|
9
|
+
# this may be the absolute schema URI of a parent schema or the URI from which the document was retrieved.
|
|
10
|
+
# @private
|
|
11
|
+
# @return [Addressable::URI, nil]
|
|
12
|
+
attr_reader :jsi_schema_base_uri
|
|
13
|
+
|
|
14
|
+
# resources which are ancestors of this JSI in the document. this does not include self.
|
|
15
|
+
# @private
|
|
16
|
+
# @return [Array<JSI::Schema>]
|
|
17
|
+
def jsi_schema_resource_ancestors
|
|
18
|
+
return @jsi_schema_resource_ancestors if instance_variable_defined?(:@jsi_schema_resource_ancestors)
|
|
19
|
+
[].freeze
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# the URI of the resource containing this node.
|
|
23
|
+
# this is always an absolute URI (with no fragment).
|
|
24
|
+
# if this node is a schema with an id, this is its absolute URI; otherwise a parent resource's URI,
|
|
25
|
+
# or nil if not contained by a resource with a URI.
|
|
26
|
+
# @return [Addressable::URI, nil]
|
|
27
|
+
def jsi_resource_ancestor_uri
|
|
28
|
+
if is_a?(Schema) && schema_absolute_uri
|
|
29
|
+
schema_absolute_uri
|
|
30
|
+
else
|
|
31
|
+
jsi_schema_base_uri
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# a schema at or below this node with the given anchor.
|
|
36
|
+
#
|
|
37
|
+
# @return [JSI::Schema, nil]
|
|
38
|
+
def jsi_anchor_subschema(anchor)
|
|
39
|
+
subschemas = jsi_anchor_subschemas_map[anchor]
|
|
40
|
+
if subschemas.size == 1
|
|
41
|
+
subschemas.first
|
|
42
|
+
else
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# schemas at or below node with the given anchor.
|
|
48
|
+
#
|
|
49
|
+
# @return [Array<JSI::Schema>]
|
|
50
|
+
def jsi_anchor_subschemas(anchor)
|
|
51
|
+
jsi_anchor_subschemas_map[anchor]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def jsi_document=(jsi_document)
|
|
57
|
+
@jsi_document = jsi_document
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def jsi_ptr=(jsi_ptr)
|
|
61
|
+
unless jsi_ptr.is_a?(Ptr)
|
|
62
|
+
raise(TypeError, "jsi_ptr must be a JSI::Ptr; got: #{jsi_ptr.inspect}")
|
|
63
|
+
end
|
|
64
|
+
@jsi_ptr = jsi_ptr
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def jsi_schema_base_uri=(jsi_schema_base_uri)
|
|
68
|
+
if jsi_schema_base_uri
|
|
69
|
+
unless jsi_schema_base_uri.respond_to?(:to_str)
|
|
70
|
+
raise(TypeError, "jsi_schema_base_uri must be string or Addressable::URI; got: #{jsi_schema_base_uri.inspect}")
|
|
71
|
+
end
|
|
72
|
+
@jsi_schema_base_uri = Addressable::URI.parse(jsi_schema_base_uri).freeze
|
|
73
|
+
unless @jsi_schema_base_uri.absolute? && !@jsi_schema_base_uri.fragment
|
|
74
|
+
raise(ArgumentError, "jsi_schema_base_uri must be an absolute URI with no fragment; got: #{jsi_schema_base_uri.inspect}")
|
|
75
|
+
end
|
|
76
|
+
else
|
|
77
|
+
@jsi_schema_base_uri = nil
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def jsi_schema_resource_ancestors=(jsi_schema_resource_ancestors)
|
|
82
|
+
if jsi_schema_resource_ancestors
|
|
83
|
+
unless jsi_schema_resource_ancestors.respond_to?(:to_ary)
|
|
84
|
+
raise(TypeError, "jsi_schema_resource_ancestors must be an array; got: #{jsi_schema_resource_ancestors.inspect}")
|
|
85
|
+
end
|
|
86
|
+
jsi_schema_resource_ancestors.each { |a| Schema.ensure_schema(a) }
|
|
87
|
+
# sanity check the ancestors are in order
|
|
88
|
+
last_anc_ptr = nil
|
|
89
|
+
jsi_schema_resource_ancestors.each do |anc|
|
|
90
|
+
if last_anc_ptr.nil?
|
|
91
|
+
# pass
|
|
92
|
+
elsif last_anc_ptr == anc.jsi_ptr
|
|
93
|
+
raise(Bug, "duplicate ancestors in #{jsi_schema_resource_ancestors.pretty_inspect}")
|
|
94
|
+
elsif !last_anc_ptr.contains?(anc.jsi_ptr)
|
|
95
|
+
raise(Bug, "ancestor ptr #{anc.jsi_ptr} not contained by previous: #{last_anc_ptr} in #{jsi_schema_resource_ancestors.pretty_inspect}")
|
|
96
|
+
end
|
|
97
|
+
if anc.jsi_ptr == jsi_ptr
|
|
98
|
+
raise(Bug, "ancestor is self")
|
|
99
|
+
elsif !anc.jsi_ptr.contains?(jsi_ptr)
|
|
100
|
+
raise(Bug, "ancestor does not contain self")
|
|
101
|
+
end
|
|
102
|
+
last_anc_ptr = anc.jsi_ptr
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
@jsi_schema_resource_ancestors = jsi_schema_resource_ancestors.to_ary.freeze
|
|
106
|
+
else
|
|
107
|
+
@jsi_schema_resource_ancestors = [].freeze
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def jsi_anchor_subschemas_map
|
|
112
|
+
jsi_memomap(__method__) do |anchor|
|
|
113
|
+
jsi_each_child_node.select do |node|
|
|
114
|
+
node.is_a?(Schema) && node.respond_to?(:anchor) && node.anchor == anchor
|
|
115
|
+
end.freeze
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSI
|
|
4
|
+
module Schema::Validation::ArrayLength
|
|
5
|
+
# @private
|
|
6
|
+
def internal_validate_maxItems(result_builder)
|
|
7
|
+
if schema_content.key?('maxItems')
|
|
8
|
+
value = schema_content['maxItems']
|
|
9
|
+
# The value of this keyword MUST be a non-negative integer.
|
|
10
|
+
if internal_integer?(value) && value >= 0
|
|
11
|
+
if result_builder.instance.respond_to?(:to_ary)
|
|
12
|
+
# An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword.
|
|
13
|
+
result_builder.validate(
|
|
14
|
+
result_builder.instance.to_ary.size <= value,
|
|
15
|
+
'instance array size is greater than `maxItems` value',
|
|
16
|
+
keyword: 'maxItems',
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
else
|
|
20
|
+
result_builder.schema_error('`maxItems` is not a non-negative integer', 'maxItems')
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @private
|
|
26
|
+
def internal_validate_minItems(result_builder)
|
|
27
|
+
if schema_content.key?('minItems')
|
|
28
|
+
value = schema_content['minItems']
|
|
29
|
+
# The value of this keyword MUST be a non-negative integer.
|
|
30
|
+
if internal_integer?(value) && value >= 0
|
|
31
|
+
if result_builder.instance.respond_to?(:to_ary)
|
|
32
|
+
# An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword.
|
|
33
|
+
result_builder.validate(
|
|
34
|
+
result_builder.instance.to_ary.size >= value,
|
|
35
|
+
'instance array size is less than `minItems` value',
|
|
36
|
+
keyword: 'minItems',
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
else
|
|
40
|
+
result_builder.schema_error('`minItems` is not a non-negative integer', 'minItems')
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
module Schema::Validation::UniqueItems
|
|
46
|
+
# @private
|
|
47
|
+
def internal_validate_uniqueItems(result_builder)
|
|
48
|
+
if schema_content.key?('uniqueItems')
|
|
49
|
+
value = schema_content['uniqueItems']
|
|
50
|
+
# The value of this keyword MUST be a boolean.
|
|
51
|
+
if value == false
|
|
52
|
+
# If this keyword has boolean value false, the instance validates successfully.
|
|
53
|
+
# (noop)
|
|
54
|
+
elsif value == true
|
|
55
|
+
if result_builder.instance.respond_to?(:to_ary)
|
|
56
|
+
# If it has boolean value true, the instance validates successfully if all of its elements are unique.
|
|
57
|
+
result_builder.validate(
|
|
58
|
+
result_builder.instance.uniq.size == result_builder.instance.size,
|
|
59
|
+
"instance array items' uniqueness does not match `uniqueItems` value",
|
|
60
|
+
keyword: 'uniqueItems',
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
result_builder.schema_error('`uniqueItems` is not a boolean', 'uniqueItems')
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|