kwalify 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +29 -62
- data/README.txt +39 -11
- data/bin/kwalify +16 -177
- data/doc/users-guide.html +279 -145
- data/examples/address-book/Makefile +1 -0
- data/examples/address-book/address-book.schema.yaml +10 -10
- data/examples/invoice/Makefile +1 -0
- data/examples/invoice/invoice.schema.yaml +17 -34
- data/examples/tapkit/Makefile +5 -0
- data/examples/tapkit/tapkit.schema.yaml +137 -0
- data/examples/tapkit/tapkit.yaml +85 -0
- data/lib/kwalify.rb +12 -3
- data/lib/kwalify/errors.rb +36 -60
- data/lib/kwalify/main-program.rb +214 -0
- data/lib/kwalify/messages.rb +91 -0
- data/lib/kwalify/meta-validator.rb +199 -76
- data/lib/kwalify/rule.rb +326 -0
- data/lib/kwalify/types.rb +86 -20
- data/lib/kwalify/util/assert-diff.rb +1 -1
- data/lib/kwalify/util/option-parser.rb +1 -1
- data/lib/kwalify/util/yaml-helper.rb +6 -6
- data/lib/kwalify/validator.rb +111 -218
- data/test/test-metavalidator.rb +608 -0
- data/test/test-metavalidator.rb.error +490 -0
- data/test/test-validator.rb +526 -0
- data/test/test.rb +12 -351
- data/todo.txt +14 -4
- metadata +12 -3
- data/lib/kwalify/error-msg.rb +0 -41
@@ -1,6 +1,6 @@
|
|
1
1
|
###
|
2
|
-
### $Rev:
|
3
|
-
### $Release: 0.
|
2
|
+
### $Rev: 10 $
|
3
|
+
### $Release: 0.2.0 $
|
4
4
|
### copyright(c) 2005 kuwata-lab all rights reserved.
|
5
5
|
###
|
6
6
|
|
@@ -12,7 +12,7 @@ module YamlHelper
|
|
12
12
|
## expand tab character to spaces
|
13
13
|
##
|
14
14
|
## ex.
|
15
|
-
## untabified_str =
|
15
|
+
## untabified_str = YamlHelper.untabify(tabbed_str)
|
16
16
|
##
|
17
17
|
## input:: String or IO
|
18
18
|
##
|
@@ -34,7 +34,7 @@ module YamlHelper
|
|
34
34
|
## { "name"=>"Bar", "gender"=>"F", "age"=>25, },
|
35
35
|
## { "name"=>"Baz", "gender"=>"M", "age"=>30, },
|
36
36
|
## ]
|
37
|
-
## hashtable =
|
37
|
+
## hashtable = YamlHelper.create_hashtable(hashlist, "name")
|
38
38
|
## p hashtable
|
39
39
|
## # => { "Foo" => { "name"=>"Foo", "gender"=>"M", "age"=>20, },
|
40
40
|
## "Bar" => { "name"=>"Bar", "gender"=>"F", "age"=>25, },
|
@@ -60,7 +60,7 @@ module YamlHelper
|
|
60
60
|
## get nested value directly.
|
61
61
|
##
|
62
62
|
## ex.
|
63
|
-
## val =
|
63
|
+
## val = YamlHelper.get_value(obj, ['aaa', 0, 'xxx'])
|
64
64
|
##
|
65
65
|
## This is equal to the following:
|
66
66
|
## begin
|
@@ -72,8 +72,8 @@ module YamlHelper
|
|
72
72
|
def self.get_value(obj, path)
|
73
73
|
val = obj
|
74
74
|
path.each do |key|
|
75
|
+
return nil unless val.is_a?(Hash) || val.is_a?(Array)
|
75
76
|
val = val[key]
|
76
|
-
return val unless val
|
77
77
|
end if path
|
78
78
|
return val
|
79
79
|
end
|
data/lib/kwalify/validator.rb
CHANGED
@@ -1,286 +1,179 @@
|
|
1
1
|
###
|
2
|
-
### $Rev:
|
3
|
-
### $Release: 0.
|
2
|
+
### $Rev: 18 $
|
3
|
+
### $Release: 0.2.0 $
|
4
4
|
### copyright(c) 2005 kuwata-lab all rights reserved.
|
5
5
|
###
|
6
6
|
|
7
|
-
require 'kwalify/
|
7
|
+
require 'kwalify/messages'
|
8
8
|
require 'kwalify/errors'
|
9
9
|
require 'kwalify/types'
|
10
|
-
require '
|
10
|
+
require 'kwalify/rule'
|
11
11
|
|
12
12
|
module Kwalify
|
13
13
|
|
14
14
|
|
15
15
|
##
|
16
16
|
## ex.
|
17
|
-
##
|
18
|
-
## validator = Kwalify::Validator.new(
|
19
|
-
##
|
20
|
-
## document = YAML.load(s)
|
17
|
+
## schema = YAML.load_file('schema.yaml')
|
18
|
+
## validator = Kwalify::Validator.new(schema)
|
19
|
+
## document = YAML.load_file('document.yaml')
|
21
20
|
## error_list = validator.validate(document)
|
22
21
|
## unless error_list.empty?
|
23
|
-
##
|
22
|
+
## error_list.each do |error|
|
23
|
+
## puts "- [#{error.path}] #{error.message}"
|
24
|
+
## end
|
24
25
|
## end
|
25
26
|
##
|
26
27
|
class Validator
|
28
|
+
include Kwalify::ErrorHelper
|
29
|
+
|
27
30
|
def initialize(hash, &block)
|
28
|
-
|
29
|
-
@schema = Schema.new(hash)
|
31
|
+
@rule = Rule.new(hash)
|
30
32
|
@block = block
|
31
33
|
end
|
32
|
-
attr_reader :
|
34
|
+
attr_reader :rule
|
33
35
|
|
34
|
-
def validate(obj)
|
35
|
-
@schema.validate(obj, &@block)
|
36
|
-
end
|
37
36
|
|
38
|
-
def
|
39
|
-
@
|
37
|
+
def _inspect
|
38
|
+
@rule._inspect
|
40
39
|
end
|
41
|
-
end
|
42
|
-
|
43
|
-
|
44
|
-
class Schema
|
45
|
-
include Kwalify::Errors
|
46
|
-
|
47
|
-
def initialize(hash, path="", schema_table={})
|
48
|
-
schema_table[hash.__id__] = self
|
49
|
-
hash.each do |key, val|
|
50
|
-
case key
|
51
|
-
#when "id"
|
52
|
-
# @id = val
|
53
|
-
when "name"
|
54
|
-
@name = val.to_s
|
55
|
-
when "desc"
|
56
|
-
@desc = val.to_s
|
57
|
-
when "type"
|
58
|
-
@type = val
|
59
|
-
@klass = Kwalify::type_table[val.downcase]
|
60
|
-
if @klass == nil
|
61
|
-
begin
|
62
|
-
@klass = Kernel.const_get(val)
|
63
|
-
rescue NameError
|
64
|
-
raise schema_error(:type_not_found, self, path)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
when "required"
|
68
|
-
unless val.is_a?(Boolean)
|
69
|
-
raise schema_error(:required_not_boolean, self, path)
|
70
|
-
else
|
71
|
-
@required = val
|
72
|
-
end
|
73
|
-
when "pattern"
|
74
|
-
pat = (val =~ /\A\/(.*)\/\z/ ? $1 : val)
|
75
|
-
begin
|
76
|
-
@pattern = Regexp.compile(pat)
|
77
|
-
rescue RegexpError => ex
|
78
|
-
raise schema_error(:regexp_error, self, path)
|
79
|
-
end
|
80
|
-
when "enum"
|
81
|
-
unless val.is_a?(Array)
|
82
|
-
raise schema_error(:enum_not_seq, self, path)
|
83
|
-
else
|
84
|
-
@enum = val
|
85
|
-
end
|
86
|
-
when "sequence"
|
87
|
-
if !val.is_a?(Array)
|
88
|
-
raise schema_error(:sequence_not_seq, self, path)
|
89
|
-
elsif val.length == 0
|
90
|
-
raise schema_error(:sequence_no_elem, self, path)
|
91
|
-
elsif val.length > 1
|
92
|
-
raise schema_error(:sequence_too_many, self, path)
|
93
|
-
else
|
94
|
-
elem = val[0]
|
95
|
-
elem ||= {}
|
96
|
-
schema = schema_table[elem.__id__]
|
97
|
-
i = 1
|
98
|
-
schema ||= Schema.new(elem, "#{path}/#{i}", schema_table)
|
99
|
-
@sequence = [ schema ]
|
100
|
-
end
|
101
|
-
when "mapping"
|
102
|
-
if !val.is_a?(Hash)
|
103
|
-
raise schema_error(:mapping_not_map, self, path)
|
104
|
-
elsif val.empty?
|
105
|
-
raise schema_error(:mapping_no_elem, self, path)
|
106
|
-
else
|
107
|
-
@mapping = {}
|
108
|
-
val.each do |key, elem|
|
109
|
-
raise schema_error(:duplicate_key, self, path, key) if @mapping.key?(key)
|
110
|
-
elem ||= {}
|
111
|
-
schema = schema_table[elem.__id__]
|
112
|
-
schema ||= Schema.new(elem, "#{path}/#{key}", schema_table)
|
113
|
-
if key == '*'
|
114
|
-
@mapping.default = schema
|
115
|
-
else
|
116
|
-
@mapping[key] = schema
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
else
|
121
|
-
raise schema_error(:unexpected_key, self, path, key)
|
122
|
-
end
|
123
|
-
end
|
124
40
|
|
125
|
-
if @type == nil
|
126
|
-
if @sequence
|
127
|
-
@type = 'seq'
|
128
|
-
elsif @mapping
|
129
|
-
@type = 'map'
|
130
|
-
else
|
131
|
-
@type = Kwalify::DEFAULT_TYPE
|
132
|
-
end
|
133
|
-
@klass = Kwalify::type_table[@type]
|
134
|
-
end
|
135
|
-
|
136
|
-
if @klass == Array
|
137
|
-
raise schema_error(:seq_has_enum, self, path) if @enum
|
138
|
-
raise schema_error(:seq_has_pattern, self, path) if @pattern
|
139
|
-
raise schema_error(:seq_has_mapping, self, path) if @mapping
|
140
|
-
elsif @klass == Hash
|
141
|
-
raise schema_error(:map_has_enum, self, path) if @enum
|
142
|
-
raise schema_error(:map_has_pattern, self, path) if @pattern
|
143
|
-
raise schema_error(:map_has_sequence, self, path) if @sequence
|
144
|
-
else
|
145
|
-
raise schema_error(:scalar_has_sequence, self, path) if @sequence
|
146
|
-
raise schema_error(:scalar_has_mapping, self, path) if @mapping
|
147
|
-
end
|
148
41
|
|
42
|
+
def validate(val)
|
43
|
+
path = ""; errors = []; done = {}
|
44
|
+
_validate(val, @rule, path, errors, done)
|
45
|
+
return errors
|
149
46
|
end
|
150
47
|
|
151
|
-
#attr_reader :id
|
152
|
-
attr_accessor :name
|
153
|
-
attr_accessor :desc
|
154
|
-
attr_accessor :enum
|
155
|
-
attr_accessor :required
|
156
|
-
attr_accessor :type
|
157
|
-
attr_accessor :klass
|
158
|
-
attr_accessor :pattern
|
159
|
-
attr_accessor :elements
|
160
|
-
#attr_accessor :constraints
|
161
48
|
|
49
|
+
protected
|
162
50
|
|
163
|
-
def inspect()
|
164
|
-
str = ""; level = 0; done = {}
|
165
|
-
_inspect(str, level, done)
|
166
|
-
return str
|
167
|
-
end
|
168
51
|
|
169
|
-
def
|
170
|
-
done[self.__id__] = true
|
171
|
-
str << " " * level << "name: #{@name}\n" if @name != nil
|
172
|
-
str << " " * level << "type: #{@type}\n" if @type != nil
|
173
|
-
str << " " * level << "klass: #{@klass.name}\n" if @klass != nil
|
174
|
-
str << " " * level << "required: #{@required}\n" if @required != nil
|
175
|
-
str << " " * level << "pattern: #{@pattern.inspect}\n" if @pattern != nil
|
176
|
-
if @enum != nil
|
177
|
-
str << " " * level << "enum:\n"
|
178
|
-
@enum.each do |item|
|
179
|
-
str << " " * (level+1) << "- #{item}\n"
|
180
|
-
end
|
181
|
-
end
|
182
|
-
@sequence.each do |schema|
|
183
|
-
if done[schema.__id__]
|
184
|
-
str << " " * (level+1) << "- ...\n"
|
185
|
-
else
|
186
|
-
str << " " * (level+1) << "- \n"
|
187
|
-
schema._inspect(str, level+2, done)
|
188
|
-
end
|
189
|
-
end if @sequence
|
190
|
-
@mapping.each do |key, schema|
|
191
|
-
if done[schema.__id__]
|
192
|
-
str << " " * (level+1) << key << ": ...\n"
|
193
|
-
else
|
194
|
-
str << " " * (level+1) << key << ":\n"
|
195
|
-
schema._inspect(str, level+2, done)
|
196
|
-
end
|
197
|
-
end if @mapping
|
52
|
+
def validate_hook(val, rule, path, errors)
|
198
53
|
end
|
199
|
-
protected :_inspect
|
200
54
|
|
201
55
|
|
202
|
-
def
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
return true if done[obj.__id__]
|
211
|
-
done[obj.__id__] = true
|
212
|
-
if @required && obj == nil
|
213
|
-
errors << validate_error(:missing_value, self, path, obj)
|
56
|
+
def _validate(val, rule, path, errors, done)
|
57
|
+
if Kwalify.collection_class?(val.class)
|
58
|
+
return true if done[val.__id__] # avoid infinite loop
|
59
|
+
done[val.__id__] = true
|
60
|
+
end
|
61
|
+
if rule.required && val == nil
|
62
|
+
#* key=:required_novalue msg="value required but none."
|
63
|
+
errors << validate_error(:required_novalue, rule, path, val)
|
214
64
|
return
|
215
65
|
end
|
216
|
-
if
|
217
|
-
|
66
|
+
if rule.klass && val != nil && !val.is_a?(rule.klass)
|
67
|
+
#* key=:type_unmatch msg="not a %s."
|
68
|
+
errors << validate_error(:type_unmatch, rule, path, val, [Kwalify.word(rule.type)])
|
218
69
|
return
|
219
70
|
end
|
220
71
|
#
|
221
72
|
n = errors.length
|
222
|
-
if
|
223
|
-
_validate_sequence(
|
224
|
-
elsif
|
225
|
-
_validate_mapping(
|
73
|
+
if rule.sequence
|
74
|
+
_validate_sequence(val, rule, path, errors, done)
|
75
|
+
elsif rule.mapping
|
76
|
+
_validate_mapping(val, rule, path, errors, done)
|
226
77
|
else
|
227
|
-
_validate_scalar(
|
78
|
+
_validate_scalar(val, rule, path, errors, done)
|
228
79
|
end
|
229
80
|
return unless errors.length == n
|
230
81
|
#
|
231
|
-
|
82
|
+
validate_hook(val, rule, path, errors)
|
83
|
+
@block.call(val, rule, path, errors) if @block
|
232
84
|
end
|
233
|
-
protected :_validate
|
234
85
|
|
235
86
|
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
87
|
+
private
|
88
|
+
|
89
|
+
|
90
|
+
def _validate_scalar(val, rule, path, errors, done)
|
91
|
+
assert_error("rule.sequence.class==#{rule.sequence.class.name} (expected NilClass)") if rule.sequence
|
92
|
+
assert_error("rule.mapping.class==#{rule.mapping.class.name} (expected NilClass)") if rule.mapping
|
93
|
+
if rule.assert_proc
|
94
|
+
unless rule.assert_proc.call(val)
|
95
|
+
#* key=:assert_failed msg="assertion expression failed (%s)."
|
96
|
+
errors << validate_error(:assert_failed, rule, path, val, [rule.assert])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
if rule.enum
|
100
|
+
unless rule.enum.include?(val)
|
101
|
+
keyname = File.basename(path)
|
102
|
+
keyname = 'enum' if keyname =~ /\A\d+\z/
|
103
|
+
#* key=:enum_notexist msg="invalid %s value."
|
104
|
+
errors << validate_error(:enum_notexist, rule, path, val, [keyname])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
#
|
108
|
+
return if val == nil
|
109
|
+
#
|
110
|
+
if rule.pattern
|
111
|
+
unless val.to_s =~ rule.pattern
|
112
|
+
#* key=:pattern_unmatch msg="not matched to pattern %s."
|
113
|
+
errors << validate_error(:pattern_unmatch, rule, path, val, [rule.pattern.inspect])
|
114
|
+
end
|
242
115
|
end
|
243
|
-
if
|
244
|
-
|
116
|
+
if rule.range
|
117
|
+
assert_error("val.class=#{val.class.name}") unless Kwalify.scalar?(val)
|
118
|
+
if rule.range['max'] && rule.range['max'] < val
|
119
|
+
#* key=:range_toolarge msg="too large (> max %s)."
|
120
|
+
errors << validate_error(:range_toolarge, rule, path, val, [rule.range['max'].to_s])
|
121
|
+
end
|
122
|
+
if rule.range['min'] && rule.range['min'] > val
|
123
|
+
#* key=:range_toosmall msg="too small (< min %s)."
|
124
|
+
errors << validate_error(:range_toosmall, rule, path, val, [rule.range['min'].to_s])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
if rule.length
|
128
|
+
assert_error("val.class=#{val.class.name}") unless val.is_a?(String) || val.is_a?(Text)
|
129
|
+
len = val.to_s.length
|
130
|
+
if rule.length['max'] && rule.length['max'] < len
|
131
|
+
#* key=:length_toolong msg="too long (length %d > max %d)."
|
132
|
+
errors << validate_error(:length_toolong, rule, path, val, [len, rule.length['max']])
|
133
|
+
end
|
134
|
+
if rule.length['min'] && rule.length['min'] > len
|
135
|
+
#* key=:length_tooshort msg="too short (length %d < min %d)."
|
136
|
+
errors << validate_error(:length_tooshort, rule, path, val, [len, rule.length['min']])
|
137
|
+
end
|
245
138
|
end
|
246
139
|
end
|
247
|
-
private :_validate_scalar
|
248
140
|
|
249
141
|
|
250
|
-
def _validate_sequence(list,
|
251
|
-
assert_error("
|
252
|
-
assert_error("
|
142
|
+
def _validate_sequence(list, seq_rule, path, errors, done)
|
143
|
+
assert_error("seq_rule.sequence.class==#{seq_rule.sequence.class.name} (expected Array)") unless seq_rule.sequence.is_a?(Array)
|
144
|
+
assert_error("seq_rule.sequence.length==#{seq_rule.sequence.length} (expected 1)") unless seq_rule.sequence.length == 1
|
253
145
|
return if list == nil
|
254
|
-
|
255
|
-
i = 0
|
256
|
-
list.each do |
|
146
|
+
rule = seq_rule.sequence[0]
|
147
|
+
i = -1 # or 0? *index*
|
148
|
+
list.each do |val|
|
257
149
|
i += 1
|
258
|
-
|
150
|
+
_validate(val, rule, "#{path}/#{i}", errors, done) ## validate recursively
|
259
151
|
end
|
260
152
|
end
|
261
|
-
private :_validate_sequence
|
262
153
|
|
263
154
|
|
264
|
-
def _validate_mapping(hash,
|
265
|
-
assert_error("
|
155
|
+
def _validate_mapping(hash, map_rule, path, errors, done)
|
156
|
+
assert_error("map_rule.mapping.class==#{map_rule.mapping.class.name} (expected Hash)") unless map_rule.mapping.is_a?(Hash)
|
266
157
|
return if hash == nil
|
267
|
-
|
268
|
-
if
|
269
|
-
|
158
|
+
map_rule.mapping.each do |key, rule|
|
159
|
+
if rule.required && !hash.key?(key)
|
160
|
+
#* key=:required_nokey msg="key '%s:' is required."
|
161
|
+
errors << validate_error(:required_nokey, rule, path, hash, [key])
|
270
162
|
end
|
271
163
|
end
|
272
164
|
hash.each do |key, value|
|
273
|
-
|
274
|
-
if
|
275
|
-
|
165
|
+
rule = map_rule.mapping[key]
|
166
|
+
if rule
|
167
|
+
_validate(value, rule, "#{path}/#{key}", errors, done) ## validate recursively
|
276
168
|
else
|
277
|
-
|
169
|
+
#* key=:key_undefined msg="key '%s' is undefined."
|
170
|
+
errors << validate_error(:key_undefined, rule, path, nil, ["#{key}:"])
|
171
|
+
##* key=:key_undefined msg="undefined key."
|
172
|
+
#errors << validate_error(:key_undefined, rule, path, "#{key}:")
|
278
173
|
end
|
279
174
|
end
|
280
175
|
end
|
281
|
-
private :_validate_mapping
|
282
|
-
|
283
176
|
|
284
177
|
end
|
285
|
-
end
|
286
178
|
|
179
|
+
end
|