kwalify 0.6.1 → 0.7.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.
Files changed (219) hide show
  1. data/CHANGES.txt +232 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.txt +16 -19
  4. data/bin/kwalify +3 -3
  5. data/contrib/inline-require +6 -4
  6. data/contrib/kwalify +3719 -2427
  7. data/doc-api/classes/CommandOptionError.html +17 -17
  8. data/doc-api/classes/CommandOptionParser.html +63 -63
  9. data/doc-api/classes/Kwalify.html +29 -7
  10. data/doc-api/classes/Kwalify/AssertionError.html +9 -9
  11. data/doc-api/classes/Kwalify/BaseError.html +72 -71
  12. data/doc-api/classes/Kwalify/BaseParser.html +461 -0
  13. data/doc-api/classes/Kwalify/CommandOptionError.html +11 -11
  14. data/doc-api/classes/Kwalify/ErrorHelper.html +51 -46
  15. data/doc-api/classes/Kwalify/HashInterface.html +13 -135
  16. data/doc-api/classes/Kwalify/Json.html +105 -0
  17. data/doc-api/classes/Kwalify/Main.html +129 -126
  18. data/doc-api/classes/Kwalify/MetaValidator.html +248 -232
  19. data/doc-api/classes/Kwalify/Parser.html +12 -12
  20. data/doc-api/classes/Kwalify/PlainYamlParser.html +166 -163
  21. data/doc-api/classes/Kwalify/PlainYamlParser/Alias.html +11 -11
  22. data/doc-api/classes/Kwalify/Rule.html +152 -130
  23. data/doc-api/classes/Kwalify/SchemaError.html +10 -10
  24. data/doc-api/classes/Kwalify/SyntaxError.html +185 -0
  25. data/doc-api/classes/Kwalify/Types.html +26 -25
  26. data/doc-api/classes/Kwalify/Util.html +389 -0
  27. data/doc-api/classes/Kwalify/Util/HashLike.html +246 -0
  28. data/doc-api/classes/Kwalify/Util/OrderedHash.html +330 -0
  29. data/doc-api/classes/Kwalify/ValidationError.html +10 -10
  30. data/doc-api/classes/Kwalify/Validator.html +153 -86
  31. data/doc-api/classes/Kwalify/Yaml.html +181 -0
  32. data/doc-api/classes/Kwalify/Yaml/Parser.html +1538 -0
  33. data/doc-api/classes/Kwalify/YamlParser.html +190 -183
  34. data/doc-api/classes/Kwalify/YamlSyntaxError.html +8 -57
  35. data/doc-api/created.rid +1 -1
  36. data/doc-api/files/__/README_txt.html +17 -22
  37. data/doc-api/files/kwalify/errors_rb.html +2 -2
  38. data/doc-api/files/kwalify/main_rb.html +4 -3
  39. data/doc-api/files/kwalify/messages_rb.html +2 -2
  40. data/doc-api/files/kwalify/meta-validator_rb.html +3 -3
  41. data/doc-api/files/kwalify/{util/yaml-helper_rb.html → parser/base_rb.html} +8 -6
  42. data/doc-api/files/kwalify/parser/yaml_rb.html +117 -0
  43. data/doc-api/files/kwalify/rule_rb.html +2 -2
  44. data/doc-api/files/kwalify/types_rb.html +2 -2
  45. data/doc-api/files/kwalify/util/assert-text-equal_rb.html +2 -2
  46. data/doc-api/files/kwalify/util/hash-interface_rb.html +9 -2
  47. data/doc-api/files/kwalify/util/hashlike_rb.html +107 -0
  48. data/doc-api/files/kwalify/util/option-parser_rb.html +2 -2
  49. data/doc-api/files/kwalify/util/ordered-hash_rb.html +107 -0
  50. data/doc-api/files/kwalify/util/testcase-helper_rb.html +2 -2
  51. data/doc-api/files/kwalify/util_rb.html +107 -0
  52. data/doc-api/files/kwalify/validator_rb.html +2 -2
  53. data/doc-api/files/kwalify/yaml-parser_rb.html +2 -2
  54. data/doc-api/files/kwalify_rb.html +3 -2
  55. data/doc-api/fr_class_index.html +8 -1
  56. data/doc-api/fr_file_index.html +5 -1
  57. data/doc-api/fr_method_index.html +128 -69
  58. data/doc/img/fig01.png +0 -0
  59. data/doc/users-guide.html +882 -717
  60. data/examples/address-book/address-book.schema.yaml +2 -2
  61. data/examples/data-binding/BABEL.data.yaml +63 -0
  62. data/examples/data-binding/BABEL.schema.yaml +31 -0
  63. data/examples/data-binding/Makefile +8 -0
  64. data/examples/data-binding/Rakefile +13 -0
  65. data/examples/data-binding/main.rb +27 -0
  66. data/examples/invoice/invoice.schema.yaml +3 -3
  67. data/examples/tapkit/tapkit.schema.yaml +2 -2
  68. data/lib/kwalify.rb +41 -4
  69. data/lib/kwalify/errors.rb +118 -96
  70. data/lib/kwalify/kwalify.schema.yaml +58 -0
  71. data/lib/kwalify/main.rb +384 -377
  72. data/lib/kwalify/messages.rb +41 -27
  73. data/lib/kwalify/meta-validator.rb +251 -331
  74. data/lib/kwalify/parser/base.rb +127 -0
  75. data/lib/kwalify/parser/yaml.rb +837 -0
  76. data/lib/kwalify/rule.rb +545 -487
  77. data/lib/kwalify/templates/genclass-java.eruby +189 -162
  78. data/lib/kwalify/templates/genclass-php.eruby +104 -0
  79. data/lib/kwalify/templates/genclass-ruby.eruby +95 -66
  80. data/lib/kwalify/types.rb +107 -106
  81. data/lib/kwalify/util.rb +157 -0
  82. data/lib/kwalify/util/assert-text-equal.rb +33 -31
  83. data/lib/kwalify/util/hash-interface.rb +11 -30
  84. data/lib/kwalify/util/hashlike.rb +51 -0
  85. data/lib/kwalify/util/option-parser.rb +144 -144
  86. data/lib/kwalify/util/ordered-hash.rb +57 -0
  87. data/lib/kwalify/util/testcase-helper.rb +3 -3
  88. data/lib/kwalify/validator.rb +267 -212
  89. data/lib/kwalify/yaml-parser.rb +822 -768
  90. data/setup.rb +861 -607
  91. data/test/Rookbook.yaml +10 -0
  92. data/test/{tmp.dir/Context.java → data/users-guide/AddressBook.java.expected} +11 -11
  93. data/test/data/users-guide/BABEL.data.yaml +24 -0
  94. data/test/data/users-guide/BABEL.schema.yaml +30 -0
  95. data/test/data/users-guide/ExampleAddressBook.java +47 -0
  96. data/test/{tmp.dir/Group.java → data/users-guide/Group.java.expected} +2 -11
  97. data/test/data/users-guide/Person.java.expected +44 -0
  98. data/test/data/users-guide/address_book.rb +52 -0
  99. data/test/data/users-guide/address_book.schema.yaml +28 -0
  100. data/test/data/users-guide/address_book.yaml +27 -0
  101. data/test/data/users-guide/answers-schema.yaml +12 -0
  102. data/test/data/users-guide/answers-validator.rb +52 -0
  103. data/test/data/users-guide/babel_genclass.result +26 -0
  104. data/test/data/users-guide/config.schema.yaml +7 -0
  105. data/test/data/users-guide/config.yaml +4 -0
  106. data/test/{tmp.dir/silent1.document → data/users-guide/document01a.yaml} +0 -0
  107. data/test/data/users-guide/document01b.yaml +3 -0
  108. data/test/data/users-guide/document02a.yaml +4 -0
  109. data/test/data/users-guide/document02b.yaml +4 -0
  110. data/test/data/users-guide/document03a.yaml +6 -0
  111. data/test/data/users-guide/document03b.yaml +6 -0
  112. data/test/data/users-guide/document04a.yaml +9 -0
  113. data/test/data/users-guide/document04b.yaml +9 -0
  114. data/test/data/users-guide/document05a.yaml +11 -0
  115. data/test/data/users-guide/document05b.yaml +12 -0
  116. data/test/data/users-guide/document06a.yaml +15 -0
  117. data/test/data/users-guide/document06b.yaml +16 -0
  118. data/test/data/users-guide/document07a.yaml +9 -0
  119. data/test/data/users-guide/document07b.yaml +7 -0
  120. data/test/data/users-guide/document12a.json +10 -0
  121. data/test/data/users-guide/document12b.json +6 -0
  122. data/test/data/users-guide/document13a.yaml +17 -0
  123. data/test/data/users-guide/document14a.yaml +3 -0
  124. data/test/data/users-guide/document14b.yaml +3 -0
  125. data/test/data/users-guide/document15a.yaml +6 -0
  126. data/test/data/users-guide/document15b.yaml +5 -0
  127. data/test/data/users-guide/example_address_book.rb +10 -0
  128. data/test/data/users-guide/example_address_book_java.result +32 -0
  129. data/test/data/users-guide/example_address_book_ruby.result +31 -0
  130. data/test/data/users-guide/genclass_java.result +4 -0
  131. data/test/data/users-guide/howto-validation-with-parsing.rb +28 -0
  132. data/test/data/users-guide/howto-validation.rb +25 -0
  133. data/test/data/users-guide/howto3.rb +6 -0
  134. data/test/data/users-guide/howto3.result +5 -0
  135. data/test/data/users-guide/howto3.yaml +8 -0
  136. data/test/data/users-guide/howto5_databinding.result +111 -0
  137. data/test/data/users-guide/invalid01.result +3 -0
  138. data/test/data/users-guide/invalid02.result +5 -0
  139. data/test/data/users-guide/invalid03.result +5 -0
  140. data/test/data/users-guide/invalid04.result +4 -0
  141. data/test/data/users-guide/invalid05.result +11 -0
  142. data/test/data/users-guide/invalid06.result +4 -0
  143. data/test/data/users-guide/invalid07.result +3 -0
  144. data/test/data/users-guide/invalid08.result +3 -0
  145. data/test/data/users-guide/invalid12.json +8 -0
  146. data/test/data/users-guide/invalid14.result +4 -0
  147. data/test/data/users-guide/invalid15.result +4 -0
  148. data/test/data/users-guide/loadbabel.rb +27 -0
  149. data/test/data/users-guide/loadconfig.rb +15 -0
  150. data/test/data/users-guide/loadconfig.result +2 -0
  151. data/test/data/users-guide/models.rb +22 -0
  152. data/test/data/users-guide/option_ha.result +6 -0
  153. data/test/data/users-guide/option_ha_genclass_java.result +7 -0
  154. data/test/{tmp.dir/meta1.schema → data/users-guide/schema01.yaml} +0 -0
  155. data/test/data/users-guide/schema02.yaml +12 -0
  156. data/test/data/users-guide/schema03.yaml +9 -0
  157. data/test/data/users-guide/schema04.yaml +20 -0
  158. data/test/data/users-guide/schema05.yaml +29 -0
  159. data/test/data/users-guide/schema06.yaml +11 -0
  160. data/test/data/users-guide/schema12.json +12 -0
  161. data/test/data/users-guide/schema13.yaml +13 -0
  162. data/test/data/users-guide/schema14.yaml +5 -0
  163. data/test/data/users-guide/schema15.yaml +21 -0
  164. data/test/data/users-guide/valid01.result +2 -0
  165. data/test/data/users-guide/valid02.result +2 -0
  166. data/test/data/users-guide/valid03.result +2 -0
  167. data/test/data/users-guide/valid04.result +2 -0
  168. data/test/data/users-guide/valid05.result +2 -0
  169. data/test/data/users-guide/valid06.result +2 -0
  170. data/test/data/users-guide/valid07.result +2 -0
  171. data/test/data/users-guide/valid08.result +2 -0
  172. data/test/data/users-guide/valid12.result +2 -0
  173. data/test/data/users-guide/valid13.result +2 -0
  174. data/test/data/users-guide/valid14.result +2 -0
  175. data/test/data/users-guide/valid15.result +2 -0
  176. data/test/data/users-guide/validate08.rb +37 -0
  177. data/test/test-action.rb +78 -0
  178. data/test/test-action.yaml +738 -0
  179. data/test/test-databinding.rb +80 -0
  180. data/test/test-databinding.yaml +301 -0
  181. data/test/test-main.rb +129 -150
  182. data/test/test-main.yaml +126 -321
  183. data/test/test-metavalidator.rb +47 -47
  184. data/test/test-metavalidator.yaml +77 -21
  185. data/test/test-parser-yaml.rb +57 -0
  186. data/test/test-parser-yaml.yaml +1749 -0
  187. data/test/test-rule.rb +14 -15
  188. data/test/test-rule.yaml +6 -3
  189. data/test/test-users-guide.rb +75 -0
  190. data/test/test-validator.rb +77 -52
  191. data/test/test-validator.yaml +168 -6
  192. data/test/test-yaml-parser.rb +47 -0
  193. data/test/{test-yamlparser.yaml → test-yaml-parser.yaml} +159 -52
  194. data/test/test.rb +37 -21
  195. metadata +136 -37
  196. data/COPYING +0 -340
  197. data/ChangeLog +0 -70
  198. data/doc-api/classes/YamlHelper.html +0 -259
  199. data/lib/kwalify/util/yaml-helper.rb +0 -82
  200. data/test/test-yamlparser.rb +0 -58
  201. data/test/tmp.dir/User.java +0 -43
  202. data/test/tmp.dir/action1.document +0 -18
  203. data/test/tmp.dir/action1.schema +0 -32
  204. data/test/tmp.dir/action2.document +0 -18
  205. data/test/tmp.dir/action2.schema +0 -32
  206. data/test/tmp.dir/emacs.document +0 -6
  207. data/test/tmp.dir/emacs.schema +0 -6
  208. data/test/tmp.dir/meta1.document +0 -0
  209. data/test/tmp.dir/meta2.document +0 -0
  210. data/test/tmp.dir/meta2.schema +0 -3
  211. data/test/tmp.dir/silent1.schema +0 -3
  212. data/test/tmp.dir/silent2.document +0 -7
  213. data/test/tmp.dir/silent2.schema +0 -3
  214. data/test/tmp.dir/stream.invalid +0 -8
  215. data/test/tmp.dir/stream.schema +0 -3
  216. data/test/tmp.dir/stream.valid +0 -8
  217. data/test/tmp.dir/untabify.document +0 -5
  218. data/test/tmp.dir/untabify.schema +0 -10
  219. data/todo.txt +0 -34
@@ -0,0 +1,57 @@
1
+ ###
2
+ ### $Rev: 84 $
3
+ ### 0.7.0
4
+ ### $COPYRIGHT$
5
+ ###
6
+
7
+ module Kwalify
8
+
9
+ module Util
10
+
11
+ class OrderedHash < Hash
12
+
13
+ def initialize(*args, &block)
14
+ super
15
+ @_keys = []
16
+ end
17
+
18
+ alias __set__ []=
19
+
20
+ def put(key, val)
21
+ @_keys << key unless self.key?(key)
22
+ __set__(key, val)
23
+ end
24
+
25
+ def add(key, val)
26
+ @_keys.delete_at(@_keys.index(key)) if self.key?(key)
27
+ @_keys << key
28
+ __set__(key, val)
29
+ end
30
+
31
+ alias []= put
32
+ #alias []= add
33
+
34
+ def keys
35
+ return @_keys.dup
36
+ end
37
+
38
+ def values
39
+ return @_keys.collect {|key| self[key] }
40
+ end
41
+
42
+ def delete(key)
43
+ @_keys.delete_at(@_keys.index(key)) if self.key?(key)
44
+ super
45
+ end
46
+
47
+ def each
48
+ @_keys.each do |key|
49
+ yield key, self[key]
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ end
@@ -1,7 +1,7 @@
1
1
  ###
2
- ### $Rev: 51 $
3
- ### $Release: 0.6.1 $
4
- ### copyright(c) 2005 kuwata-lab all rights reserved.
2
+ ### $Rev: 83 $
3
+ ### $Release: 0.7.0 $
4
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
5
5
  ###
6
6
 
7
7
  require 'yaml'
@@ -1,7 +1,7 @@
1
1
  ###
2
- ### $Rev: 48 $
3
- ### $Release: 0.6.1 $
4
- ### copyright(c) 2005 kuwata-lab all rights reserved.
2
+ ### $Rev: 89 $
3
+ ### $Release: 0.7.0 $
4
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
5
5
  ###
6
6
 
7
7
  require 'kwalify/messages'
@@ -11,214 +11,269 @@ require 'kwalify/rule'
11
11
 
12
12
  module Kwalify
13
13
 
14
- ##
15
- ## ex.
16
- ## schema = YAML.load_file('schema.yaml')
17
- ## validator = Kwalify::Validator.new(schema)
18
- ## document = YAML.load_file('document.yaml')
19
- ## error_list = validator.validate(document)
20
- ## unless error_list.empty?
21
- ## error_list.each do |error|
22
- ## puts "- [#{error.path}] #{error.message}"
23
- ## end
24
- ## end
25
- ##
26
- class Validator
27
- include Kwalify::ErrorHelper
28
-
29
- def initialize(hash, &block)
30
- @rule = Rule.new(hash)
31
- @block = block
32
- end
33
- attr_reader :rule
34
-
35
-
36
- def _inspect
37
- @rule._inspect
38
- end
39
-
40
-
41
- def validate(value)
42
- path = ""; errors = []; done = {}
43
- _validate(value, @rule, path, errors, done)
44
- return errors
45
- end
46
-
47
-
48
- protected
49
-
50
-
51
- def validate_hook(value, rule, path, errors)
52
- end
53
-
54
-
55
- def _validate(value, rule, path, errors, done)
56
- if Types.collection?(value)
57
- return if done[value.__id__] # avoid infinite loop
58
- done[value.__id__] = true
59
- end
60
- if rule.required && value == nil
61
- #* key=:required_novalue msg="value required but none."
62
- errors << validate_error(:required_novalue, rule, path, value)
63
- return
64
- end
65
- if rule.type_class && value != nil && !value.is_a?(rule.type_class)
66
- #* key=:type_unmatch msg="not a %s."
67
- errors << validate_error(:type_unmatch, rule, path, value, [Kwalify.word(rule.type)])
68
- return
69
- end
70
- #
71
- n = errors.length
72
- if rule.sequence
73
- _validate_sequence(value, rule, path, errors, done)
74
- elsif rule.mapping
75
- _validate_mapping(value, rule, path, errors, done)
76
- else
77
- _validate_scalar(value, rule, path, errors, done)
78
- end
79
- return unless errors.length == n
80
- #
81
- validate_hook(value, rule, path, errors)
82
- @block.call(value, rule, path, errors) if @block
83
- end
84
-
85
-
86
- private
87
-
88
-
89
- def _validate_scalar(value, rule, path, errors, done)
90
- assert_error("rule.sequence.class==#{rule.sequence.class.name} (expected NilClass)") if rule.sequence
91
- assert_error("rule.mapping.class==#{rule.mapping.class.name} (expected NilClass)") if rule.mapping
92
- if rule.assert_proc
93
- unless rule.assert_proc.call(value)
94
- #* key=:assert_failed msg="assertion expression failed (%s)."
95
- errors << validate_error(:assert_failed, rule, path, value, [rule.assert])
96
- end
97
- end
98
- if rule.enum
99
- unless rule.enum.include?(value)
100
- keyname = File.basename(path)
101
- keyname = 'enum' if keyname =~ /\A\d+\z/
102
- #* key=:enum_notexist msg="invalid %s value."
103
- errors << validate_error(:enum_notexist, rule, path, value, [keyname])
104
- end
105
- end
106
- #
107
- return if value == nil
108
- #
109
- if rule.pattern
110
- unless value.to_s =~ rule.regexp
111
- #* key=:pattern_unmatch msg="not matched to pattern %s."
112
- errors << validate_error(:pattern_unmatch, rule, path, value, [rule.pattern])
113
- end
114
- end
115
- if rule.range
116
- assert_error("value.class=#{value.class.name}") unless Types.scalar?(value)
117
- if rule.range['max'] && rule.range['max'] < value
118
- #* key=:range_toolarge msg="too large (> max %s)."
119
- errors << validate_error(:range_toolarge, rule, path, value, [rule.range['max'].to_s])
120
- end
121
- if rule.range['min'] && rule.range['min'] > value
122
- #* key=:range_toosmall msg="too small (< min %s)."
123
- errors << validate_error(:range_toosmall, rule, path, value, [rule.range['min'].to_s])
124
- end
125
- if rule.range['max-ex'] && rule.range['max-ex'] <= value
126
- #* key=:range_toolargeex msg="too large (>= max %s)."
127
- errors << validate_error(:range_toolargeex, rule, path, value, [rule.range['max-ex'].to_s])
128
- end
129
- if rule.range['min-ex'] && rule.range['min-ex'] >= value
130
- #* key=:range_toosmallex msg="too small (<= min %s)."
131
- errors << validate_error(:range_toosmallex, rule, path, value, [rule.range['min-ex'].to_s])
132
- end
133
- end
134
- if rule.length
135
- assert_error("value.class=#{value.class.name}") unless value.is_a?(String) || value.is_a?(Text)
136
- len = value.to_s.length
137
- if rule.length['max'] && rule.length['max'] < len
138
- #* key=:length_toolong msg="too long (length %d > max %d)."
139
- errors << validate_error(:length_toolong, rule, path, value, [len, rule.length['max']])
140
- end
141
- if rule.length['min'] && rule.length['min'] > len
142
- #* key=:length_tooshort msg="too short (length %d < min %d)."
143
- errors << validate_error(:length_tooshort, rule, path, value, [len, rule.length['min']])
144
- end
145
- if rule.length['max-ex'] && rule.length['max-ex'] <= len
146
- #* key=:length_toolongex msg="too long (length %d >= max %d)."
147
- errors << validate_error(:length_toolongex, rule, path, value, [len, rule.length['max-ex']])
148
- end
149
- if rule.length['min-ex'] && rule.length['min-ex'] >= len
150
- #* key=:length_tooshortex msg="too short (length %d <= min %d)."
151
- errors << validate_error(:length_tooshortex, rule, path, value, [len, rule.length['min-ex']])
152
- end
153
- end
154
- end
155
-
156
-
157
- def _validate_sequence(list, seq_rule, path, errors, done)
158
- assert_error("seq_rule.sequence.class==#{seq_rule.sequence.class.name} (expected Array)") unless seq_rule.sequence.is_a?(Array)
159
- assert_error("seq_rule.sequence.length==#{seq_rule.sequence.length} (expected 1)") unless seq_rule.sequence.length == 1
160
- return if list == nil
161
- rule = seq_rule.sequence[0]
162
- list.each_with_index do |val, i|
163
- _validate(val, rule, "#{path}/#{i}", errors, done) ## validate recursively
164
- end
165
- if rule.type == 'map'
166
- unique_keys = []
167
- rule.mapping.keys.each do |key|
168
- map_rule = rule.mapping[key]
169
- unique_keys << key if map_rule.unique || map_rule.ident
170
- end
171
- unique_keys.each do |key|
172
- table = {}
173
- list.each_with_index do |map, i|
174
- val = map[key]
175
- next if val == nil
176
- curr_path = "#{path}/#{i}/#{key}"
177
- if table[val]
178
- #* key=:value_notunique msg="is already used at '%s'."
179
- errors << validate_error(:value_notunique, rule, "#{path}/#{i}/#{key}", val, "#{path}/#{table[val]}/#{key}")
180
- else
181
- table[val] = i
182
- end
183
- end
184
- end if !unique_keys.empty?
185
- elsif rule.unique
186
- table = {}
187
- list.each_with_index do |val, i|
188
- next if val == nil
189
- if table[val]
190
- # #* key=:value_notunique msg="is already used at '%s'."
191
- errors << validate_error(:value_notunique, rule, "#{path}/#{i}", val, "#{path}/#{table[val]}")
192
- else
193
- table[val] = i
194
- end
195
- end
196
- end
197
- end
198
-
199
-
200
- def _validate_mapping(hash, map_rule, path, errors, done)
201
- assert_error("map_rule.mapping.class==#{map_rule.mapping.class.name} (expected Hash)") unless map_rule.mapping.is_a?(Hash)
202
- return if hash == nil
203
- map_rule.mapping.each do |key, rule|
204
- if rule.required && !hash.key?(key)
205
- #* key=:required_nokey msg="key '%s:' is required."
206
- errors << validate_error(:required_nokey, rule, path, hash, [key])
207
- end
208
- end
209
- hash.each do |key, val|
210
- rule = map_rule.mapping[key]
211
- unless rule
212
- #* key=:key_undefined msg="key '%s' is undefined."
213
- errors << validate_error(:key_undefined, rule, "#{path}/#{key}", hash, ["#{key}:"])
214
- ##* key=:key_undefined msg="undefined key."
215
- #errors << validate_error(:key_undefined, rule, "#{path}/#{key}", "#{key}:")
216
- else
217
- _validate(val, rule, "#{path}/#{key}", errors, done) ## validate recursively
218
- end
219
- end
220
- end
221
-
222
- end
14
+ ##
15
+ ## validate YAML document
16
+ ##
17
+ ## ex1. validate yaml document
18
+ ## schema = YAML.load_file('schema.yaml')
19
+ ## validator = Kwalify::Validator.new(schema)
20
+ ## document = YAML.load_file('document.yaml')
21
+ ## erros = validator.validate(document)
22
+ ## if errors && !errors.empty?
23
+ ## errors.each do |err|
24
+ ## puts "- [#{err.path}] #{err.message}"
25
+ ## end
26
+ ## end
27
+ ##
28
+ ## ex2. validate with parsing
29
+ ## schema = YAML.load_file('schema.yaml')
30
+ ## validator = Kwalify::Validator.new(schema)
31
+ ## parser = Kwalify::Yaml::Parser.new(validator)
32
+ ## document = parser.parse(File.read('document.yaml'))
33
+ ## errors = parser.errors
34
+ ## if errors && errors.empty?
35
+ ## errors.each do |e|
36
+ ## puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}"
37
+ ## end
38
+ ## end
39
+ ##
40
+ class Validator
41
+ include Kwalify::ErrorHelper
42
+
43
+
44
+ def initialize(hash_or_rule, &block)
45
+ obj = hash_or_rule
46
+ @rule = (obj.nil? || obj.is_a?(Rule)) ? obj : Rule.new(obj)
47
+ @block = block
48
+ end
49
+ attr_reader :rule
50
+
51
+
52
+ def _inspect
53
+ @rule._inspect
54
+ end
55
+
56
+
57
+ def validate(value)
58
+ path = ''; errors = []; done = {}; uniq_table = nil
59
+ _validate(value, @rule, path, errors, done, uniq_table)
60
+ return errors
61
+ end
62
+
63
+
64
+ protected
65
+
66
+
67
+ def validate_hook(value, rule, path, errors)
68
+ ## may be overrided by subclass
69
+ end
70
+
71
+
72
+ public
73
+
74
+
75
+ def _validate(value, rule, path, errors, done, uniq_table, recursive=true)
76
+ #if Types.collection?(value)
77
+ if !Types.scalar?(value)
78
+ #if done[value.__id__]
79
+ # rule2 = done[value.__id__]
80
+ # if rule2.is_a?(Rule)
81
+ # return if rule.equal?(rule2)
82
+ # done[value.__id__] = [rule2, rule]
83
+ # elsif rule2.is_a?(Array)
84
+ # return if rule2.any? {|r| r.equal?(rule)}
85
+ # done[value.__id__] << rule
86
+ # else
87
+ # raise "unreachable"
88
+ # end
89
+ #end
90
+ return if done[value.__id__] # avoid infinite loop
91
+ done[value.__id__] = rule
92
+ end
93
+ if rule.required && value.nil?
94
+ #* key=:required_novalue msg="value required but none."
95
+ errors << validate_error(:required_novalue, rule, path, value)
96
+ return
97
+ end
98
+ if rule.type_class && !value.nil? && !value.is_a?(rule.type_class)
99
+ unless rule.classobj && value.is_a?(rule.classobj)
100
+ #* key=:type_unmatch msg="not a %s."
101
+ errors << validate_error(:type_unmatch, rule, path, value, [Kwalify.word(rule.type)])
102
+ return
103
+ end
104
+ end
105
+ #
106
+ n = errors.length
107
+ if rule.sequence
108
+ _validate_sequence(value, rule, path, errors, done, uniq_table, recursive)
109
+ elsif rule.mapping
110
+ _validate_mapping(value, rule, path, errors, done, uniq_table, recursive)
111
+ else
112
+ _validate_scalar(value, rule, path, errors, done, uniq_table)
113
+ end
114
+ return unless errors.length == n
115
+ #
116
+ validate_hook(value, rule, path, errors)
117
+ @block.call(value, rule, path, errors) if @block
118
+ end
119
+
120
+
121
+ private
122
+
123
+
124
+ def _validate_sequence(list, seq_rule, path, errors, done, uniq_table, recursive=true)
125
+ assert_error("seq_rule.sequence.class==#{seq_rule.sequence.class.name} (expected Array)") unless seq_rule.sequence.is_a?(Array)
126
+ assert_error("seq_rule.sequence.length==#{seq_rule.sequence.length} (expected 1)") unless seq_rule.sequence.length == 1
127
+ return if list.nil? || !recursive
128
+ rule = seq_rule.sequence[0]
129
+ uniq_table = rule._uniqueness_check_table()
130
+ list.each_with_index do |val, i|
131
+ child_path = path.is_a?(Array) ? path + [i] : "#{path}/#{i}"
132
+ _validate(val, rule, child_path, errors, done, uniq_table) ## validate recursively
133
+ end
134
+ end
135
+
136
+
137
+ def _validate_mapping(hash, map_rule, path, errors, done, uniq_table, recursive=true)
138
+ assert_error("map_rule.mapping.class==#{map_rule.mapping.class.name} (expected Hash)") unless map_rule.mapping.is_a?(Hash)
139
+ return if hash.nil?
140
+ return if !recursive
141
+ _validate_mapping_required_keys(hash, map_rule, path, errors)
142
+ hash.each do |key, val|
143
+ rule = map_rule.mapping[key]
144
+ child_path = path.is_a?(Array) ? path+[key] : "#{path}/#{key}"
145
+ unless rule
146
+ #* key=:key_undefined msg="key '%s' is undefined."
147
+ errors << validate_error(:key_undefined, rule, child_path, hash, ["#{key}:"])
148
+ ##* key=:key_undefined msg="undefined key."
149
+ #errors << validate_error(:key_undefined, rule, child_path, "#{key}:")
150
+ else
151
+ _validate(val, rule, child_path, errors, done,
152
+ uniq_table ? uniq_table[key] : nil) ## validate recursively
153
+ end
154
+ end
155
+ end
156
+
157
+
158
+ def _validate_mapping_required_keys(hash, map_rule, path, errors) #:nodoc:
159
+ map_rule.mapping.each do |key, rule|
160
+ #next unless rule.required
161
+ #val = hash.is_a?(Hash) ? hash[key] : hash.instance_variable_get("@#{key}")
162
+ #if val.nil?
163
+ if rule.required && hash[key].nil? # or !hash.key?(key)
164
+ #* key=:required_nokey msg="key '%s:' is required."
165
+ errors << validate_error(:required_nokey, rule, path, hash, [key])
166
+ end
167
+ end
168
+ end
169
+ public :_validate_mapping_required_keys
170
+
171
+
172
+ def _validate_scalar(value, rule, path, errors, done, uniq_table)
173
+ assert_error("rule.sequence.class==#{rule.sequence.class.name} (expected NilClass)") if rule.sequence
174
+ assert_error("rule.mapping.class==#{rule.mapping.class.name} (expected NilClass)") if rule.mapping
175
+ _validate_assert( value, rule, path, errors) if rule.assert_proc
176
+ _validate_enum( value, rule, path, errors) if rule.enum
177
+ return if value.nil?
178
+ _validate_pattern(value, rule, path, errors) if rule.pattern
179
+ _validate_range( value, rule, path, errors) if rule.range
180
+ _validate_length( value, rule, path, errors) if rule.length
181
+ _validate_unique( value, rule, path, errors, uniq_table) if uniq_table
182
+ end
183
+
184
+
185
+ def _validate_unique(value, rule, path, errors, uniq_table)
186
+ assert_error "uniq_table=#{uniq_table.inspect}" unless rule.unique || rule.ident
187
+ if uniq_table.key?(value)
188
+ exist_at = uniq_table[value]
189
+ exist_at = "/#{exist_at.join('/')}" if exist_at.is_a?(Array)
190
+ #* key=:value_notunique msg="is already used at '%s'."
191
+ errors << validate_error(:value_notunique, rule, path, value, exist_at)
192
+ else
193
+ uniq_table[value] = path.dup
194
+ end
195
+ end
196
+ public :_validate_unique
197
+
198
+
199
+ def _validate_assert(value, rule, path, errors)
200
+ assert_error("rule=#{rule._inspect}") unless rule.assert_proc
201
+ unless rule.assert_proc.call(value)
202
+ #* key=:assert_failed msg="assertion expression failed (%s)."
203
+ errors << validate_error(:assert_failed, rule, path, value, [rule.assert])
204
+ end
205
+ end
206
+
207
+
208
+ def _validate_enum(value, rule, path, errors)
209
+ assert_error("rule=#{rule._inspect}") unless rule.enum
210
+ unless rule.enum.include?(value)
211
+ keyname = path.is_a?(Array) ? path[-1] : File.basename(path)
212
+ keyname = 'enum' if keyname =~ /\A\d+\z/
213
+ #* key=:enum_notexist msg="invalid %s value."
214
+ errors << validate_error(:enum_notexist, rule, path, value, [keyname])
215
+ end
216
+ end
217
+
218
+
219
+ def _validate_pattern(value, rule, path, errors)
220
+ assert_error("rule=#{rule._inspect}") unless rule.pattern
221
+ unless value.to_s =~ rule.regexp
222
+ #* key=:pattern_unmatch msg="not matched to pattern %s."
223
+ errors << validate_error(:pattern_unmatch, rule, path, value, [rule.pattern])
224
+ end
225
+ end
226
+
227
+
228
+ def _validate_range(value, rule, path, errors)
229
+ assert_error("rule=#{rule._inspect}") unless rule.range
230
+ assert_error("value.class=#{value.class.name}") unless Types.scalar?(value)
231
+ h = rule.range
232
+ max, min, max_ex, min_ex = h['max'], h['min'], h['max-ex'], h['min-ex']
233
+ if max && max < value
234
+ #* key=:range_toolarge msg="too large (> max %s)."
235
+ errors << validate_error(:range_toolarge, rule, path, value, [max.to_s])
236
+ end
237
+ if min && min > value
238
+ #* key=:range_toosmall msg="too small (< min %s)."
239
+ errors << validate_error(:range_toosmall, rule, path, value, [min.to_s])
240
+ end
241
+ if max_ex && max_ex <= value
242
+ #* key=:range_toolargeex msg="too large (>= max %s)."
243
+ errors << validate_error(:range_toolargeex, rule, path, value, [max_ex.to_s])
244
+ end
245
+ if min_ex && min_ex >= value
246
+ #* key=:range_toosmallex msg="too small (<= min %s)."
247
+ errors << validate_error(:range_toosmallex, rule, path, value, [min_ex.to_s])
248
+ end
249
+ end
250
+
251
+
252
+ def _validate_length(value, rule, path, errors)
253
+ assert_error("rule=#{rule._inspect}") unless rule.length
254
+ assert_error("value.class=#{value.class.name}") unless value.is_a?(String) || value.is_a?(Text)
255
+ len = value.to_s.length
256
+ h = rule.length
257
+ max, min, max_ex, min_ex = h['max'], h['min'], h['max-ex'], h['min-ex']
258
+ if max && max < len
259
+ #* key=:length_toolong msg="too long (length %d > max %d)."
260
+ errors << validate_error(:length_toolong, rule, path, value, [len, max])
261
+ end
262
+ if min && min > len
263
+ #* key=:length_tooshort msg="too short (length %d < min %d)."
264
+ errors << validate_error(:length_tooshort, rule, path, value, [len, min])
265
+ end
266
+ if max_ex && max_ex <= len
267
+ #* key=:length_toolongex msg="too long (length %d >= max %d)."
268
+ errors << validate_error(:length_toolongex, rule, path, value, [len, max_ex])
269
+ end
270
+ if min_ex && min_ex >= len
271
+ #* key=:length_tooshortex msg="too short (length %d <= min %d)."
272
+ errors << validate_error(:length_tooshortex, rule, path, value, [len, min_ex])
273
+ end
274
+ end
275
+
276
+
277
+ end
223
278
 
224
279
  end