json_skooma 0.1.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 (141) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +22 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +107 -0
  5. data/data/draft-2019-09/README.md +60 -0
  6. data/data/draft-2019-09/hyper-schema.json +26 -0
  7. data/data/draft-2019-09/links.json +91 -0
  8. data/data/draft-2019-09/meta/applicator.json +56 -0
  9. data/data/draft-2019-09/meta/content.json +17 -0
  10. data/data/draft-2019-09/meta/core.json +57 -0
  11. data/data/draft-2019-09/meta/format.json +14 -0
  12. data/data/draft-2019-09/meta/hyper-schema.json +29 -0
  13. data/data/draft-2019-09/meta/meta-data.json +37 -0
  14. data/data/draft-2019-09/meta/validation.json +98 -0
  15. data/data/draft-2019-09/output/hyper-schema.json +62 -0
  16. data/data/draft-2019-09/output/schema.json +86 -0
  17. data/data/draft-2019-09/output/verbose-example.json +130 -0
  18. data/data/draft-2019-09/schema.json +42 -0
  19. data/data/draft-2020-12/README.md +89 -0
  20. data/data/draft-2020-12/adr/README.md +15 -0
  21. data/data/draft-2020-12/archive/hyper-schema.json +28 -0
  22. data/data/draft-2020-12/archive/links.json +93 -0
  23. data/data/draft-2020-12/archive/meta/hyper-schema.json +30 -0
  24. data/data/draft-2020-12/hyper-schema.json +27 -0
  25. data/data/draft-2020-12/links.json +85 -0
  26. data/data/draft-2020-12/meta/applicator.json +48 -0
  27. data/data/draft-2020-12/meta/content.json +17 -0
  28. data/data/draft-2020-12/meta/core.json +51 -0
  29. data/data/draft-2020-12/meta/format-annotation.json +14 -0
  30. data/data/draft-2020-12/meta/format-assertion.json +14 -0
  31. data/data/draft-2020-12/meta/hyper-schema.json +29 -0
  32. data/data/draft-2020-12/meta/meta-data.json +37 -0
  33. data/data/draft-2020-12/meta/unevaluated.json +15 -0
  34. data/data/draft-2020-12/meta/validation.json +98 -0
  35. data/data/draft-2020-12/output/hyper-schema.json +62 -0
  36. data/data/draft-2020-12/output/schema.json +96 -0
  37. data/data/draft-2020-12/output/verbose-example.json +130 -0
  38. data/data/draft-2020-12/schema.json +58 -0
  39. data/lib/json_skooma/dialects/draft201909.rb +137 -0
  40. data/lib/json_skooma/dialects/draft202012.rb +146 -0
  41. data/lib/json_skooma/formatters.rb +135 -0
  42. data/lib/json_skooma/inflector.rb +13 -0
  43. data/lib/json_skooma/json_node.rb +100 -0
  44. data/lib/json_skooma/json_pointer.rb +79 -0
  45. data/lib/json_skooma/json_schema.rb +176 -0
  46. data/lib/json_skooma/keywords/applicator/additional_properties.rb +37 -0
  47. data/lib/json_skooma/keywords/applicator/all_of.rb +25 -0
  48. data/lib/json_skooma/keywords/applicator/any_of.rb +26 -0
  49. data/lib/json_skooma/keywords/applicator/contains.rb +31 -0
  50. data/lib/json_skooma/keywords/applicator/dependent_schemas.rb +35 -0
  51. data/lib/json_skooma/keywords/applicator/else.rb +22 -0
  52. data/lib/json_skooma/keywords/applicator/if.rb +17 -0
  53. data/lib/json_skooma/keywords/applicator/items.rb +36 -0
  54. data/lib/json_skooma/keywords/applicator/not.rb +19 -0
  55. data/lib/json_skooma/keywords/applicator/one_of.rb +35 -0
  56. data/lib/json_skooma/keywords/applicator/pattern_properties.rb +46 -0
  57. data/lib/json_skooma/keywords/applicator/prefix_items.rb +31 -0
  58. data/lib/json_skooma/keywords/applicator/properties.rb +34 -0
  59. data/lib/json_skooma/keywords/applicator/property_names.rb +25 -0
  60. data/lib/json_skooma/keywords/applicator/then.rb +22 -0
  61. data/lib/json_skooma/keywords/base.rb +74 -0
  62. data/lib/json_skooma/keywords/base_annotation.rb +12 -0
  63. data/lib/json_skooma/keywords/content/content_encoding.rb +12 -0
  64. data/lib/json_skooma/keywords/content/content_media_type.rb +12 -0
  65. data/lib/json_skooma/keywords/content/content_schema.rb +19 -0
  66. data/lib/json_skooma/keywords/core/anchor.rb +22 -0
  67. data/lib/json_skooma/keywords/core/comment.rb +12 -0
  68. data/lib/json_skooma/keywords/core/defs.rb +13 -0
  69. data/lib/json_skooma/keywords/core/dynamic_anchor.rb +22 -0
  70. data/lib/json_skooma/keywords/core/dynamic_ref.rb +67 -0
  71. data/lib/json_skooma/keywords/core/id.rb +28 -0
  72. data/lib/json_skooma/keywords/core/ref.rb +35 -0
  73. data/lib/json_skooma/keywords/core/schema.rb +26 -0
  74. data/lib/json_skooma/keywords/core/vocabulary.rb +34 -0
  75. data/lib/json_skooma/keywords/draft_2019_09/additional_items.rb +40 -0
  76. data/lib/json_skooma/keywords/draft_2019_09/items.rb +41 -0
  77. data/lib/json_skooma/keywords/draft_2019_09/recursive_anchor.rb +12 -0
  78. data/lib/json_skooma/keywords/draft_2019_09/recursive_ref.rb +46 -0
  79. data/lib/json_skooma/keywords/draft_2019_09/unevaluated_items.rb +56 -0
  80. data/lib/json_skooma/keywords/format_annotation/format.rb +27 -0
  81. data/lib/json_skooma/keywords/meta_data/default.rb +11 -0
  82. data/lib/json_skooma/keywords/meta_data/deprecated.rb +11 -0
  83. data/lib/json_skooma/keywords/meta_data/description.rb +11 -0
  84. data/lib/json_skooma/keywords/meta_data/examples.rb +11 -0
  85. data/lib/json_skooma/keywords/meta_data/read_only.rb +11 -0
  86. data/lib/json_skooma/keywords/meta_data/title.rb +11 -0
  87. data/lib/json_skooma/keywords/meta_data/write_only.rb +11 -0
  88. data/lib/json_skooma/keywords/unevaluated/unevaluated_items.rb +59 -0
  89. data/lib/json_skooma/keywords/unevaluated/unevaluated_properties.rb +43 -0
  90. data/lib/json_skooma/keywords/unknown.rb +21 -0
  91. data/lib/json_skooma/keywords/validation/const.rb +17 -0
  92. data/lib/json_skooma/keywords/validation/dependent_required.rb +24 -0
  93. data/lib/json_skooma/keywords/validation/enum.rb +19 -0
  94. data/lib/json_skooma/keywords/validation/exclusive_maximum.rb +18 -0
  95. data/lib/json_skooma/keywords/validation/exclusive_minimum.rb +18 -0
  96. data/lib/json_skooma/keywords/validation/max_contains.rb +24 -0
  97. data/lib/json_skooma/keywords/validation/max_items.rb +18 -0
  98. data/lib/json_skooma/keywords/validation/max_length.rb +18 -0
  99. data/lib/json_skooma/keywords/validation/max_properties.rb +18 -0
  100. data/lib/json_skooma/keywords/validation/maximum.rb +18 -0
  101. data/lib/json_skooma/keywords/validation/min_contains.rb +31 -0
  102. data/lib/json_skooma/keywords/validation/min_items.rb +18 -0
  103. data/lib/json_skooma/keywords/validation/min_length.rb +18 -0
  104. data/lib/json_skooma/keywords/validation/min_properties.rb +18 -0
  105. data/lib/json_skooma/keywords/validation/minimum.rb +18 -0
  106. data/lib/json_skooma/keywords/validation/multiple_of.rb +20 -0
  107. data/lib/json_skooma/keywords/validation/pattern.rb +23 -0
  108. data/lib/json_skooma/keywords/validation/required.rb +19 -0
  109. data/lib/json_skooma/keywords/validation/type.rb +26 -0
  110. data/lib/json_skooma/keywords/validation/unique_items.rb +20 -0
  111. data/lib/json_skooma/keywords/value_schemas.rb +87 -0
  112. data/lib/json_skooma/memoizable.rb +21 -0
  113. data/lib/json_skooma/metaschema.rb +32 -0
  114. data/lib/json_skooma/registry.rb +130 -0
  115. data/lib/json_skooma/result.rb +125 -0
  116. data/lib/json_skooma/sources.rb +55 -0
  117. data/lib/json_skooma/validators/base.rb +31 -0
  118. data/lib/json_skooma/validators/date.rb +18 -0
  119. data/lib/json_skooma/validators/date_time.rb +24 -0
  120. data/lib/json_skooma/validators/duration.rb +25 -0
  121. data/lib/json_skooma/validators/email.rb +36 -0
  122. data/lib/json_skooma/validators/hostname.rb +17 -0
  123. data/lib/json_skooma/validators/idn_email.rb +30 -0
  124. data/lib/json_skooma/validators/idn_hostname.rb +15 -0
  125. data/lib/json_skooma/validators/ipv4.rb +20 -0
  126. data/lib/json_skooma/validators/ipv6.rb +16 -0
  127. data/lib/json_skooma/validators/iri.rb +47 -0
  128. data/lib/json_skooma/validators/iri_reference.rb +15 -0
  129. data/lib/json_skooma/validators/json_pointer.rb +19 -0
  130. data/lib/json_skooma/validators/regex.rb +15 -0
  131. data/lib/json_skooma/validators/relative_json_pointer.rb +18 -0
  132. data/lib/json_skooma/validators/time.rb +32 -0
  133. data/lib/json_skooma/validators/uri.rb +60 -0
  134. data/lib/json_skooma/validators/uri_reference.rb +15 -0
  135. data/lib/json_skooma/validators/uri_template.rb +26 -0
  136. data/lib/json_skooma/validators/uuid.rb +15 -0
  137. data/lib/json_skooma/validators.rb +17 -0
  138. data/lib/json_skooma/version.rb +5 -0
  139. data/lib/json_skooma/vocabulary.rb +12 -0
  140. data/lib/json_skooma.rb +39 -0
  141. metadata +244 -0
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+
5
+ module JSONSkooma
6
+ class JSONNode < SimpleDelegator
7
+ extend Memoizable
8
+
9
+ attr_reader :parent, :root, :key, :type
10
+
11
+ def initialize(value, key: nil, parent: nil, item_class: JSONNode, **item_params)
12
+ @key = key
13
+ @parent = parent
14
+ @root = parent&.root || self
15
+ @item_class = item_class
16
+ @item_params = item_params
17
+ @type, data = parse_value(value)
18
+
19
+ super(data)
20
+ end
21
+
22
+ def [](key)
23
+ case type
24
+ when "array"
25
+ super(key.to_i)
26
+ when "object"
27
+ super(key.to_s)
28
+ else
29
+ raise Error, "Cannot get key #{key} from #{__getobj__.class} in #{path}"
30
+ end
31
+ end
32
+
33
+ def value
34
+ case type
35
+ when "array"
36
+ map(&:value)
37
+ when "object"
38
+ transform_values(&:value)
39
+ else
40
+ __getobj__
41
+ end
42
+ end
43
+ memoize :value
44
+
45
+ def path
46
+ result = []
47
+ each_parent { |node| result.unshift(node.key) }
48
+ JSONPointer.new(result)
49
+ end
50
+ memoize :path
51
+
52
+ def ==(other)
53
+ return super(other.__getobj__) if other.is_a?(self.class)
54
+ super
55
+ end
56
+
57
+ def !=(other)
58
+ return super(other.__getobj__) if other.is_a?(self.class)
59
+ super
60
+ end
61
+
62
+ private
63
+
64
+ def each_parent
65
+ node = self
66
+
67
+ while node.parent
68
+ yield node
69
+ node = node.parent
70
+ end
71
+ end
72
+
73
+ def parse_value(value)
74
+ case value
75
+ when true, false
76
+ ["boolean", value]
77
+ when String
78
+ ["string", value]
79
+ when Integer, Float
80
+ ["number", value]
81
+ when nil
82
+ ["null", value]
83
+ when Hash
84
+ ["object", map_object_value(value)]
85
+ when Array
86
+ ["array", map_array_value(value)]
87
+ else
88
+ raise Error, "Unknown JSON type: #{value.class}"
89
+ end
90
+ end
91
+
92
+ def map_array_value(value)
93
+ value.map.with_index { |v, i| @item_class.new(v, key: i.to_s, parent: self, **@item_params) }
94
+ end
95
+
96
+ def map_object_value(value)
97
+ value.map { |k, v| [k.to_s, @item_class.new(v, key: k.to_s, parent: self, **@item_params)] }.to_h
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hana"
4
+ require "cgi"
5
+
6
+ module JSONSkooma
7
+ class JSONPointer < Hana::Pointer
8
+ extend Memoizable
9
+ ESC_REGEX = /[\/^~]/.freeze
10
+ ESC2 = {"^" => "^^", "~" => "~0", "/" => "~1"}.freeze
11
+ ESCAPE_REGEX = /([^ a-zA-Z0-9_.\-~\/!$&'()*+,;=]+)/.freeze
12
+
13
+ def self.new_root(path)
14
+ return [""] if path == ""
15
+
16
+ parts = ((path.include?("%") || path.include?("+")) ? CGI.unescape(path) : path).split(/(?<!\^)\//).each { |part|
17
+ part.gsub!(/\^[\/^]|~[01]/) { |m| ESC[m] }
18
+ }
19
+
20
+ parts.push("") if path.end_with?("/")
21
+
22
+ new(parts)
23
+ end
24
+
25
+ def initialize(path)
26
+ if path.is_a?(Array)
27
+ @path = path
28
+ else
29
+ super CGI.unescape(path)
30
+ end
31
+ end
32
+
33
+ def child(key)
34
+ self.class.new(@path.dup.push(key))
35
+ end
36
+
37
+ def <<(part)
38
+ case part
39
+ when Array
40
+ @path.concat(part.map(&:to_s))
41
+ when JSONPointer
42
+ @path.concat(part.path)
43
+ else
44
+ @path << part.to_s
45
+ end
46
+ self
47
+ end
48
+
49
+ def to_s
50
+ return "" if @path == []
51
+ return "/" if @path == [""]
52
+
53
+ "/" + @path.map(&method(:escape)).join("/")
54
+ end
55
+
56
+ memoize :to_s
57
+
58
+ def ==(other)
59
+ return super unless other.is_a?(self.class)
60
+ other.path == path
61
+ end
62
+
63
+ protected
64
+
65
+ attr_reader :path
66
+
67
+ private
68
+
69
+ def escape(part)
70
+ string = part.gsub(ESC_REGEX) { |m| ESC2[m] }
71
+ encoding = string.encoding
72
+ string.b.gsub!(ESCAPE_REGEX) do |m|
73
+ "%" + m.unpack("H2" * m.bytesize).join("%").upcase
74
+ end
75
+ string.tr!(" ", "+")
76
+ string.force_encoding(encoding)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ class JSONSchema < JSONNode
5
+ extend Memoizable
6
+
7
+ attr_reader :uri, :cache_id, :registry
8
+
9
+ attr_writer :metaschema_uri
10
+
11
+ def initialize(value, registry: Registry::DEFAULT_NAME, cache_id: "default", uri: nil, metaschema_uri: nil, parent: nil, key: nil)
12
+ super(value, parent: parent, key: key)
13
+
14
+ @keywords = {}
15
+
16
+ @cache_id = cache_id
17
+ @registry = Registry[registry]
18
+ self.metaschema_uri = metaschema_uri.is_a?(String) ? URI.parse(metaschema_uri) : metaschema_uri
19
+ self.uri = uri.is_a?(String) ? URI.parse(uri) : uri
20
+
21
+ return if type != "object"
22
+
23
+ self.uri ||= URI("urn:uuid:#{SecureRandom.uuid}") if parent.nil?
24
+ resolve_keywords(value.transform_keys(&:to_s))
25
+ resolve_references if parent.nil?
26
+ end
27
+
28
+ def evaluate(instance, result = nil)
29
+ instance = JSONSkooma::JSONNode.new(instance) unless instance.is_a?(JSONNode)
30
+
31
+ result ||= Result.new(self, instance)
32
+ case value
33
+ when true
34
+ # do nothing
35
+ when false
36
+ result.failure("The instance is disallowed by a boolean false schema")
37
+ else
38
+ @keywords.each do |key, keyword|
39
+ next if keyword.static || !keyword.instance_types.include?(instance.type)
40
+
41
+ result.call(instance, key, self) do |subresult|
42
+ keyword.evaluate(instance, subresult)
43
+ end
44
+ end
45
+
46
+ if result.children.any? { |_, child| !child.passed? && child.instance.path == instance.path }
47
+ result.failure
48
+ end
49
+ end
50
+
51
+ result
52
+ end
53
+
54
+ def validate
55
+ metaschema.evaluate(self)
56
+ end
57
+
58
+ def parent_schema
59
+ node = parent
60
+ while node
61
+ return node if node.is_a?(JSONSchema)
62
+
63
+ node = node.parent
64
+ end
65
+ end
66
+ memoize :parent_schema
67
+
68
+ def uri=(uri)
69
+ return if @uri == uri
70
+
71
+ @base_uri = nil
72
+ @registry.delete_schema(@uri, cache_id: @cache_id) if @uri
73
+ @uri = uri
74
+ @registry.add_schema(@uri, self, cache_id: @cache_id) if @uri
75
+ end
76
+
77
+ def metaschema
78
+ raise RegistryError, "The schema's metaschema URI has not been set" if metaschema_uri.nil?
79
+
80
+ @metaschema ||= @registry.metaschema(metaschema_uri)
81
+ end
82
+
83
+ def metaschema_uri
84
+ @metaschema_uri || parent_schema&.metaschema_uri
85
+ end
86
+ memoize :metaschema_uri
87
+
88
+ def base_uri
89
+ return parent_schema&.base_uri unless uri
90
+
91
+ @base_uri ||= uri.dup.tap { |u| u.fragment = nil }
92
+ end
93
+
94
+ def canonical_uri
95
+ return uri if uri
96
+
97
+ keys = []
98
+ node = self
99
+ while node.parent
100
+ keys.unshift(node.key)
101
+ node = node.parent
102
+
103
+ if node.is_a?(JSONSchema) && node.uri
104
+ fragment = JSONPointer.new(node.uri.fragment || "") << keys
105
+ return node.uri.dup.tap { |u| u.fragment = fragment.to_s }
106
+ end
107
+ end
108
+ end
109
+ memoize :canonical_uri
110
+
111
+ def resolve_references
112
+ @keywords.each_value { |kw| kw.resolve }
113
+ end
114
+
115
+ private
116
+
117
+ def resolve_keywords(value)
118
+ bootstrap(value)
119
+
120
+ kw_classes = value.keys
121
+ .reject { |k| @keywords.key?(k) }
122
+ .map { |k| [k, kw_class(k)] }
123
+ .to_h
124
+
125
+ dependencies_in_order(kw_classes) do |kw_class|
126
+ add_keyword(kw_class.new(self, value[kw_class.key]))
127
+ end
128
+ end
129
+
130
+ def bootstrap(value)
131
+ bootstrap_kw_classes = {
132
+ "$schema" => Keywords::Core::Schema,
133
+ "$id" => Keywords::Core::Id
134
+ }
135
+
136
+ bootstrap_kw_classes.each do |key, kw_class|
137
+ next unless value.key?(key)
138
+
139
+ add_keyword(kw_class.new(self, value[key]))
140
+ end
141
+ end
142
+
143
+ def kw_class(k)
144
+ metaschema.kw_class(k)
145
+ end
146
+
147
+ def add_keyword(kw)
148
+ @keywords[kw.key] = kw
149
+ __getobj__[kw.key] = kw.json
150
+ end
151
+
152
+ def dependencies_in_order(kw_classes)
153
+ dependencies = kw_classes.map do |_, kw_class|
154
+ [kw_class, kw_class.depends_on.map { |dep| kw_classes[dep] }.compact]
155
+ end.to_h
156
+
157
+ while dependencies.any?
158
+ kw_class, _ = dependencies.find { |_, depclasses| depclasses.empty? }
159
+ dependencies.delete(kw_class)
160
+ dependencies.each { |_, deps| deps.delete(kw_class) }
161
+ yield kw_class
162
+ end
163
+ end
164
+
165
+ def parse_value(value, **options)
166
+ case value
167
+ when true, false
168
+ ["boolean", value]
169
+ when Hash
170
+ ["object", {}]
171
+ else
172
+ raise TypeError, "#{value} is not JSONSchema-compatible"
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class AdditionalProperties < Base
7
+ self.key = "additionalProperties"
8
+ self.instance_types = "object"
9
+ self.value_schema = :schema
10
+ self.depends_on = %w[properties patternProperties]
11
+
12
+ def evaluate(instance, result)
13
+ known_property_names = result.sibling(instance, "properties")&.schema_node&.keys || []
14
+ known_property_patterns = result.sibling(instance, "patternProperties")&.schema_node&.keys || []
15
+
16
+ annotation = []
17
+ error = []
18
+
19
+ instance.each do |name, item|
20
+ if !known_property_names.include?(name) && !known_property_patterns.any? { |pattern| Regexp.new(pattern).match?(name) }
21
+ if json.evaluate(item, result).passed?
22
+ annotation << name
23
+ else
24
+ error << name
25
+ # reset to success for the next iteration
26
+ result.success
27
+ end
28
+ end
29
+ end
30
+ return result.annotate(annotation) if error.empty?
31
+
32
+ result.failure(error)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class AllOf < Base
7
+ self.key = "allOf"
8
+ self.value_schema = :array_of_schemas
9
+
10
+ def evaluate(instance, result)
11
+ err_indices = []
12
+ json.each.with_index do |subschema, index|
13
+ result.call(instance, index.to_s) do |subresult|
14
+ subschema.evaluate(instance, subresult)
15
+ err_indices << index unless subresult.passed?
16
+ end
17
+ end
18
+ return if err_indices.empty?
19
+
20
+ result.failure("The instance is invalid against subschemas #{err_indices}")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class AnyOf < Base
7
+ self.key = "anyOf"
8
+ self.value_schema = :array_of_schemas
9
+
10
+ def evaluate(instance, result)
11
+ valid = false
12
+ json.each.with_index do |subschema, index|
13
+ result.call(instance, index.to_s) do |subresult|
14
+ subschema.evaluate(instance, subresult)
15
+ valid = true if subresult.passed?
16
+ end
17
+ end
18
+
19
+ return if valid
20
+
21
+ result.failure("The instance must be valid against at least one subschema")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class Contains < Base
7
+ self.key = "contains"
8
+ self.instance_types = "array"
9
+ self.value_schema = :schema
10
+
11
+ def evaluate(instance, result)
12
+ annotation = []
13
+ instance.each_with_index do |item, index|
14
+ if json.evaluate(item, result).passed?
15
+ annotation << index
16
+ else
17
+ result.success
18
+ end
19
+ end
20
+ result.annotate(annotation)
21
+
22
+ return if annotation.any?
23
+
24
+ result.failure(
25
+ "The array does not contain any element that is valid against the `#{key}` subschema"
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class DependentSchemas < Base
7
+ self.key = "dependentSchemas"
8
+ self.instance_types = "object"
9
+ self.value_schema = :object_of_schemas
10
+
11
+ def evaluate(instance, result)
12
+ annotation = []
13
+ err_names = []
14
+
15
+ json.each do |name, subschema|
16
+ next unless instance.key?(name)
17
+
18
+ result.call(instance, name) do |subresult|
19
+ if subschema.evaluate(instance, subresult).passed?
20
+ annotation << name
21
+ else
22
+ err_names << name
23
+ end
24
+ end
25
+ end
26
+ return result.annotate(annotation) if err_names.none?
27
+
28
+ result.failure(
29
+ "Properties #{err_names} are invalid against the corresponding `dependentSchemas` subschemas"
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class Else < Base
7
+ self.key = "else"
8
+ self.value_schema = :schema
9
+ self.depends_on = %w[if]
10
+
11
+ def evaluate(instance, result)
12
+ condition = result.sibling(instance, "if")
13
+ if condition && !condition.valid?
14
+ json.evaluate(instance, result)
15
+ else
16
+ result.discard
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class If < Base
7
+ self.key = "if"
8
+ self.value_schema = :schema
9
+
10
+ def evaluate(instance, result)
11
+ json.evaluate(instance, result)
12
+ result.skip_assertion
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class Items < Base
7
+ self.key = "items"
8
+ self.instance_types = %w[array]
9
+ self.value_schema = :schema
10
+ self.depends_on = %w[prefixItems]
11
+
12
+ def evaluate(instance, result)
13
+ prefix_items = result.sibling(instance, "prefixItems")
14
+ start_index = prefix_items&.schema_node&.length || 0
15
+
16
+ annotation = nil
17
+ error = []
18
+ instance.each_with_index do |item, index|
19
+ next if index < start_index
20
+
21
+ if json.evaluate(item, result).passed?
22
+ annotation = true
23
+ else
24
+ error << index
25
+ # reset to passed for the next iteration
26
+ result.success
27
+ end
28
+ end
29
+ return result.annotate(annotation) if error.empty?
30
+
31
+ result.failure(error)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class Not < Base
7
+ self.key = "not"
8
+ self.value_schema = :schema
9
+
10
+ def evaluate(instance, result)
11
+ json.evaluate(instance, result)
12
+ return result.success unless result.passed?
13
+
14
+ result.failure("The instance must not be valid against the subschema")
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class OneOf < Base
7
+ self.key = "oneOf"
8
+ self.value_schema = :array_of_schemas
9
+
10
+ def evaluate(instance, result)
11
+ valid_indices = []
12
+ err_indices = []
13
+
14
+ json.each.with_index do |subschema, index|
15
+ result.call(instance, index.to_s) do |subresult|
16
+ subschema.evaluate(instance, subresult)
17
+ if subresult.passed?
18
+ valid_indices << index
19
+ else
20
+ err_indices << index
21
+ end
22
+ end
23
+ end
24
+
25
+ return if valid_indices.size == 1
26
+
27
+ result.failure(
28
+ "The instance must be valid against exactly one subschema; " \
29
+ "it is valid against #{valid_indices} and invalid against #{err_indices}'"
30
+ )
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSkooma
4
+ module Keywords
5
+ module Applicator
6
+ class PatternProperties < Base
7
+ self.key = "patternProperties"
8
+ self.instance_types = "object"
9
+ self.value_schema = :object_of_schemas
10
+
11
+ def initialize(parent_schema, value)
12
+ super
13
+ @patterned = json.map do |pattern, subschema|
14
+ [Regexp.new(pattern), pattern, subschema]
15
+ end
16
+ end
17
+
18
+ def evaluate(instance, result)
19
+ matched_names = Set.new
20
+ err_names = []
21
+
22
+ instance.each do |name, item|
23
+ @patterned.each do |regexp, pattern, subschema|
24
+ if regexp.match?(name)
25
+ result.call(item, pattern) do |subresult|
26
+ subschema.evaluate(item, subresult)
27
+ if subresult.passed?
28
+ matched_names << name
29
+ else
30
+ err_names << name
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ if err_names.any?
38
+ result.failure("Properties #{err_names} are invalid")
39
+ else
40
+ result.annotate(matched_names.to_a)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end