schema-validator 0.0.1
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 +7 -0
- data/LICENSE +22 -0
- data/README.md +42 -0
- data/bin/schema-validator +82 -0
- data/lib/rx/ruby/Rx.rb +658 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 35da5c079158aa63d96d214b3ae9178236ffeba8
|
4
|
+
data.tar.gz: 5deffeb8218a0c223536dce3214362b41c668ed5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6605965706c35485ffb5da9774151e68a4d7616eba9cd100da244f8f17349bdb9cf041dea688d648e4576704b0f700a59f1011b7351815c8390744cd104bf92e
|
7
|
+
data.tar.gz: 61967ced8167906dd409ca9131df95e5317b260956f80a4beded6b40231c64ac9626a44300fd78226e8f9e13b8b91ad0297148c7c4c040e11a4215610144cd5b
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2017 Lars Lockefeer
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# SchemaValidator
|
2
|
+
|
3
|
+
A simple CLI wrapper around [Rx](http://rx.codesimply.com) for all your schema validation needs.
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
```
|
8
|
+
Usage: runner [options]
|
9
|
+
-s, --schema SCHEMA The schema to validate against
|
10
|
+
-f, --file FILE The file to validate
|
11
|
+
-m META_SCHEMATA, A file containing meta schema definitions
|
12
|
+
--meta-schemata
|
13
|
+
```
|
14
|
+
|
15
|
+
Example: validate `validatable.json` against the schema defined in `schema.json`, using the meta schemas from `meta_schemata.json`:
|
16
|
+
|
17
|
+
```
|
18
|
+
./schema-validator -s schema.json -f validatable.json -m meta_schemata.json
|
19
|
+
```
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Clone this repository and run:
|
24
|
+
|
25
|
+
```
|
26
|
+
gem build schema-validator.gemspec
|
27
|
+
gem install schema-validator-{version}.gem
|
28
|
+
```
|
29
|
+
|
30
|
+
Or add the following to your Gemfile:
|
31
|
+
|
32
|
+
```
|
33
|
+
gem 'schema-validator', :git => 'https://github.com/larslockefeer/schema-validator.git', :submodules => true
|
34
|
+
```
|
35
|
+
|
36
|
+
## Acknowledgements
|
37
|
+
|
38
|
+
Under the hood, this gem uses [Rx](http://rx.codesimply.com) as the actual schema validation implementation.
|
39
|
+
|
40
|
+
## Author
|
41
|
+
|
42
|
+
* Lars Lockefeer ([@larslockefeer](https://twitter.com/larslockefeer))
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'optparse'
|
5
|
+
require 'ostruct'
|
6
|
+
require_relative '../lib/rx/ruby/Rx.rb'
|
7
|
+
require 'yaml'
|
8
|
+
|
9
|
+
# A utility to parse JSON or YAML
|
10
|
+
def parse_json_or_yaml(file)
|
11
|
+
begin
|
12
|
+
JSON.parse(file)
|
13
|
+
rescue Exception => e
|
14
|
+
YAML.load(file)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
options = OpenStruct.new
|
19
|
+
OptionParser.new do |opt|
|
20
|
+
opt.on('-s', '--schema SCHEMA', 'The schema to validate against.') { |o| options.schema = o }
|
21
|
+
opt.on('-f', '--file FILE', 'The file to validate.') { |o| options.validatable = o }
|
22
|
+
opt.on('-m', '--meta-schemata META_SCHEMATA', 'A file containing meta schema definitions.') { |o| options.meta_schemata = o }
|
23
|
+
end.parse!
|
24
|
+
|
25
|
+
unless options.schema && options.validatable
|
26
|
+
puts "Please provide a schema and a file to validate."
|
27
|
+
puts "Run me with the -h flag to see usage instructions."
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
if options.meta_schemata
|
32
|
+
meta_schemata_file = File.open(options.meta_schemata).read
|
33
|
+
meta_schemata = parse_json_or_yaml(meta_schemata_file)
|
34
|
+
else
|
35
|
+
meta_schemata = []
|
36
|
+
end
|
37
|
+
|
38
|
+
schema_file = File.open(options.schema).read
|
39
|
+
schema = parse_json_or_yaml(schema_file)
|
40
|
+
|
41
|
+
file = File.open(options.validatable).read
|
42
|
+
validatable = parse_json_or_yaml(file)
|
43
|
+
|
44
|
+
rx = Rx.new({ :load_core => true })
|
45
|
+
|
46
|
+
# Step 1: Learn the meta schemata
|
47
|
+
unless meta_schemata.empty?
|
48
|
+
puts "Learning meta schemata"
|
49
|
+
end
|
50
|
+
meta_schemata.each do |meta_schema|
|
51
|
+
begin
|
52
|
+
rx.learn_type(meta_schema['uri'], meta_schema['schema'])
|
53
|
+
puts " ✅ Learned meta scheme #{meta_schema['uri']}"
|
54
|
+
rescue Exception => e
|
55
|
+
puts " ❌ An error occured learning meta scheme #{meta_schema['uri']}"
|
56
|
+
puts " #{e.message}"
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Step 2: Load the schema that we are going to validate against
|
62
|
+
puts "Loading schema to validate against"
|
63
|
+
begin
|
64
|
+
schema = rx.make_schema(schema)
|
65
|
+
puts " ✅ Schema loaded successfully"
|
66
|
+
rescue Exception => e
|
67
|
+
puts " ❌ An error occured loading the schema"
|
68
|
+
puts " #{e.message}"
|
69
|
+
exit
|
70
|
+
end
|
71
|
+
|
72
|
+
# Step 3: Validate our file against the schema
|
73
|
+
puts "Validating"
|
74
|
+
if schema then
|
75
|
+
begin
|
76
|
+
schema.check!(validatable)
|
77
|
+
puts " ✅ File is according to schema."
|
78
|
+
rescue Exception => e
|
79
|
+
puts " ❌ An error occured validating the file against the schema"
|
80
|
+
puts " #{e.message}"
|
81
|
+
end
|
82
|
+
end
|
data/lib/rx/ruby/Rx.rb
ADDED
@@ -0,0 +1,658 @@
|
|
1
|
+
|
2
|
+
class Rx
|
3
|
+
def self.schema(schema)
|
4
|
+
Rx.new(:load_core => true).make_schema(schema)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(opt={})
|
8
|
+
@type_registry = {}
|
9
|
+
@prefix = {
|
10
|
+
'' => 'tag:codesimply.com,2008:rx/core/',
|
11
|
+
'.meta' => 'tag:codesimply.com,2008:rx/meta/',
|
12
|
+
}
|
13
|
+
|
14
|
+
if opt[:load_core] then
|
15
|
+
Type::Core.core_types.each { |t| register_type(t) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def register_type(type)
|
20
|
+
uri = type.uri
|
21
|
+
|
22
|
+
if @type_registry.has_key?(uri) then
|
23
|
+
raise Rx::Exception.new(
|
24
|
+
"attempted to register already-known type #{uri}"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
@type_registry[ uri ] = type
|
29
|
+
end
|
30
|
+
|
31
|
+
def learn_type(uri, schema)
|
32
|
+
if @type_registry.has_key?(uri) then
|
33
|
+
raise Rx::Exception.new(
|
34
|
+
"attempted to learn type for already-registered uri #{uri}"
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# make sure schema is valid
|
39
|
+
# should this be in a begin/rescue?
|
40
|
+
make_schema(schema)
|
41
|
+
|
42
|
+
@type_registry[ uri ] = { 'schema' => schema }
|
43
|
+
end
|
44
|
+
|
45
|
+
def expand_uri(name)
|
46
|
+
if name.match(/\A\w+:/) then; return name; end;
|
47
|
+
|
48
|
+
match = name.match(/\A\/(.*?)\/(.+)\z/)
|
49
|
+
if ! match then
|
50
|
+
raise Rx::Exception.new("couldn't understand Rx type name: #{name}")
|
51
|
+
end
|
52
|
+
|
53
|
+
if ! @prefix.has_key?(match[1]) then
|
54
|
+
raise Rx::Exception.new("unknown prefix '#{match[1]}' in name 'name'")
|
55
|
+
end
|
56
|
+
|
57
|
+
return @prefix[ match[1] ] + match[2]
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_prefix(name, base)
|
61
|
+
if @prefix.has_key?(name) then
|
62
|
+
throw Rx::Exception.new("the prefix '#{name}' is already registered")
|
63
|
+
end
|
64
|
+
|
65
|
+
@prefix[name] = base
|
66
|
+
end
|
67
|
+
|
68
|
+
def make_schema(schema)
|
69
|
+
schema = { 'type' => schema } if schema.instance_of?(String)
|
70
|
+
|
71
|
+
if not (schema.instance_of?(Hash) and schema['type']) then
|
72
|
+
raise Rx::Exception.new('invalid type')
|
73
|
+
end
|
74
|
+
|
75
|
+
uri = expand_uri(schema['type'])
|
76
|
+
|
77
|
+
if ! @type_registry.has_key?(uri) then
|
78
|
+
raise Rx::Exception.new('unknown type')
|
79
|
+
end
|
80
|
+
|
81
|
+
type_class = @type_registry[uri]
|
82
|
+
|
83
|
+
if type_class.instance_of?(Hash) then
|
84
|
+
if schema.keys != [ 'type' ] then
|
85
|
+
raise Rx::Exception.new('composed type does not take check arguments')
|
86
|
+
end
|
87
|
+
return make_schema(type_class['schema'])
|
88
|
+
else
|
89
|
+
return type_class.new(schema, self)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Helper; end;
|
94
|
+
class Helper::Range
|
95
|
+
|
96
|
+
def initialize(arg)
|
97
|
+
@range = { }
|
98
|
+
|
99
|
+
arg.each_pair { |key,value|
|
100
|
+
if not ['min', 'max', 'min-ex', 'max-ex'].index(key) then
|
101
|
+
raise Rx::Exception.new("illegal argument for Rx::Helper::Range")
|
102
|
+
end
|
103
|
+
|
104
|
+
@range[ key ] = value
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def check(value)
|
109
|
+
return false if ! @range['min' ].nil? and value < @range['min' ]
|
110
|
+
return false if ! @range['min-ex'].nil? and value <= @range['min-ex']
|
111
|
+
return false if ! @range['max-ex'].nil? and value >= @range['max-ex']
|
112
|
+
return false if ! @range['max' ].nil? and value > @range['max' ]
|
113
|
+
return true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class Exception < StandardError
|
118
|
+
end
|
119
|
+
|
120
|
+
class ValidationError < StandardError
|
121
|
+
attr_accessor :path
|
122
|
+
|
123
|
+
def initialize(message, path)
|
124
|
+
@message = message
|
125
|
+
@path = path
|
126
|
+
end
|
127
|
+
|
128
|
+
def path
|
129
|
+
@path ||= ""
|
130
|
+
end
|
131
|
+
|
132
|
+
def message
|
133
|
+
"#{@message} (#{@path})"
|
134
|
+
end
|
135
|
+
|
136
|
+
def inspect
|
137
|
+
"#{@message} (#{@path})"
|
138
|
+
end
|
139
|
+
|
140
|
+
def to_s
|
141
|
+
inspect
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
class Type
|
146
|
+
def initialize(param, rx)
|
147
|
+
assert_valid_params(param)
|
148
|
+
end
|
149
|
+
|
150
|
+
def uri; self.class.uri; end
|
151
|
+
|
152
|
+
def assert_valid_params(param)
|
153
|
+
param.each_key { |k|
|
154
|
+
unless self.allowed_param?(k) then
|
155
|
+
raise Rx::Exception.new("unknown parameter #{k} for #{uri}")
|
156
|
+
end
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
module NoParams
|
161
|
+
def initialize(param, rx)
|
162
|
+
return if param.keys.length == 0
|
163
|
+
return if param.keys == [ 'type' ]
|
164
|
+
|
165
|
+
raise Rx::Exception.new('this type is not parameterized')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class Type::Core < Type
|
170
|
+
class << self
|
171
|
+
def uri
|
172
|
+
return 'tag:codesimply.com,2008:rx/core/' + subname
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def check(value)
|
177
|
+
begin
|
178
|
+
check!(value)
|
179
|
+
true
|
180
|
+
rescue ValidationError
|
181
|
+
false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
class All < Type::Core
|
186
|
+
@@allowed_param = { 'of' => true, 'type' => true }
|
187
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
188
|
+
|
189
|
+
def initialize(param, rx)
|
190
|
+
super
|
191
|
+
|
192
|
+
if ! param.has_key?('of') then
|
193
|
+
raise Rx::Exception.new("no 'of' parameter provided for #{uri}")
|
194
|
+
end
|
195
|
+
|
196
|
+
if param['of'].length == 0 then
|
197
|
+
raise Rx::Exception.new("no schemata provided for 'of' in #{uri}")
|
198
|
+
end
|
199
|
+
|
200
|
+
@alts = [ ]
|
201
|
+
param['of'].each { |alt| @alts.push(rx.make_schema(alt)) }
|
202
|
+
end
|
203
|
+
|
204
|
+
class << self; def subname; return 'all'; end; end
|
205
|
+
|
206
|
+
def check!(value)
|
207
|
+
@alts.each do |alt|
|
208
|
+
begin
|
209
|
+
alt.check!(value)
|
210
|
+
rescue ValidationError => e
|
211
|
+
e.path = "/all" + e.path
|
212
|
+
raise e
|
213
|
+
end
|
214
|
+
end
|
215
|
+
return true
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
class Any < Type::Core
|
220
|
+
@@allowed_param = { 'of' => true, 'type' => true }
|
221
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
222
|
+
|
223
|
+
def initialize(param, rx)
|
224
|
+
super
|
225
|
+
|
226
|
+
if param['of'] then
|
227
|
+
if param['of'].length == 0 then
|
228
|
+
raise Rx::Exception.new(
|
229
|
+
"no alternatives provided for 'of' in #{uri}"
|
230
|
+
)
|
231
|
+
end
|
232
|
+
|
233
|
+
@alts = [ ]
|
234
|
+
param['of'].each { |alt| @alts.push(rx.make_schema(alt)) }
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
class << self; def subname; return 'any'; end; end
|
239
|
+
|
240
|
+
def check!(value)
|
241
|
+
return true unless @alts
|
242
|
+
|
243
|
+
@alts.each do |alt|
|
244
|
+
begin
|
245
|
+
return true if alt.check!(value)
|
246
|
+
rescue ValidationError
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
raise ValidationError.new("expected one to match", "/any")
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
class Arr < Type::Core
|
255
|
+
class << self; def subname; return 'arr'; end; end
|
256
|
+
|
257
|
+
@@allowed_param = { 'contents' => true, 'length' => true, 'type' => true }
|
258
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
259
|
+
|
260
|
+
def initialize(param, rx)
|
261
|
+
super
|
262
|
+
|
263
|
+
unless param['contents'] then
|
264
|
+
raise Rx::Exception.new("no contents schema given for #{uri}")
|
265
|
+
end
|
266
|
+
|
267
|
+
@contents_schema = rx.make_schema( param['contents'] )
|
268
|
+
|
269
|
+
if param['length'] then
|
270
|
+
@length_range = Rx::Helper::Range.new( param['length'] )
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def check!(value)
|
275
|
+
unless value.instance_of?(Array)
|
276
|
+
raise ValidationError.new("expected array got #{value.class}", "/arr")
|
277
|
+
end
|
278
|
+
|
279
|
+
if @length_range
|
280
|
+
unless @length_range.check(value.length)
|
281
|
+
raise ValidationError.new("expected array with #{@length_range} elements, got #{value.length}", "/arr")
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
if @contents_schema then
|
286
|
+
value.each do |v|
|
287
|
+
begin
|
288
|
+
@contents_schema.check!(v)
|
289
|
+
rescue ValidationError => e
|
290
|
+
e.path = "/arr" + e.path
|
291
|
+
raise e
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
return true
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
class Bool < Type::Core
|
301
|
+
class << self; def subname; return 'bool'; end; end
|
302
|
+
|
303
|
+
include Type::NoParams
|
304
|
+
|
305
|
+
def check!(value)
|
306
|
+
unless value.instance_of?(TrueClass) or value.instance_of?(FalseClass)
|
307
|
+
raise ValidationError.new("expected bool got #{value.inspect}", "/bool")
|
308
|
+
end
|
309
|
+
true
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
class Fail < Type::Core
|
314
|
+
class << self; def subname; return 'fail'; end; end
|
315
|
+
include Type::NoParams
|
316
|
+
def check(value); return false; end
|
317
|
+
def check!(value); raise ValidationError.new("explicit fail", "/fail"); end
|
318
|
+
end
|
319
|
+
|
320
|
+
#
|
321
|
+
# Added by dan - 81030
|
322
|
+
class Date < Type::Core
|
323
|
+
class << self; def subname; return 'date'; end; end
|
324
|
+
|
325
|
+
include Type::NoParams
|
326
|
+
|
327
|
+
def check!(value)
|
328
|
+
unless value.instance_of?(::Date)
|
329
|
+
raise ValidationError("expected Date got #{value.inspect}", "/date")
|
330
|
+
end
|
331
|
+
true
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
class Def < Type::Core
|
336
|
+
class << self; def subname; return 'def'; end; end
|
337
|
+
include Type::NoParams
|
338
|
+
def check!(value); raise ValidationError.new("def failed", "/def") unless ! value.nil?; end
|
339
|
+
end
|
340
|
+
|
341
|
+
class Map < Type::Core
|
342
|
+
class << self; def subname; return 'map'; end; end
|
343
|
+
@@allowed_param = { 'values' => true, 'type' => true }
|
344
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
345
|
+
|
346
|
+
def initialize(param, rx)
|
347
|
+
super
|
348
|
+
|
349
|
+
unless param['values'] then
|
350
|
+
raise Rx::Exception.new("no values schema given for #{uri}")
|
351
|
+
end
|
352
|
+
|
353
|
+
@value_schema = rx.make_schema(param['values'])
|
354
|
+
end
|
355
|
+
|
356
|
+
def check!(value)
|
357
|
+
unless value.instance_of?(Hash) or value.class.to_s == "HashWithIndifferentAccess"
|
358
|
+
raise ValidationError.new("expected map got #{value.inspect}", "/map")
|
359
|
+
end
|
360
|
+
|
361
|
+
if @value_schema
|
362
|
+
value.each_value do |v|
|
363
|
+
begin
|
364
|
+
@value_schema.check!(v)
|
365
|
+
rescue ValidationError => e
|
366
|
+
e.path = "/map" + e.path
|
367
|
+
raise e
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
return true
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
class Nil < Type::Core
|
377
|
+
class << self; def subname; return 'nil'; end; end
|
378
|
+
include Type::NoParams
|
379
|
+
def check!(value); raise ValidationError.new("expected nil got #{value.inspect}", "/nil") unless value.nil?; true; end
|
380
|
+
end
|
381
|
+
|
382
|
+
class Num < Type::Core
|
383
|
+
class << self; def subname; return 'num'; end; end
|
384
|
+
@@allowed_param = { 'range' => true, 'type' => true, 'value' => true }
|
385
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
386
|
+
|
387
|
+
def initialize(param, rx)
|
388
|
+
super
|
389
|
+
|
390
|
+
if param.has_key?('value') then
|
391
|
+
if ! param['value'].kind_of?(Numeric) then
|
392
|
+
raise Rx::Exception.new("invalid value parameter for #{uri}")
|
393
|
+
end
|
394
|
+
|
395
|
+
@value = param['value']
|
396
|
+
end
|
397
|
+
|
398
|
+
if param['range'] then
|
399
|
+
@value_range = Rx::Helper::Range.new( param['range'] )
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def check!(value)
|
404
|
+
if not value.kind_of?(Numeric)
|
405
|
+
raise ValidationError.new("expected Numeric got #{value.inspect}", "/#{self.class.subname}")
|
406
|
+
end
|
407
|
+
if @value_range and not @value_range.check(value)
|
408
|
+
raise ValidationError.new("expected Numeric in range #{@value_range} got #{value.inspect}", "/#{self.class.subname}")
|
409
|
+
end
|
410
|
+
if @value and value != @value
|
411
|
+
raise ValidationError.new("expected Numeric to equal #{@value} got #{value.inspect}", "/#{self.class.subname}")
|
412
|
+
end
|
413
|
+
true
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
class Int < Type::Core::Num
|
418
|
+
class << self; def subname; return 'int'; end; end
|
419
|
+
|
420
|
+
def initialize(param, rx)
|
421
|
+
super
|
422
|
+
|
423
|
+
if @value and @value % 1 != 0 then
|
424
|
+
raise Rx::Exception.new("invalid value parameter for #{uri}")
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def check!(value)
|
429
|
+
super
|
430
|
+
unless value % 1 == 0
|
431
|
+
raise ValidationError.new("expected Integer got #{value.inspect}", "/int")
|
432
|
+
end
|
433
|
+
return true
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
437
|
+
class One < Type::Core
|
438
|
+
class << self; def subname; return 'one'; end; end
|
439
|
+
include Type::NoParams
|
440
|
+
|
441
|
+
def check!(value)
|
442
|
+
unless [ Numeric, String, TrueClass, FalseClass ].any? { |cls| value.kind_of?(cls) }
|
443
|
+
raise ValidationError.new("expected One got #{value.inspect}", "/one")
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
class Rec < Type::Core
|
449
|
+
class << self; def subname; return 'rec'; end; end
|
450
|
+
@@allowed_param = {
|
451
|
+
'type' => true,
|
452
|
+
'rest' => true,
|
453
|
+
'required' => true,
|
454
|
+
'optional' => true,
|
455
|
+
}
|
456
|
+
|
457
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
458
|
+
|
459
|
+
def initialize(param, rx)
|
460
|
+
super
|
461
|
+
|
462
|
+
@field = { }
|
463
|
+
|
464
|
+
@rest_schema = rx.make_schema(param['rest']) if param['rest']
|
465
|
+
|
466
|
+
[ 'optional', 'required' ].each { |type|
|
467
|
+
next unless param[type]
|
468
|
+
param[type].keys.each { |field|
|
469
|
+
if @field[field] then
|
470
|
+
raise Rx::Exception.new("#{field} in both required and optional")
|
471
|
+
end
|
472
|
+
|
473
|
+
@field[field] = {
|
474
|
+
:required => (type == 'required'),
|
475
|
+
:schema => rx.make_schema(param[type][field]),
|
476
|
+
}
|
477
|
+
}
|
478
|
+
}
|
479
|
+
end
|
480
|
+
|
481
|
+
def check!(value)
|
482
|
+
unless value.instance_of?(Hash) or value.class.to_s == "HashWithIndifferentAccess"
|
483
|
+
raise ValidationError.new("expected Hash got #{value.class}", "/rec")
|
484
|
+
end
|
485
|
+
|
486
|
+
rest = [ ]
|
487
|
+
|
488
|
+
value.each do |field, field_value|
|
489
|
+
unless @field[field] then
|
490
|
+
rest.push(field)
|
491
|
+
next
|
492
|
+
end
|
493
|
+
|
494
|
+
begin
|
495
|
+
@field[field][:schema].check!(field_value)
|
496
|
+
rescue ValidationError => e
|
497
|
+
e.path = "/rec:'#{field}'"
|
498
|
+
raise e
|
499
|
+
end
|
500
|
+
end
|
501
|
+
|
502
|
+
@field.select { |k,v| @field[k][:required] }.each do |pair|
|
503
|
+
unless value.has_key?(pair[0])
|
504
|
+
raise ValidationError.new("expected Hash to have key: '#{pair[0]}', only had #{value.keys.inspect}", "/rec")
|
505
|
+
end
|
506
|
+
end
|
507
|
+
|
508
|
+
if rest.length > 0 then
|
509
|
+
unless @rest_schema
|
510
|
+
raise ValidationError.new("Hash had extra keys: #{rest.inspect}", "/rec")
|
511
|
+
end
|
512
|
+
rest_hash = { }
|
513
|
+
rest.each { |field| rest_hash[field] = value[field] }
|
514
|
+
begin
|
515
|
+
@rest_schema.check!(rest_hash)
|
516
|
+
rescue ValidationError => e
|
517
|
+
e.path = "/rec"
|
518
|
+
raise e
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
return true
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
class Seq < Type::Core
|
527
|
+
class << self; def subname; return 'seq'; end; end
|
528
|
+
@@allowed_param = { 'tail' => true, 'contents' => true, 'type' => true }
|
529
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
530
|
+
|
531
|
+
def initialize(param, rx)
|
532
|
+
super
|
533
|
+
|
534
|
+
unless param['contents'] and param['contents'].kind_of?(Array) then
|
535
|
+
raise Rx::Exception.new("missing or invalid contents for #{uri}")
|
536
|
+
end
|
537
|
+
|
538
|
+
@content_schemata = param['contents'].map { |s| rx.make_schema(s) }
|
539
|
+
|
540
|
+
if param['tail'] then
|
541
|
+
@tail_schema = rx.make_schema(param['tail'])
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def check!(value)
|
546
|
+
unless value.instance_of?(Array)
|
547
|
+
raise ValidationError.new("expected Array got #{value.inspect}", "/seq")
|
548
|
+
end
|
549
|
+
if value.length < @content_schemata.length
|
550
|
+
raise ValidationError.new("expected Array to have at least #{@content_schemata.length} elements, had #{value.length}", "/seq")
|
551
|
+
end
|
552
|
+
@content_schemata.each_index { |i|
|
553
|
+
begin
|
554
|
+
@content_schemata[i].check!(value[i])
|
555
|
+
rescue ValidationError => e
|
556
|
+
e.path = "/seq" + e.path
|
557
|
+
raise e
|
558
|
+
end
|
559
|
+
}
|
560
|
+
|
561
|
+
if value.length > @content_schemata.length then
|
562
|
+
unless @tail_schema
|
563
|
+
raise ValidationError.new("expected tail_schema", "/seq")
|
564
|
+
end
|
565
|
+
begin
|
566
|
+
@tail_schema.check!(value[
|
567
|
+
@content_schemata.length,
|
568
|
+
value.length - @content_schemata.length
|
569
|
+
])
|
570
|
+
rescue ValidationError => e
|
571
|
+
e.path = "/seq" + e.path
|
572
|
+
raise e
|
573
|
+
end
|
574
|
+
end
|
575
|
+
|
576
|
+
return true
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
class Str < Type::Core
|
581
|
+
class << self; def subname; return 'str'; end; end
|
582
|
+
@@allowed_param = { 'type' => true, 'value' => true, 'length' => true }
|
583
|
+
def allowed_param?(p); return @@allowed_param[p]; end
|
584
|
+
|
585
|
+
def initialize(param, rx)
|
586
|
+
super
|
587
|
+
|
588
|
+
if param['length'] then
|
589
|
+
@length_range = Rx::Helper::Range.new( param['length'] )
|
590
|
+
end
|
591
|
+
|
592
|
+
if param.has_key?('value') then
|
593
|
+
if ! param['value'].instance_of?(String) then
|
594
|
+
raise Rx::Exception.new("invalid value parameter for #{uri}")
|
595
|
+
end
|
596
|
+
|
597
|
+
@value = param['value']
|
598
|
+
end
|
599
|
+
end
|
600
|
+
|
601
|
+
def check!(value)
|
602
|
+
unless value.instance_of?(String)
|
603
|
+
raise ValidationError.new("expected String got #{value.inspect}", "/str")
|
604
|
+
end
|
605
|
+
|
606
|
+
if @length_range
|
607
|
+
unless @length_range.check(value.length)
|
608
|
+
raise ValidationError.new("expected string with #{@length_range} characters, got #{value.length}", "/str")
|
609
|
+
end
|
610
|
+
end
|
611
|
+
|
612
|
+
if @value and value != @value
|
613
|
+
raise ValidationError.new("expected #{@value.inspect} got #{value.inspect}", "/str")
|
614
|
+
end
|
615
|
+
return true
|
616
|
+
end
|
617
|
+
end
|
618
|
+
|
619
|
+
#
|
620
|
+
# Added by dan - 81106
|
621
|
+
class Time < Type::Core
|
622
|
+
class << self; def subname; return 'time'; end; end
|
623
|
+
|
624
|
+
include Type::NoParams
|
625
|
+
|
626
|
+
def check!(value)
|
627
|
+
unless value.instance_of?(::Time)
|
628
|
+
raise ValidationError.new("expected Time got #{value.inspect}", "/time")
|
629
|
+
end
|
630
|
+
true
|
631
|
+
end
|
632
|
+
end
|
633
|
+
|
634
|
+
class << self
|
635
|
+
def core_types
|
636
|
+
return [
|
637
|
+
Type::Core::All,
|
638
|
+
Type::Core::Any,
|
639
|
+
Type::Core::Arr,
|
640
|
+
Type::Core::Bool,
|
641
|
+
Type::Core::Date,
|
642
|
+
Type::Core::Def,
|
643
|
+
Type::Core::Fail,
|
644
|
+
Type::Core::Int,
|
645
|
+
Type::Core::Map,
|
646
|
+
Type::Core::Nil,
|
647
|
+
Type::Core::Num,
|
648
|
+
Type::Core::One,
|
649
|
+
Type::Core::Rec,
|
650
|
+
Type::Core::Seq,
|
651
|
+
Type::Core::Str,
|
652
|
+
Type::Core::Time
|
653
|
+
]
|
654
|
+
end
|
655
|
+
end
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: schema-validator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lars Lockefeer
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-27 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "\n A JSON schema validation tool.\n "
|
14
|
+
email:
|
15
|
+
- lars.lockefeer@teampicnic.com
|
16
|
+
executables:
|
17
|
+
- schema-validator
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- bin/schema-validator
|
24
|
+
- lib/rx/ruby/Rx.rb
|
25
|
+
homepage: https://github.com/larslockefeer/schema-validator
|
26
|
+
licenses:
|
27
|
+
- MIT
|
28
|
+
metadata: {}
|
29
|
+
post_install_message:
|
30
|
+
rdoc_options: []
|
31
|
+
require_paths:
|
32
|
+
- lib/rx/ruby
|
33
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - "~>"
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '2.0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
requirements: []
|
44
|
+
rubyforge_project:
|
45
|
+
rubygems_version: 2.4.8
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: A JSON schema validation tool.
|
49
|
+
test_files: []
|