kwalify 0.1.0 → 0.2.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.
- 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
|