json_skooma 0.1.0

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