yaml-schema 1.0.1 → 1.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.
- checksums.yaml +4 -4
- data/lib/yaml-schema.rb +112 -52
- data/test/validator_test.rb +157 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 579d1085952dfe8a826de14f05a2af2073da12bf45ef5d5959155447a02a9c0d
|
|
4
|
+
data.tar.gz: 165be9ca5619b56f47f54ba456e31213662a35fff3f41a21b9d03e0e400feae5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: eadb3ccf99d89937351e506a95dc95d3527e19569e1e89b6dad29f66a2b5f9ade673015838526494a7a3c5c2b4f487bb5b386236505542759f0330e60207dd44
|
|
7
|
+
data.tar.gz: 79132f662fe6b73bed894481f10df139b89839fd7cf97ccd972c8e66bbbaaea6184ef248735c4d5b2695609d72826fa0ff247894f468030a02e1d2548b6d9ee3
|
data/lib/yaml-schema.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module YAMLSchema
|
|
4
|
-
VERSION = "1.0
|
|
4
|
+
VERSION = "1.1.0"
|
|
5
5
|
|
|
6
6
|
class Pointer
|
|
7
7
|
include Enumerable
|
|
@@ -70,6 +70,7 @@ module YAMLSchema
|
|
|
70
70
|
class UnexpectedValue < Exception; end
|
|
71
71
|
class InvalidSchema < Exception; end
|
|
72
72
|
class InvalidString < Exception; end
|
|
73
|
+
class InvalidPattern < Exception; end
|
|
73
74
|
class MissingRequiredField < Exception; end
|
|
74
75
|
|
|
75
76
|
Valid = Struct.new(:exception).new.freeze
|
|
@@ -188,6 +189,7 @@ module YAMLSchema
|
|
|
188
189
|
return make_error UnexpectedProperty, "unknown property #{key.value.dump}", path
|
|
189
190
|
end
|
|
190
191
|
}
|
|
192
|
+
|
|
191
193
|
valid = _validate(sub_schema["type"], sub_schema, val, valid, aliases, path + [key.value])
|
|
192
194
|
|
|
193
195
|
return valid if valid.exception
|
|
@@ -212,36 +214,6 @@ module YAMLSchema
|
|
|
212
214
|
raise InvalidSchema, "objects must specify items or properties"
|
|
213
215
|
end
|
|
214
216
|
end
|
|
215
|
-
when "string"
|
|
216
|
-
unless node.scalar?
|
|
217
|
-
return make_error UnexpectedType, "expected Scalar, got #{node.class.name.dump}", path
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
unless node.quoted || node.tag == "!str"
|
|
221
|
-
if node.value == "false" || node.value == "true"
|
|
222
|
-
return make_error UnexpectedValue, "expected string, got boolean", path
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
if node.value == ""
|
|
226
|
-
return make_error UnexpectedValue, "expected string, got null", path
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
if node.value.match?(/^[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*)$/)
|
|
230
|
-
return make_error UnexpectedValue, "expected string, got integer", path
|
|
231
|
-
end
|
|
232
|
-
end
|
|
233
|
-
|
|
234
|
-
if schema["maxLength"] && node.value.bytesize > schema["maxLength"]
|
|
235
|
-
return make_error InvalidString, "expected string length to be <= #{schema["maxLength"]}", path
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
if schema["minLength"] && node.value.bytesize < schema["minLength"]
|
|
239
|
-
return make_error InvalidString, "expected string length to be >= #{schema["minLength"]}", path
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
if schema["pattern"] && !(node.value.match?(schema["pattern"]))
|
|
243
|
-
return make_error InvalidString, "expected string to match #{schema["pattern"]}", path
|
|
244
|
-
end
|
|
245
217
|
when "array"
|
|
246
218
|
unless node.sequence?
|
|
247
219
|
return make_error UnexpectedType, "expected Sequence, got #{node.class.name.dump}", path
|
|
@@ -268,36 +240,124 @@ module YAMLSchema
|
|
|
268
240
|
else
|
|
269
241
|
raise NotImplementedError
|
|
270
242
|
end
|
|
271
|
-
|
|
243
|
+
else
|
|
272
244
|
unless node.scalar?
|
|
273
245
|
return make_error UnexpectedType, "expected Scalar, got #{node.class.name.dump}", path
|
|
274
246
|
end
|
|
275
247
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
248
|
+
if type == "string"
|
|
249
|
+
unless node.quoted || node.tag == "!str"
|
|
250
|
+
type = extract_type(node.value)
|
|
251
|
+
|
|
252
|
+
if type != :string
|
|
253
|
+
return make_error UnexpectedValue, "expected string, got #{type}", path
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
if schema["maxLength"] && node.value.bytesize > schema["maxLength"]
|
|
258
|
+
return make_error InvalidString, "expected string length to be <= #{schema["maxLength"]}", path
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
if schema["minLength"] && node.value.bytesize < schema["minLength"]
|
|
262
|
+
return make_error InvalidString, "expected string length to be >= #{schema["minLength"]}", path
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
if schema["pattern"] && !(node.value.match?(schema["pattern"]))
|
|
266
|
+
return make_error InvalidPattern, "expected string '#{node.value.dump}' to match #{schema["pattern"]}", path
|
|
267
|
+
end
|
|
268
|
+
else
|
|
269
|
+
if node.quoted
|
|
270
|
+
return make_error UnexpectedValue, "expected #{type}, got string", path
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
if type == "null"
|
|
274
|
+
unless node.value == ""
|
|
275
|
+
return make_error UnexpectedValue, "expected empty string, got #{node.value.dump}", path
|
|
276
|
+
end
|
|
277
|
+
else
|
|
278
|
+
if schema["pattern"] && !(node.value.match?(schema["pattern"]))
|
|
279
|
+
return make_error InvalidPattern, "expected '#{node.value.dump}' to match #{schema["pattern"]}", path
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
case type
|
|
283
|
+
when "boolean"
|
|
284
|
+
unless node.value == "false" || node.value == "true"
|
|
285
|
+
return make_error UnexpectedValue, "expected 'true' or 'false' for boolean", path
|
|
286
|
+
end
|
|
287
|
+
when "integer", "float", "time", "date", "symbol"
|
|
288
|
+
found_type = extract_type(node.value)
|
|
289
|
+
unless found_type == type.to_sym
|
|
290
|
+
return make_error UnexpectedValue, "expected #{type}, got #{type}", path
|
|
291
|
+
end
|
|
292
|
+
else
|
|
293
|
+
raise "unknown type #{schema["type"]}"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
289
296
|
end
|
|
290
|
-
|
|
291
|
-
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
valid
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Taken from http://yaml.org/type/timestamp.html
|
|
303
|
+
TIME = /^-?\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d\d:\d\d(?:\.\d*)?(?:\s*(?:Z|[-+]\d{1,2}:?(?:\d\d)?))?$/
|
|
304
|
+
|
|
305
|
+
# Taken from http://yaml.org/type/float.html
|
|
306
|
+
# Base 60, [-+]inf and NaN are handled separately
|
|
307
|
+
FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10))$/x
|
|
308
|
+
|
|
309
|
+
# Taken from http://yaml.org/type/int.html and modified to ensure at least one numerical symbol exists
|
|
310
|
+
INTEGER_STRICT = /^(?:[-+]?0b[_]*[0-1][0-1_]* (?# base 2)
|
|
311
|
+
|[-+]?0[_]*[0-7][0-7_]* (?# base 8)
|
|
312
|
+
|[-+]?(0|[1-9][0-9_]*) (?# base 10)
|
|
313
|
+
|[-+]?0x[_]*[0-9a-fA-F][0-9a-fA-F_]* (?# base 16))$/x
|
|
314
|
+
|
|
315
|
+
# Tokenize +string+ returning the Ruby object
|
|
316
|
+
def extract_type(string)
|
|
317
|
+
return :null if string.empty?
|
|
318
|
+
# Check for a String type, being careful not to get caught by hash keys, hex values, and
|
|
319
|
+
# special floats (e.g., -.inf).
|
|
320
|
+
if string.match?(%r{^[^\d.:-]?[[:alpha:]_\s!@#$%\^&*(){}<>|/\\~;=]+}) || string.match?(/\n/)
|
|
321
|
+
return :string if string.length > 5
|
|
322
|
+
|
|
323
|
+
if string.match?(/^[^ytonf~]/i)
|
|
324
|
+
:string
|
|
325
|
+
elsif string == '~' || string.match?(/^null$/i)
|
|
326
|
+
:null
|
|
327
|
+
elsif string.match?(/^(yes|true|on)$/i)
|
|
328
|
+
:boolean
|
|
329
|
+
elsif string.match?(/^(no|false|off)$/i)
|
|
330
|
+
:boolean
|
|
331
|
+
else
|
|
332
|
+
:string
|
|
292
333
|
end
|
|
293
|
-
|
|
294
|
-
|
|
334
|
+
elsif string.match?(TIME)
|
|
335
|
+
:time
|
|
336
|
+
elsif string.match?(/^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/)
|
|
337
|
+
:date
|
|
338
|
+
elsif string.match?(/^\+?\.inf$/i)
|
|
339
|
+
:float
|
|
340
|
+
elsif string.match?(/^-\.inf$/i)
|
|
341
|
+
:float
|
|
342
|
+
elsif string.match?(/^\.nan$/i)
|
|
343
|
+
:float
|
|
344
|
+
elsif string.match?(/^:./)
|
|
345
|
+
:symbol
|
|
346
|
+
elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}$/)
|
|
347
|
+
:sexagesimal
|
|
348
|
+
elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}\.[0-9_]*$/)
|
|
349
|
+
:sexagesimal
|
|
350
|
+
elsif string.match?(FLOAT)
|
|
351
|
+
if string.match?(/\A[-+]?\.\Z/)
|
|
352
|
+
:string
|
|
353
|
+
else
|
|
354
|
+
:float
|
|
295
355
|
end
|
|
356
|
+
elsif string.match?(INTEGER_STRICT)
|
|
357
|
+
:integer
|
|
296
358
|
else
|
|
297
|
-
|
|
359
|
+
:string
|
|
298
360
|
end
|
|
299
|
-
|
|
300
|
-
valid
|
|
301
361
|
end
|
|
302
362
|
end
|
|
303
363
|
end
|
data/test/validator_test.rb
CHANGED
|
@@ -5,6 +5,162 @@ require "psych"
|
|
|
5
5
|
module YAMLSchema
|
|
6
6
|
class Validator
|
|
7
7
|
class ErrorTest < Minitest::Test
|
|
8
|
+
def test_pattern_symbol
|
|
9
|
+
ast = Psych.parse(Psych.dump({ "foo" => :foo }))
|
|
10
|
+
|
|
11
|
+
assert_raises InvalidPattern do
|
|
12
|
+
Validator.validate({
|
|
13
|
+
"type" => "object",
|
|
14
|
+
"properties" => {
|
|
15
|
+
"foo" => {
|
|
16
|
+
"type" => "symbol",
|
|
17
|
+
"pattern" => /\A:bar\z/
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}, ast.children.first)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def test_pattern_time
|
|
25
|
+
ast = Psych.parse(Psych.dump({ "foo" => Time.now }))
|
|
26
|
+
|
|
27
|
+
assert_raises InvalidPattern do
|
|
28
|
+
Validator.validate({
|
|
29
|
+
"type" => "object",
|
|
30
|
+
"properties" => {
|
|
31
|
+
"foo" => {
|
|
32
|
+
"type" => "time",
|
|
33
|
+
"pattern" => /\Ayay\z/
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
}, ast.children.first)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def test_pattern_null
|
|
41
|
+
ast = Psych.parse(Psych.dump({ "foo" => nil }))
|
|
42
|
+
|
|
43
|
+
assert Validator.validate({
|
|
44
|
+
"type" => "object",
|
|
45
|
+
"properties" => {
|
|
46
|
+
"foo" => {
|
|
47
|
+
"type" => "null",
|
|
48
|
+
"pattern" => /\Anotnull\z/
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}, ast.children.first)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def test_pattern_float
|
|
55
|
+
ast = Psych.parse(Psych.dump({ "foo" => 1.2 }))
|
|
56
|
+
|
|
57
|
+
assert_raises InvalidPattern do
|
|
58
|
+
Validator.validate({
|
|
59
|
+
"type" => "object",
|
|
60
|
+
"properties" => {
|
|
61
|
+
"foo" => {
|
|
62
|
+
"type" => "float",
|
|
63
|
+
"pattern" => /\A1\.3\z/
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}, ast.children.first)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def test_pattern_boolean
|
|
71
|
+
yaml = "---\n foo: true"
|
|
72
|
+
ast = Psych.parse(yaml)
|
|
73
|
+
|
|
74
|
+
assert_raises InvalidPattern do
|
|
75
|
+
Validator.validate({
|
|
76
|
+
"type" => "object",
|
|
77
|
+
"properties" => {
|
|
78
|
+
"foo" => {
|
|
79
|
+
"type" => "boolean",
|
|
80
|
+
"pattern" => /\Atru\z/
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}, ast.children.first)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_pattern_date
|
|
88
|
+
yaml = "---\n foo: 2025-11-19"
|
|
89
|
+
|
|
90
|
+
ast = Psych.parse(yaml)
|
|
91
|
+
assert Validator.validate({
|
|
92
|
+
"type" => "object",
|
|
93
|
+
"properties" => {
|
|
94
|
+
"foo" => {
|
|
95
|
+
"type" => "date",
|
|
96
|
+
"pattern" => /\A2025-11-19\z/
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
}, ast.children.first)
|
|
100
|
+
|
|
101
|
+
assert_raises InvalidPattern do
|
|
102
|
+
Validator.validate({
|
|
103
|
+
"type" => "object",
|
|
104
|
+
"properties" => {
|
|
105
|
+
"foo" => {
|
|
106
|
+
"type" => "date",
|
|
107
|
+
"pattern" => /\A2025-11-20\z/
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}, ast.children.first)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def test_accept_non_strings
|
|
115
|
+
[Float::INFINITY, -Float::INFINITY, Float::NAN].each do |v|
|
|
116
|
+
ast = Psych.parse(Psych.dump({ "foo" => v }))
|
|
117
|
+
assert Validator.validate({
|
|
118
|
+
"type" => "object",
|
|
119
|
+
"properties" => {
|
|
120
|
+
"foo" => { "type" => "float" },
|
|
121
|
+
},
|
|
122
|
+
}, ast.children.first)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
ast = Psych.parse(Psych.dump({ "foo" => Time.now }))
|
|
126
|
+
assert Validator.validate({
|
|
127
|
+
"type" => "object",
|
|
128
|
+
"properties" => {
|
|
129
|
+
"foo" => { "type" => "time" },
|
|
130
|
+
},
|
|
131
|
+
}, ast.children.first)
|
|
132
|
+
|
|
133
|
+
ast = Psych.parse(Psych.dump({ "foo" => Date.today }))
|
|
134
|
+
assert Validator.validate({
|
|
135
|
+
"type" => "object",
|
|
136
|
+
"properties" => {
|
|
137
|
+
"foo" => { "type" => "date" },
|
|
138
|
+
},
|
|
139
|
+
}, ast.children.first)
|
|
140
|
+
|
|
141
|
+
ast = Psych.parse(Psych.dump({ "foo" => :foo }))
|
|
142
|
+
assert Validator.validate({
|
|
143
|
+
"type" => "object",
|
|
144
|
+
"properties" => {
|
|
145
|
+
"foo" => { "type" => "symbol" },
|
|
146
|
+
},
|
|
147
|
+
}, ast.children.first)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def test_reject_non_strings
|
|
151
|
+
[Float::INFINITY, -Float::INFINITY, Float::NAN, Time.now, Date.today, :foo].each do |v|
|
|
152
|
+
ast = Psych.parse(Psych.dump({ "foo" => v }))
|
|
153
|
+
assert_raises UnexpectedValue do
|
|
154
|
+
Validator.validate({
|
|
155
|
+
"type" => "object",
|
|
156
|
+
"properties" => {
|
|
157
|
+
"foo" => { "type" => "string" },
|
|
158
|
+
},
|
|
159
|
+
}, ast.children.first)
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
8
164
|
def test_property_max_length
|
|
9
165
|
ast = Psych.parse("---\n hello: world")
|
|
10
166
|
assert_raises InvalidString do
|
|
@@ -121,7 +277,7 @@ module YAMLSchema
|
|
|
121
277
|
|
|
122
278
|
def test_regular_expression
|
|
123
279
|
ast = Psych.parse("bar")
|
|
124
|
-
assert_raises
|
|
280
|
+
assert_raises InvalidPattern do
|
|
125
281
|
Validator.validate({
|
|
126
282
|
"type" => "string",
|
|
127
283
|
"pattern" => /foo/
|