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
data/lib/kwalify/rule.rb
ADDED
@@ -0,0 +1,326 @@
|
|
1
|
+
###
|
2
|
+
### $Rev: 18 $
|
3
|
+
### $Release: 0.2.0 $
|
4
|
+
### copyright(c) 2005 kuwata-lab all rights reserved.
|
5
|
+
###
|
6
|
+
|
7
|
+
require 'kwalify/messages'
|
8
|
+
require 'kwalify/errors'
|
9
|
+
require 'kwalify/types'
|
10
|
+
|
11
|
+
module Kwalify
|
12
|
+
|
13
|
+
class Rule
|
14
|
+
include Kwalify::ErrorHelper
|
15
|
+
|
16
|
+
def initialize(hash=nil)
|
17
|
+
configure(hash, "", {}) if hash
|
18
|
+
end
|
19
|
+
|
20
|
+
def configure(hash, path="", rule_table={})
|
21
|
+
unless hash.is_a?(Hash)
|
22
|
+
#* key=:schema_notmap msg="schema definition is not a mapping."
|
23
|
+
raise Kwalify.schema_error(:schema_notmap, nil, "/", nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
rule = self
|
27
|
+
rule_table[hash.__id__] = rule
|
28
|
+
|
29
|
+
## 'type:'
|
30
|
+
@type = hash['type']
|
31
|
+
@type = Kwalify::DEFAULT_TYPE if @type == nil
|
32
|
+
unless @type.is_a?(String)
|
33
|
+
#* key=:type_notstr msg="not a string."
|
34
|
+
raise schema_error(:type_notstr, rule, "#{path}/type", @type.to_s)
|
35
|
+
end
|
36
|
+
@klass = Kwalify.get_type_class(@type)
|
37
|
+
#if @klass == nil
|
38
|
+
# begin
|
39
|
+
# @klass = Kernel.const_get(@type)
|
40
|
+
# rescue NameError
|
41
|
+
# end
|
42
|
+
#end
|
43
|
+
unless @klass
|
44
|
+
#* key=:type_unknown msg="unknown type."
|
45
|
+
raise schema_error(:type_unknown, rule, "#{path}/type", @type.to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
## other key
|
49
|
+
hash.each do |key, val|
|
50
|
+
curr_path = "#{path}/#{key}"
|
51
|
+
case key
|
52
|
+
#when "id"
|
53
|
+
# @id = val
|
54
|
+
when "name"
|
55
|
+
@name = val
|
56
|
+
|
57
|
+
when "desc"
|
58
|
+
@desc = val
|
59
|
+
|
60
|
+
when "type"
|
61
|
+
# done
|
62
|
+
|
63
|
+
when "required"
|
64
|
+
@required = val
|
65
|
+
unless val == nil || val.is_a?(Boolean)
|
66
|
+
#* key=:required_notbool msg="not a boolean."
|
67
|
+
raise schema_error(:required_notbool, rule, curr_path, val)
|
68
|
+
end
|
69
|
+
|
70
|
+
when "pattern"
|
71
|
+
pat = (val =~ /\A\/(.*)\/([mi]?[mi]?)\z/ ? $1 : val)
|
72
|
+
begin
|
73
|
+
@pattern = Regexp.compile(pat)
|
74
|
+
rescue RegexpError => ex
|
75
|
+
#* key=:pattern_syntaxerr msg="has regexp error."
|
76
|
+
raise schema_error(:pattern_syntaxerr, rule, curr_path, val)
|
77
|
+
end
|
78
|
+
|
79
|
+
when "enum"
|
80
|
+
@enum = val
|
81
|
+
unless val.is_a?(Array)
|
82
|
+
#* key=:enum_notseq msg="not a sequence."
|
83
|
+
raise schema_error(:enum_notseq, rule, curr_path, val)
|
84
|
+
end
|
85
|
+
if @type == 'seq' || @type == 'map' # unless Kwalify.scalar_class?(@klass)
|
86
|
+
#* key=:enum_notscalar msg="not available with seq or map."
|
87
|
+
raise schema_error(:enum_notscalar, rule, path, 'enum:')
|
88
|
+
end
|
89
|
+
elem_table = {}
|
90
|
+
@enum.each do |elem|
|
91
|
+
unless !elem_table[elem]
|
92
|
+
#* key=:enum_duplicate msg="duplicated enum value."
|
93
|
+
raise schema_error(:enum_duplicate, rule, curr_path, elem.to_s)
|
94
|
+
end
|
95
|
+
elem_table[elem] = true
|
96
|
+
unless elem.is_a?(@klass)
|
97
|
+
#* key=:enum_type_unmatch msg="%s type expected."
|
98
|
+
raise schema_error(:enum_type_unmatch, rule, curr_path, elem, [Kwalify.word(@type)])
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
when "assert" # or "cond-expr" ?
|
103
|
+
unless val.is_a?(String)
|
104
|
+
#* key=:assert_notstr msg="not a string."
|
105
|
+
raise schema_error(:assert_notstr, rule, curr_path, val)
|
106
|
+
end
|
107
|
+
unless val =~ /\bval\b/
|
108
|
+
#* key=:assert_noval msg="'val' is not used."
|
109
|
+
raise schema_error(:assert_noval, rule, curr_path, val)
|
110
|
+
end
|
111
|
+
begin
|
112
|
+
@assert = val
|
113
|
+
@assert_proc = eval "proc { |val| #{val} }"
|
114
|
+
rescue SyntaxError => ex
|
115
|
+
#* key=:assert_syntaxerr msg="expression syntax error."
|
116
|
+
raise schema_error(:assert_syntaxerr, rule, curr_path, val)
|
117
|
+
end
|
118
|
+
|
119
|
+
when "range"
|
120
|
+
@range = val
|
121
|
+
unless val.is_a?(Hash)
|
122
|
+
#* key=:range_notmap msg="not a mapping."
|
123
|
+
raise schema_error(:range_notmap, rule, curr_path, val)
|
124
|
+
end
|
125
|
+
if @type == 'map' || @type == 'seq' || @type == 'bool'
|
126
|
+
#* key=:range_notscalar msg="is available only with scalar type."
|
127
|
+
raise schema_error(:range_notscalar, rule, path, 'range:')
|
128
|
+
end
|
129
|
+
val.each do |rkey, rval|
|
130
|
+
case rkey
|
131
|
+
when 'max', 'min'
|
132
|
+
unless rval.is_a?(@klass)
|
133
|
+
typename = Kwalify.word(@type) || @type
|
134
|
+
#* key=:range_type_unmatch msg="not a %s."
|
135
|
+
raise schema_error(:range_type_unmatch, rule, "#{curr_path}/#{rkey}", rval, [typename])
|
136
|
+
end
|
137
|
+
else
|
138
|
+
#* key=:range_undefined msg="undefined key."
|
139
|
+
raise schema_error(:range_undefined, rule, curr_path, "#{rkey}:")
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
when "length" # or "width"
|
144
|
+
@length = val
|
145
|
+
unless val.is_a?(Hash)
|
146
|
+
#* key=:length_notmap msg="not a mapping."
|
147
|
+
raise schema_error(:length_notmap, rule, curr_path, val)
|
148
|
+
end
|
149
|
+
unless @type == 'str' || @type == 'text'
|
150
|
+
#* key=:length_nottext msg="is available only with string or text."
|
151
|
+
raise schema_error(:length_nottext, rule, path, 'length:')
|
152
|
+
end
|
153
|
+
val.each do |lkey, lval|
|
154
|
+
case lkey
|
155
|
+
when 'max', 'min'
|
156
|
+
unless lval.is_a?(Integer)
|
157
|
+
#* key=:length_notint msg="not an integer."
|
158
|
+
raise schema_error(:length_notint, rule, "#{curr_path}/#{lkey}", lval)
|
159
|
+
end
|
160
|
+
else
|
161
|
+
#* key=:length_undefined msg="undefined key."
|
162
|
+
raise schema_error(:length_undefined, rule, curr_path, "#{lkey}:")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
when "sequence"
|
167
|
+
if val != nil && !val.is_a?(Array)
|
168
|
+
#* key=:sequence_notseq msg="not a sequence."
|
169
|
+
raise schema_error(:sequence_notseq, rule, curr_path, val)
|
170
|
+
elsif val == nil || val.empty?
|
171
|
+
#* key=:sequence_noelem msg="required one element."
|
172
|
+
raise schema_error(:sequence_noelem, rule, curr_path, val)
|
173
|
+
elsif val.length > 1
|
174
|
+
#* key=:sequence_toomany msg="required just one element."
|
175
|
+
raise schema_error(:sequence_toomany, rule, curr_path, val)
|
176
|
+
else
|
177
|
+
elem = val[0]
|
178
|
+
elem ||= {}
|
179
|
+
i = 0 # or 1? *index*
|
180
|
+
rule = rule_table[elem.__id__]
|
181
|
+
rule ||= Rule.new.configure(elem, "#{curr_path}/#{i}", rule_table)
|
182
|
+
@sequence = [ rule ]
|
183
|
+
end
|
184
|
+
|
185
|
+
when "mapping"
|
186
|
+
if val != nil && !val.is_a?(Hash)
|
187
|
+
#* key=:mapping_notmap msg="not a mapping."
|
188
|
+
raise schema_error(:mapping_notmap, rule, curr_path, val)
|
189
|
+
elsif val == nil || val.empty?
|
190
|
+
#* key=:mapping_noelem msg="required at least one element."
|
191
|
+
raise schema_error(:mapping_noelem, rule, curr_path, val)
|
192
|
+
else
|
193
|
+
@mapping = {}
|
194
|
+
val.each do |key, elem|
|
195
|
+
##* key=:key_duplicate msg="key duplicated."
|
196
|
+
#raise schema_error(:key_duplicate, rule, curr_path, key) if @mapping.key?(key)
|
197
|
+
elem ||= {}
|
198
|
+
rule = rule_table[elem.__id__]
|
199
|
+
rule ||= Rule.new.configure(elem, "#{curr_path}/#{key}", rule_table)
|
200
|
+
if key == '*'
|
201
|
+
@mapping.default = rule
|
202
|
+
else
|
203
|
+
@mapping[key] = rule
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
else
|
209
|
+
#* key=:key_unknown msg="unknown key."
|
210
|
+
raise schema_error(:key_unknown, rule, path, "#{key}:")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if @type == 'seq'
|
215
|
+
#* key=:seq_nosequence msg="type 'seq' requires 'sequence:'."
|
216
|
+
raise Kwalify.validate_error(:seq_nosequence, rule, path, nil) unless hash.key?('sequence')
|
217
|
+
#* key=:seq_conflict msg="not available with sequence."
|
218
|
+
raise schema_error(:seq_conflict, rule, path, 'enum:') if @enum
|
219
|
+
raise schema_error(:seq_conflict, rule, path, 'pattern:') if @pattern
|
220
|
+
raise schema_error(:seq_conflict, rule, path, 'mapping:') if @mapping
|
221
|
+
raise schema_error(:seq_conflict, rule, path, 'range:') if @range
|
222
|
+
raise schema_error(:seq_conflict, rule, path, 'length:') if @length
|
223
|
+
elsif @type == 'map'
|
224
|
+
#* key=:map_nomapping msg="type 'map' requires 'mapping:'."
|
225
|
+
raise Kwalify.validate_error(:map_nomapping, rule, path, nil) unless hash.key?('mapping')
|
226
|
+
#* key=:map_conflict msg="not available with mapping."
|
227
|
+
raise schema_error(:map_conflict, rule, path, 'enum:') if @enum
|
228
|
+
raise schema_error(:map_conflict, rule, path, 'pattern:') if @pattern
|
229
|
+
raise schema_error(:map_conflict, rule, path, 'sequence:') if @sequence
|
230
|
+
raise schema_error(:map_conflict, rule, path, 'range:') if @range
|
231
|
+
raise schema_error(:map_conflict, rule, path, 'length:') if @length
|
232
|
+
else
|
233
|
+
#* key=:scalar_conflict msg="not available with scalar type."
|
234
|
+
raise schema_error(:scalar_conflict, rule, path, 'sequence:') if @sequence
|
235
|
+
raise schema_error(:scalar_conflict, rule, path, 'mapping:') if @mapping
|
236
|
+
if @enum
|
237
|
+
#* key=:enum_conflict msg="not available with 'enum:'."
|
238
|
+
raise schema_error(:enum_conflict, rule, path, 'range:') if @range
|
239
|
+
raise schema_error(:enum_conflict, rule, path, 'length:') if @length
|
240
|
+
raise schema_error(:enum_conflict, rule, path, 'pattern:') if @pattern
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
return self
|
245
|
+
|
246
|
+
end # end of def configure
|
247
|
+
|
248
|
+
#attr_reader :id
|
249
|
+
attr_reader :name
|
250
|
+
attr_reader :desc
|
251
|
+
attr_reader :enum
|
252
|
+
attr_reader :required
|
253
|
+
attr_reader :type
|
254
|
+
attr_reader :klass
|
255
|
+
attr_reader :pattern
|
256
|
+
attr_reader :sequence
|
257
|
+
attr_reader :mapping
|
258
|
+
attr_reader :assert
|
259
|
+
attr_reader :assert_proc
|
260
|
+
attr_reader :range
|
261
|
+
attr_reader :length
|
262
|
+
|
263
|
+
|
264
|
+
#def inspect()
|
265
|
+
# str = ""; level = 0; done = {}
|
266
|
+
# _inspect(str, level, done)
|
267
|
+
# return str
|
268
|
+
#end
|
269
|
+
|
270
|
+
|
271
|
+
#protected
|
272
|
+
|
273
|
+
|
274
|
+
def _inspect(str="", level=0, done={})
|
275
|
+
done[self.__id__] = true
|
276
|
+
str << " " * level << "name: #{@name}\n" if @name != nil
|
277
|
+
str << " " * level << "desc: #{@desc}\n" if @desc != nil
|
278
|
+
str << " " * level << "type: #{@type}\n" if @type != nil
|
279
|
+
str << " " * level << "klass: #{@klass.name}\n" if @klass != nil
|
280
|
+
str << " " * level << "required: #{@required}\n" if @required != nil
|
281
|
+
str << " " * level << "pattern: #{@pattern.inspect}\n" if @pattern != nil
|
282
|
+
str << " " * level << "assert: #{@assert}\n" if @assert != nil
|
283
|
+
if @enum != nil
|
284
|
+
str << " " * level << "enum:\n"
|
285
|
+
@enum.each do |item|
|
286
|
+
str << " " * (level+1) << "- #{item}\n"
|
287
|
+
end
|
288
|
+
end
|
289
|
+
if @range != nil
|
290
|
+
str << " " * level
|
291
|
+
str << "range: { "
|
292
|
+
str << "max: #{@range['max'].inspect}" if @range['max'] != nil
|
293
|
+
str << ", " if @range['max'] != nil && @range['min'] != nil
|
294
|
+
str << "min: #{@range['min'].inspect}" if @range['min'] != nil
|
295
|
+
str << "}\n"
|
296
|
+
end
|
297
|
+
if @length != nil
|
298
|
+
str << " " * level
|
299
|
+
str << "length: { "
|
300
|
+
str << "max: #{@length['max'].inspect}" if @length['max'] != nil
|
301
|
+
str << ", " if @length['max'] != nil && @length['min'] != nil
|
302
|
+
str << "min: #{@length['min'].inspect}" if @length['min'] != nil
|
303
|
+
str << "}\n"
|
304
|
+
end
|
305
|
+
@sequence.each do |rule|
|
306
|
+
if done[rule.__id__]
|
307
|
+
str << " " * (level+1) << "- ...\n"
|
308
|
+
else
|
309
|
+
str << " " * (level+1) << "- \n"
|
310
|
+
rule._inspect(str, level+2, done)
|
311
|
+
end
|
312
|
+
end if @sequence
|
313
|
+
@mapping.each do |key, rule|
|
314
|
+
if done[rule.__id__]
|
315
|
+
str << ' ' * (level+1) << '"' << key << "\": ...\n"
|
316
|
+
else
|
317
|
+
str << ' ' * (level+1) << '"' << key << "\":\n"
|
318
|
+
rule._inspect(str, level+2, done)
|
319
|
+
end
|
320
|
+
end if @mapping
|
321
|
+
return str
|
322
|
+
end
|
323
|
+
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
data/lib/kwalify/types.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
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
|
|
@@ -11,60 +11,126 @@ module Kwalify
|
|
11
11
|
module Boolean
|
12
12
|
end
|
13
13
|
end
|
14
|
-
#module Boolean; end
|
15
14
|
class TrueClass
|
16
15
|
include Kwalify::Boolean
|
17
|
-
#include Boolean
|
18
16
|
end
|
19
17
|
class FalseClass
|
20
18
|
include Kwalify::Boolean
|
21
|
-
#include Boolean
|
22
19
|
end
|
20
|
+
#module Boolean; end
|
21
|
+
#class TrueClass
|
22
|
+
# include Boolean
|
23
|
+
#end
|
24
|
+
#class FalseClass
|
25
|
+
# include Boolean
|
26
|
+
#end
|
23
27
|
|
24
28
|
|
25
29
|
module Kwalify
|
26
30
|
module Text
|
27
31
|
end
|
28
32
|
end
|
29
|
-
#module Text; end
|
30
33
|
class String
|
31
34
|
include Kwalify::Text
|
32
|
-
#include Text
|
33
35
|
end
|
34
36
|
class Numeric
|
35
37
|
include Kwalify::Text
|
36
|
-
|
38
|
+
end
|
39
|
+
#module Text; end
|
40
|
+
#class String
|
41
|
+
# include Text
|
42
|
+
#end
|
43
|
+
#class Numeric
|
44
|
+
# include Text
|
45
|
+
#end
|
46
|
+
|
47
|
+
|
48
|
+
module Kwalify
|
49
|
+
module Scalar
|
50
|
+
end
|
51
|
+
end
|
52
|
+
class String
|
53
|
+
include Kwalify::Scalar
|
54
|
+
end
|
55
|
+
class Numeric
|
56
|
+
include Kwalify::Scalar
|
57
|
+
end
|
58
|
+
class Date
|
59
|
+
include Kwalify::Scalar
|
60
|
+
end
|
61
|
+
class Time
|
62
|
+
include Kwalify::Scalar
|
63
|
+
end
|
64
|
+
class TrueClass
|
65
|
+
include Kwalify::Scalar
|
66
|
+
end
|
67
|
+
class FalseClass
|
68
|
+
include Kwalify::Scalar
|
69
|
+
end
|
70
|
+
class NilClass
|
71
|
+
include Kwalify::Scalar
|
72
|
+
end
|
73
|
+
module Kwalify
|
74
|
+
module Text
|
75
|
+
include Kwalify::Scalar
|
76
|
+
end
|
37
77
|
end
|
38
78
|
|
39
79
|
|
40
80
|
module Kwalify
|
41
81
|
|
42
|
-
DEFAULT_TYPE = "
|
82
|
+
DEFAULT_TYPE = "str" ## use "str" as default of @type
|
43
83
|
|
44
84
|
@@type_table = {
|
45
85
|
"seq" => Array,
|
46
|
-
"sequence" => Array,
|
47
|
-
#"list" => Array,
|
48
|
-
#"array" => Array,
|
49
86
|
"map" => Hash,
|
50
|
-
"
|
51
|
-
#"
|
87
|
+
"str" => String,
|
88
|
+
#"string" => String,
|
52
89
|
"text" => Text,
|
53
|
-
"
|
54
|
-
"integer" => Integer,
|
90
|
+
"int" => Integer,
|
91
|
+
#"integer" => Integer,
|
55
92
|
"float" => Float,
|
56
93
|
"number" => Numeric,
|
57
94
|
#"numeric" => Numeric,
|
58
95
|
"date" => Date,
|
59
96
|
"time" => Time,
|
60
|
-
"
|
61
|
-
|
62
|
-
"
|
97
|
+
"timestamp" => Time,
|
98
|
+
"bool" => Boolean,
|
99
|
+
#"boolean" => Boolean,
|
100
|
+
#"object" => Object,
|
63
101
|
"any" => Object,
|
102
|
+
"scalar" => Scalar,
|
64
103
|
}
|
65
|
-
|
104
|
+
|
66
105
|
def self.type_table
|
67
106
|
return @@type_table
|
68
107
|
end
|
69
108
|
|
109
|
+
def self.get_type_class(type)
|
110
|
+
klass = @@type_table[type]
|
111
|
+
#assert_error('type=#{type.inspect}') unless klass
|
112
|
+
return klass
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
module TypeHelper
|
117
|
+
def collection_class?(klass)
|
118
|
+
return klass == Array || klass == Hash
|
119
|
+
end
|
120
|
+
|
121
|
+
def scalar_class?(klass)
|
122
|
+
return klass != Array && klass != Hash && klass != Object
|
123
|
+
end
|
124
|
+
|
125
|
+
def collection?(val)
|
126
|
+
return collection_class?(val.class)
|
127
|
+
end
|
128
|
+
|
129
|
+
def scalar?(val)
|
130
|
+
return scalar_class?(val.class)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
extend TypeHelper
|
135
|
+
|
70
136
|
end
|