jsi 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -1
  3. data/CHANGELOG.md +15 -0
  4. data/README.md +105 -38
  5. data/lib/jsi/base.rb +349 -155
  6. data/lib/jsi/jsi_coder.rb +5 -4
  7. data/lib/jsi/metaschema_node/bootstrap_schema.rb +100 -0
  8. data/lib/jsi/metaschema_node.rb +156 -129
  9. data/lib/jsi/pathed_node.rb +47 -49
  10. data/lib/jsi/ptr.rb +292 -0
  11. data/lib/jsi/schema/application/child_application/contains.rb +16 -0
  12. data/lib/jsi/schema/application/child_application/draft04.rb +22 -0
  13. data/lib/jsi/schema/application/child_application/draft06.rb +29 -0
  14. data/lib/jsi/schema/application/child_application/draft07.rb +29 -0
  15. data/lib/jsi/schema/application/child_application/items.rb +18 -0
  16. data/lib/jsi/schema/application/child_application/properties.rb +25 -0
  17. data/lib/jsi/schema/application/child_application.rb +40 -0
  18. data/lib/jsi/schema/application/draft04.rb +8 -0
  19. data/lib/jsi/schema/application/draft06.rb +8 -0
  20. data/lib/jsi/schema/application/draft07.rb +8 -0
  21. data/lib/jsi/schema/application/inplace_application/dependencies.rb +28 -0
  22. data/lib/jsi/schema/application/inplace_application/draft04.rb +26 -0
  23. data/lib/jsi/schema/application/inplace_application/draft06.rb +27 -0
  24. data/lib/jsi/schema/application/inplace_application/draft07.rb +33 -0
  25. data/lib/jsi/schema/application/inplace_application/ifthenelse.rb +20 -0
  26. data/lib/jsi/schema/application/inplace_application/ref.rb +18 -0
  27. data/lib/jsi/schema/application/inplace_application/someof.rb +29 -0
  28. data/lib/jsi/schema/application/inplace_application.rb +46 -0
  29. data/lib/jsi/schema/application.rb +12 -0
  30. data/lib/jsi/schema/draft04.rb +14 -0
  31. data/lib/jsi/schema/draft06.rb +14 -0
  32. data/lib/jsi/schema/draft07.rb +14 -0
  33. data/lib/jsi/schema/issue.rb +36 -0
  34. data/lib/jsi/schema/ref.rb +159 -0
  35. data/lib/jsi/schema/schema_ancestor_node.rb +119 -0
  36. data/lib/jsi/schema/validation/array.rb +69 -0
  37. data/lib/jsi/schema/validation/const.rb +20 -0
  38. data/lib/jsi/schema/validation/contains.rb +25 -0
  39. data/lib/jsi/schema/validation/core.rb +39 -0
  40. data/lib/jsi/schema/validation/dependencies.rb +49 -0
  41. data/lib/jsi/schema/validation/draft04/minmax.rb +91 -0
  42. data/lib/jsi/schema/validation/draft04.rb +112 -0
  43. data/lib/jsi/schema/validation/draft06.rb +122 -0
  44. data/lib/jsi/schema/validation/draft07.rb +159 -0
  45. data/lib/jsi/schema/validation/enum.rb +25 -0
  46. data/lib/jsi/schema/validation/ifthenelse.rb +46 -0
  47. data/lib/jsi/schema/validation/items.rb +54 -0
  48. data/lib/jsi/schema/validation/not.rb +20 -0
  49. data/lib/jsi/schema/validation/numeric.rb +121 -0
  50. data/lib/jsi/schema/validation/object.rb +45 -0
  51. data/lib/jsi/schema/validation/pattern.rb +34 -0
  52. data/lib/jsi/schema/validation/properties.rb +101 -0
  53. data/lib/jsi/schema/validation/property_names.rb +32 -0
  54. data/lib/jsi/schema/validation/ref.rb +40 -0
  55. data/lib/jsi/schema/validation/required.rb +27 -0
  56. data/lib/jsi/schema/validation/someof.rb +90 -0
  57. data/lib/jsi/schema/validation/string.rb +47 -0
  58. data/lib/jsi/schema/validation/type.rb +49 -0
  59. data/lib/jsi/schema/validation.rb +51 -0
  60. data/lib/jsi/schema.rb +486 -133
  61. data/lib/jsi/schema_classes.rb +157 -42
  62. data/lib/jsi/schema_registry.rb +141 -0
  63. data/lib/jsi/schema_set.rb +141 -0
  64. data/lib/jsi/simple_wrap.rb +2 -2
  65. data/lib/jsi/typelike_modules.rb +52 -37
  66. data/lib/jsi/util/attr_struct.rb +106 -0
  67. data/lib/jsi/util.rb +141 -25
  68. data/lib/jsi/validation/error.rb +34 -0
  69. data/lib/jsi/validation/result.rb +210 -0
  70. data/lib/jsi/validation.rb +15 -0
  71. data/lib/jsi/version.rb +3 -1
  72. data/lib/jsi.rb +55 -9
  73. data/lib/schemas/json-schema.org/draft-04/schema.rb +8 -3
  74. data/lib/schemas/json-schema.org/draft-06/schema.rb +8 -3
  75. data/lib/schemas/json-schema.org/draft-07/schema.rb +12 -0
  76. data/readme.rb +138 -0
  77. data/{resources}/schemas/json-schema.org/draft-04/schema.json +149 -0
  78. data/{resources}/schemas/json-schema.org/draft-06/schema.json +154 -0
  79. data/{resources}/schemas/json-schema.org/draft-07/schema.json +168 -0
  80. metadata +69 -118
  81. data/.simplecov +0 -3
  82. data/Rakefile.rb +0 -9
  83. data/jsi.gemspec +0 -28
  84. data/lib/jsi/base/to_rb.rb +0 -128
  85. data/lib/jsi/json/node.rb +0 -203
  86. data/lib/jsi/json/pointer.rb +0 -419
  87. data/lib/jsi/json-schema-fragments.rb +0 -61
  88. data/lib/jsi/json.rb +0 -10
  89. data/resources/icons/AGPL-3.0.png +0 -0
  90. data/test/base_array_test.rb +0 -323
  91. data/test/base_hash_test.rb +0 -337
  92. data/test/base_test.rb +0 -486
  93. data/test/jsi_coder_test.rb +0 -85
  94. data/test/jsi_json_arraynode_test.rb +0 -150
  95. data/test/jsi_json_hashnode_test.rb +0 -132
  96. data/test/jsi_json_node_test.rb +0 -257
  97. data/test/jsi_json_pointer_test.rb +0 -102
  98. data/test/jsi_test.rb +0 -11
  99. data/test/jsi_typelike_as_json_test.rb +0 -53
  100. data/test/metaschema_node_test.rb +0 -19
  101. data/test/schema_module_test.rb +0 -21
  102. data/test/schema_test.rb +0 -208
  103. data/test/spreedly_openapi_test.rb +0 -8
  104. data/test/test_helper.rb +0 -97
  105. data/test/util_test.rb +0 -62
@@ -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