jcrvalidator 0.5.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.
@@ -0,0 +1,115 @@
1
+ # Copyright (c) 2015 American Registry for Internet Numbers
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
13
+ # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'jcr/parser'
16
+ require 'jcr/map_rule_names'
17
+ require 'jcr/check_groups'
18
+ require 'jcr/evaluate_rules'
19
+
20
+ module JCR
21
+
22
+ class ObjectBehavior
23
+ attr_accessor :checked_hash
24
+
25
+ def initialize
26
+ @checked_hash = {}
27
+ end
28
+ end
29
+
30
+ def self.evaluate_object_rule jcr, rule_atom, data, econs, behavior = nil
31
+
32
+ rules, annotations = get_rules_and_annotations( jcr )
33
+
34
+ # if the data is not an object (Hash)
35
+ return evaluate_reject( annotations,
36
+ Evaluation.new( false, "#{data} is not an object at #{jcr} from #{rule_atom}") ) unless data.is_a? Hash
37
+
38
+ # if the object has no members and there are zero sub-rules (it is suppose to be empty)
39
+ return evaluate_reject( annotations,
40
+ Evaluation.new( true, nil ) ) if rules.empty? && data.length == 0
41
+
42
+ # if the object has members and there are zero sub-rules (it is suppose to be empty)
43
+ return evaluate_reject( annotations,
44
+ Evaluation.new( false, "Non-empty object at #{jcr} from #{rule_atom}" ) ) if rules.empty? && data.length != 0
45
+
46
+ retval = nil
47
+ behavior = ObjectBehavior.new unless behavior
48
+
49
+ rules.each do |rule|
50
+
51
+ # short circuit logic
52
+ if rule[:choice_combiner] && retval && retval.success
53
+ next
54
+ elsif rule[:sequence_combiner] && retval && !retval.success
55
+ return evaluate_reject( annotations, retval ) # short circuit
56
+ end
57
+
58
+ repeat_min, repeat_max = get_repetitions( rule )
59
+
60
+ # Pay attention here:
61
+ # Group rules need to be treated differently than other rules
62
+ # Groups must be evaluated as if they are rules evaluated in
63
+ # isolation until they evaluate as true.
64
+ # Also, groups must be handed the entire object, not key/values
65
+ # as member rules use.
66
+
67
+ if (grule = get_group(rule, econs))
68
+
69
+ successes = 0
70
+ for i in 0..repeat_max
71
+ group_behavior = ObjectBehavior.new
72
+ e = evaluate_rule( grule, rule_atom, data, econs, group_behavior )
73
+ if e.success
74
+ behavior.checked_hash.merge!( group_behavior.checked_hash )
75
+ successes = successes + 1
76
+ else
77
+ break;
78
+ end
79
+ end
80
+
81
+ if successes == 0 && repeat_min > 0
82
+ retval = Evaluation.new( false, "object does not contain group #{rule} for #{jcr} from #{rule_atom}")
83
+ elsif successes < repeat_min
84
+ retval = Evaluation.new( false, "object does not have contain necessary number of group #{rule} for #{jcr} from #{rule_atom}")
85
+ else
86
+ retval = Evaluation.new( true, nil )
87
+ end
88
+
89
+ else # if not grule
90
+
91
+ repeat_results = data.select do |k,v|
92
+ unless behavior.checked_hash[k]
93
+ e = evaluate_rule(rule, rule_atom, [k, v], econs, nil)
94
+ behavior.checked_hash[k] = e.success
95
+ e.success
96
+ end
97
+ end
98
+
99
+ if repeat_results.length == 0 && repeat_min > 0
100
+ retval = Evaluation.new( false, "object does not contain #{rule} for #{jcr} from #{rule_atom}")
101
+ elsif repeat_results.length < repeat_min
102
+ retval = Evaluation.new( false, "object does not have enough #{rule} for #{jcr} from #{rule_atom}")
103
+ elsif repeat_results.length > repeat_max
104
+ retval = Evaluation.new( false, "object has too many #{rule} for #{jcr} from #{rule_atom}")
105
+ else
106
+ retval = Evaluation.new( true, nil)
107
+ end
108
+ end
109
+
110
+ end # end if grule else
111
+
112
+ return evaluate_reject( annotations, retval )
113
+ end
114
+
115
+ end
@@ -0,0 +1,211 @@
1
+ # Copyright (c) 2015 American Registry for Internet Numbers
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
13
+ # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'ipaddr'
16
+ require 'time'
17
+ require 'addressable/uri'
18
+ require 'addressable/template'
19
+ require 'email_address_validator'
20
+ require 'big-phoney'
21
+
22
+ require 'jcr/parser'
23
+ require 'jcr/map_rule_names'
24
+ require 'jcr/check_groups'
25
+ require 'jcr/evaluate_array_rules'
26
+ require 'jcr/evaluate_object_rules'
27
+ require 'jcr/evaluate_group_rules'
28
+ require 'jcr/evaluate_member_rules'
29
+ require 'jcr/evaluate_value_rules'
30
+
31
+
32
+ # Adapted from Matt Sears
33
+ class Proc
34
+ def jcr_callback(callable, *args)
35
+ self === Class.new do
36
+ method_name = callable.to_sym
37
+ define_method(method_name) { |&block| block.nil? ? true : block.call(*args) }
38
+ define_method("#{method_name}?") { true }
39
+ def method_missing(method_name, *args, &block) false; end
40
+ end.new
41
+ end
42
+ end
43
+
44
+ module JCR
45
+
46
+ class Evaluation
47
+ attr_accessor :success, :reason, :child_evaluation
48
+ def initialize success, reason
49
+ @success = success
50
+ @reason = reason
51
+ end
52
+ end
53
+
54
+ class EvalConditions
55
+ attr_accessor :mapping, :callbacks
56
+ def initialize mapping, callbacks
57
+ @mapping = mapping
58
+ if callbacks
59
+ @callbacks = callbacks
60
+ else
61
+ @callbacks = {}
62
+ end
63
+ end
64
+ end
65
+
66
+ def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil
67
+ retval = Evaluation.new( false, "failed to evaluate rule properly" )
68
+ case
69
+ when behavior.is_a?( ArrayBehavior )
70
+ retval = evaluate_array_rule( jcr, rule_atom, data, econs, behavior)
71
+ when behavior.is_a?( ObjectBehavior )
72
+ retval = evaluate_object_rule( jcr, rule_atom, data, econs, behavior)
73
+ when jcr[:rule]
74
+ retval = evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior)
75
+ when jcr[:target_rule_name]
76
+ target = econs.mapping[ jcr[:target_rule_name][:rule_name].to_s ]
77
+ raise "Target rule not in mapping. This should have been checked earlier." unless target
78
+ retval = evaluate_rule( target, target, data, econs, behavior )
79
+ when jcr[:primitive_rule]
80
+ retval = evaluate_value_rule( jcr[:primitive_rule], rule_atom, data, econs)
81
+ when jcr[:group_rule]
82
+ retval = evaluate_group_rule( jcr[:group_rule], rule_atom, data, econs, behavior)
83
+ when jcr[:array_rule]
84
+ retval = evaluate_array_rule( jcr[:array_rule], rule_atom, data, econs, behavior)
85
+ when jcr[:object_rule]
86
+ retval = evaluate_object_rule( jcr[:object_rule], rule_atom, data, econs, behavior)
87
+ when jcr[:member_rule]
88
+ retval = evaluate_member_rule( jcr[:member_rule], rule_atom, data, econs)
89
+ else
90
+ retval = Evaluation.new( true, nil )
91
+ end
92
+ if jcr.is_a?( Hash ) && jcr[:rule_name]
93
+ rn = jcr[:rule_name].to_s
94
+ if econs.callbacks[ rn ]
95
+ retval = evaluate_callback( jcr, data, econs, rn, retval )
96
+ end
97
+ end
98
+ return retval
99
+ end
100
+
101
+ def self.evaluate_callback jcr, data, econs, callback, e
102
+ retval = e
103
+ c = econs.callbacks[ callback ]
104
+ if e.success
105
+ retval = c.jcr_callback :rule_eval_true, jcr, data
106
+ else
107
+ retval = c.jcr_callback :rule_eval_false, jcr, data, e
108
+ end
109
+ if retval.is_a? TrueClass
110
+ retval = Evaluation.new( true, nil )
111
+ elsif retval.is_a? FalseClass
112
+ retval = Evaluation.new( false, nil )
113
+ elsif retval.is_a? String
114
+ retval = Evaluation.new( false, retval )
115
+ end
116
+ return retval
117
+ end
118
+
119
+ def self.get_repetitions rule
120
+
121
+ repeat_min = 1
122
+ repeat_max = 1
123
+ if rule[:optional]
124
+ repeat_min = 0
125
+ repeat_max = 1
126
+ elsif rule[:one_or_more]
127
+ repeat_min = 1
128
+ repeat_max = Float::INFINITY
129
+ elsif rule[:specific_repetition] && rule[:specific_repetition].is_a?( Parslet::Slice )
130
+ repeat_min = repeat_max = rule[:specific_repetition].to_s.to_i
131
+ else
132
+ o = rule[:repetition_interval]
133
+ if o
134
+ repeat_min = 0
135
+ repeat_max = Float::INFINITY
136
+ end
137
+ o = rule[:repetition_min]
138
+ if o
139
+ if o.is_a?( Parslet::Slice )
140
+ repeat_min = o.to_s.to_i
141
+ end
142
+ end
143
+ o = rule[:repetition_max]
144
+ if o
145
+ if o.is_a?( Parslet::Slice )
146
+ repeat_max = o.to_s.to_i
147
+ end
148
+ end
149
+ end
150
+
151
+ return repeat_min, repeat_max
152
+ end
153
+
154
+ def self.get_rules_and_annotations jcr
155
+ rules = []
156
+ annotations = []
157
+
158
+ if jcr.is_a?( Hash )
159
+ jcr = [ jcr ]
160
+ end
161
+
162
+ if jcr.is_a? Array
163
+ i = 0
164
+ jcr.each do |sub|
165
+ case
166
+ when sub[:unordered_annotation]
167
+ annotations << sub
168
+ i = i + 1
169
+ when sub[:reject_annotation]
170
+ annotations << sub
171
+ i = i + 1
172
+ when sub[:root_annotation]
173
+ annotations << sub
174
+ i = i + 1
175
+ when sub[:primitive_rule],sub[:object_rule],sub[:group_rule],sub[:array_rule],sub[:target_rule_name]
176
+ break
177
+ end
178
+ end
179
+ rules = jcr[i,jcr.length]
180
+ end
181
+
182
+ return rules, annotations
183
+ end
184
+
185
+ def self.evaluate_reject annotations, evaluation
186
+ reject = false
187
+ annotations.each do |a|
188
+ if a[:reject_annotation]
189
+ reject = true
190
+ break
191
+ end
192
+ end
193
+
194
+ if reject
195
+ evaluation.success = !evaluation.success
196
+ end
197
+ return evaluation
198
+ end
199
+
200
+ def self.get_group rule, econs
201
+ return rule[:group_rule] if rule[:group_rule]
202
+ #else
203
+ if rule[:target_rule_name]
204
+ target = econs.mapping[ rule[:target_rule_name][:rule_name].to_s ]
205
+ raise "Target rule not in mapping. This should have been checked earlier." unless target
206
+ return get_group( target, econs )
207
+ end
208
+ #else
209
+ return false
210
+ end
211
+ end
@@ -0,0 +1,279 @@
1
+ # Copyright (c) 2015 American Registry for Internet Numbers
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
13
+ # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'ipaddr'
16
+ require 'time'
17
+ require 'addressable/uri'
18
+ require 'addressable/template'
19
+ require 'email_address_validator'
20
+ require 'big-phoney'
21
+
22
+ require 'jcr/parser'
23
+ require 'jcr/map_rule_names'
24
+ require 'jcr/check_groups'
25
+
26
+ module JCR
27
+
28
+ def self.evaluate_value_rule jcr, rule_atom, data, mapping
29
+ rules, annotations = get_rules_and_annotations( jcr )
30
+
31
+ return evaluate_reject( annotations, evaluate_values( rules[0], rule_atom, data, mapping ) )
32
+ end
33
+
34
+ def self.evaluate_values jcr, rule_atom, data, mapping
35
+ case
36
+
37
+ #
38
+ # any
39
+ #
40
+
41
+ when jcr[:any]
42
+ return Evaluation.new( true, nil )
43
+
44
+ #
45
+ # integers
46
+ #
47
+
48
+ when jcr[:integer_v]
49
+ si = jcr[:integer_v].to_s
50
+ if si == "integer"
51
+ return bad_value( jcr, rule_atom, "integer", data ) unless data.is_a?( Fixnum )
52
+ end
53
+ when jcr[:integer]
54
+ i = jcr[:integer].to_s.to_i
55
+ return bad_value( jcr, rule_atom, i, data ) unless data == i
56
+ when jcr[:integer_min],jcr[:integer_max]
57
+ return bad_value( jcr, rule_atom, "integer", data ) unless data.is_a?( Fixnum )
58
+ min = jcr[:integer_min].to_s.to_i
59
+ return bad_value( jcr, rule_atom, min, data ) unless data >= min
60
+ max = jcr[:integer_max].to_s.to_i
61
+ return bad_value( jcr, rule_atom, max, data ) unless data <= max
62
+
63
+ #
64
+ # floats
65
+ #
66
+
67
+ when jcr[:float_v]
68
+ sf = jcr[:float_v].to_s
69
+ if sf == "float"
70
+ return bad_value( jcr, rule_atom, "float", data ) unless data.is_a?( Float )
71
+ end
72
+ when jcr[:float]
73
+ f = jcr[:float].to_s.to_f
74
+ return bad_value( jcr, rule_atom, f, data ) unless data == f
75
+ when jcr[:float_min],jcr[:float_max]
76
+ return bad_value( jcr, rule_atom, "float", data ) unless data.is_a?( Float )
77
+ min = jcr[:float_min].to_s.to_f
78
+ return bad_value( jcr, rule_atom, min, data ) unless data >= min
79
+ max = jcr[:float_max].to_s.to_f
80
+ return bad_value( jcr, rule_atom, max, data ) unless data <= max
81
+
82
+ #
83
+ # boolean
84
+ #
85
+
86
+ when jcr[:true_v]
87
+ return bad_value( jcr, rule_atom, "true", data ) unless data
88
+ when jcr[:false_v]
89
+ return bad_value( jcr, rule_atom, "false", data ) if data
90
+ when jcr[:boolean_v]
91
+ return bad_value( jcr, rule_atom, "boolean", data ) unless ( data.is_a?( TrueClass ) || data.is_a?( FalseClass ) )
92
+
93
+ #
94
+ # strings
95
+ #
96
+
97
+ when jcr[:string]
98
+ return bad_value( jcr, rule_atom, "string", data ) unless data.is_a? String
99
+ when jcr[:q_string]
100
+ s = jcr[:q_string].to_s
101
+ return bad_value( jcr, rule_atom, s, data ) unless data == s
102
+
103
+ #
104
+ # regex
105
+ #
106
+
107
+ when jcr[:regex]
108
+ regex = Regexp.new( jcr[:regex].to_s )
109
+ return bad_value( jcr, rule_atom, regex, data ) unless data.is_a? String
110
+ return bad_value( jcr, rule_atom, regex, data ) unless data =~ regex
111
+
112
+ #
113
+ # ip addresses
114
+ #
115
+
116
+ when jcr[:ip4]
117
+ return bad_value( jcr, rule_atom, "IPv4 Address", data ) unless data.is_a? String
118
+ begin
119
+ ip = IPAddr.new( data )
120
+ rescue IPAddr::InvalidAddressError
121
+ return bad_value( jcr, rule_atom, "IPv4 Address", data )
122
+ end
123
+ return bad_value( jcr, rule_atom, "IPv4 Address", data ) unless ip.ipv4?
124
+ when jcr[:ip6]
125
+ return bad_value( jcr, rule_atom, "IPv6 Address", data ) unless data.is_a? String
126
+ begin
127
+ ip = IPAddr.new( data )
128
+ rescue IPAddr::InvalidAddressError
129
+ return bad_value( jcr, rule_atom, "IPv6 Address", data )
130
+ end
131
+ return bad_value( jcr, rule_atom, "IPv6 Address", data ) unless ip.ipv6?
132
+
133
+ #
134
+ # domain names
135
+ #
136
+
137
+ when jcr[:fqdn]
138
+ return bad_value( jcr, rule_atom, "Fully Qualified Domain Name", data ) unless data.is_a? String
139
+ return bad_value( jcr, rule_atom, "Fully Qualified Domain Name", data ) if data.empty?
140
+ a = data.split( '.' )
141
+ a.each do |label|
142
+ return bad_value( jcr, rule_atom, "Fully Qualified Domain Name", data ) if label.start_with?( '-' )
143
+ return bad_value( jcr, rule_atom, "Fully Qualified Domain Name", data ) if label.end_with?( '-' )
144
+ label.each_char do |char|
145
+ unless (char >= 'a' && char <= 'z') \
146
+ || (char >= 'A' && char <= 'Z') \
147
+ || (char >= '0' && char <='9') \
148
+ || char == '-'
149
+ return bad_value( jcr, rule_atom, "Fully Qualified Domain Name", data )
150
+ end
151
+ end
152
+ end
153
+ when jcr[:idn]
154
+ return bad_value( jcr, rule_atom, "Internationalized Domain Name", data ) unless data.is_a? String
155
+ return bad_value( jcr, rule_atom, "Internationalized Domain Name", data ) if data.empty?
156
+ a = data.split( '.' )
157
+ a.each do |label|
158
+ return bad_value( jcr, rule_atom, "Internationalized Domain Name", data ) if label.start_with?( '-' )
159
+ return bad_value( jcr, rule_atom, "Internationalized Domain Name", data ) if label.end_with?( '-' )
160
+ label.each_char do |char|
161
+ unless (char >= 'a' && char <= 'z') \
162
+ || (char >= 'A' && char <= 'Z') \
163
+ || (char >= '0' && char <='9') \
164
+ || char == '-' \
165
+ || char.ord > 127
166
+ return bad_value( jcr, rule_atom, "Internationalized Domain Name", data )
167
+ end
168
+ end
169
+ end
170
+
171
+ #
172
+ # uri and uri templates
173
+ #
174
+
175
+ when jcr[:uri]
176
+ return bad_value( jcr, rule_atom, "URI", data ) unless data.is_a?( String )
177
+ uri = Addressable::URI.parse( data )
178
+ return bad_value( jcr, rule_atom, "URI", data ) unless uri.is_a?( Addressable::URI )
179
+ when jcr[:uri_template]
180
+ t = jcr[:uri_template].to_s
181
+ return bad_value( jcr, rule_atom, t, data ) unless data.is_a? String
182
+ template = Addressable::Template.new( t )
183
+ e = template.extract( data )
184
+ if e == nil
185
+ return bad_value( jcr, rule_atom, t, data )
186
+ else
187
+ e.each do |k,v|
188
+ return bad_value( jcr, rule_atom, t, data ) unless v
189
+ end
190
+ end
191
+
192
+ #
193
+ # phone and email value rules
194
+ #
195
+
196
+ when jcr[:email]
197
+ return bad_value( jcr, rule_atom, "Email Address", data ) unless data.is_a? String
198
+ return bad_value( jcr, rule_atom, "Email Address", data ) unless EmailAddressValidator.validate( data, true )
199
+
200
+ when jcr[:phone]
201
+ return bad_value( jcr, rule_atom, "Phone Number", data ) unless data.is_a? String
202
+ p = BigPhoney::PhoneNumber.new( data )
203
+ return bad_value( jcr, rule_atom, "Phone Number", data ) unless p.valid?
204
+
205
+ #
206
+ # base64 values
207
+ #
208
+
209
+ when jcr[:base64]
210
+ return bad_value( jcr, rule_atom, "Base 64 Data", data ) unless data.is_a? String
211
+ return bad_value( jcr, rule_atom, "Base 64 Data", data ) if data.empty?
212
+ pad_start = false
213
+ data.each_char do |char|
214
+ if pad_start && char != '='
215
+ return bad_value( jcr, rule_atom, "Base 64 Data", data )
216
+ elsif char == '='
217
+ pad_start = true
218
+ end
219
+ unless (char >= 'a' && char <= 'z') \
220
+ || (char >= 'A' && char <= 'Z') \
221
+ || (char >= '0' && char <='9') \
222
+ || char == '=' || char == '+' || char == '/'
223
+ return bad_value( jcr, rule_atom, "Base 64 Data", data )
224
+ end
225
+ end
226
+
227
+ #
228
+ # time and date values
229
+ #
230
+
231
+ when jcr[:date_time]
232
+ return bad_value( jcr, rule_atom, "Time and Date", data ) unless data.is_a? String
233
+ begin
234
+ Time.iso8601( data )
235
+ rescue ArgumentError
236
+ return bad_value( jcr, rule_atom, "Time and Date", data )
237
+ end
238
+ when jcr[:full_date]
239
+ return bad_value( jcr, rule_atom, "Date", data ) unless data.is_a? String
240
+ begin
241
+ d = data + "T23:20:50.52Z"
242
+ Time.iso8601( d )
243
+ rescue ArgumentError
244
+ return bad_value( jcr, rule_atom, "Date", data )
245
+ end
246
+ when jcr[:full_time]
247
+ return bad_value( jcr, rule_atom, "Time", data ) unless data.is_a? String
248
+ begin
249
+ t = "1985-04-12T" + data + "Z"
250
+ Time.iso8601( t )
251
+ rescue ArgumentError
252
+ return bad_value( jcr, rule_atom, "Time", data )
253
+ end
254
+
255
+ #
256
+ # null
257
+ #
258
+
259
+ when jcr[:null]
260
+ return bad_value( jcr, rule_atom, nil, data ) unless data == nil
261
+
262
+ #
263
+ # groups
264
+ #
265
+
266
+ when jcr[:group_rule]
267
+ return evaluate_group_rule jcr[:group_rule], rule_atom, data, mapping
268
+
269
+ else
270
+ raise "unknown value rule evaluation. this shouldn't happen"
271
+ end
272
+ return Evaluation.new( true, nil )
273
+ end
274
+
275
+ def self.bad_value jcr, rule_atom, expected, actual
276
+ Evaluation.new( false, "expected #{expected} but got #{actual} at #{jcr} from #{rule_atom}" )
277
+ end
278
+
279
+ end
@@ -0,0 +1,106 @@
1
+ # Copyright (c) 2015 American Registry for Internet Numbers
2
+ #
3
+ # Permission to use, copy, modify, and/or distribute this software for any
4
+ # purpose with or without fee is hereby granted, provided that the above
5
+ # copyright notice and this permission notice appear in all copies.
6
+ #
7
+ # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
+ # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
+ # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10
+ # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11
+ # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12
+ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
13
+ # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14
+
15
+ require 'jcr/parser'
16
+ require 'jcr/evaluate_rules'
17
+
18
+ module JCR
19
+
20
+ class Root
21
+ attr_accessor :nameless, :name, :rule, :default
22
+
23
+ def initialize rule, name = nil, nameless = true, default = false
24
+ @rule = rule
25
+ @name = name
26
+ @nameless = nameless
27
+ if name
28
+ @nameless = false
29
+ end
30
+ @default = default
31
+ end
32
+ end
33
+
34
+ def self.find_roots( tree )
35
+ roots = Array.new
36
+ if tree.is_a? Hash
37
+ tree = [ tree ]
38
+ end
39
+ tree.each do |node|
40
+ if node[:rule]
41
+ roots.concat( find_roots_in_named( node ) )
42
+ elsif (top_rule = get_rule_by_type( node ))
43
+ roots << Root.new( node, nil, true, true )
44
+ roots.concat( find_roots_in_unnamed( top_rule ) )
45
+ end
46
+ end
47
+ return roots
48
+ end
49
+
50
+ def self.find_roots_in_named( node )
51
+ roots = Array.new
52
+ rn = node[:rule][:rule_name].to_str
53
+ rule = node[:rule]
54
+ ruledef = get_rule_by_type( rule )
55
+ if ruledef
56
+ if ruledef.is_a? Array
57
+ ruledef.each do |rdi|
58
+ if rdi[:root_annotation]
59
+ roots << Root.new( node, rn )
60
+ elsif (subrule = get_rule_by_type( rdi ))
61
+ roots.concat( find_roots_in_unnamed( subrule ) )
62
+ end
63
+ end
64
+ elsif ruledef.is_a? Hash
65
+ subrule = get_rule_by_type( ruledef )
66
+ roots.concat( find_roots_in_unnamed( subrule ) ) if subrule
67
+ end
68
+ end
69
+ return roots
70
+ end
71
+
72
+ def self.find_roots_in_unnamed( node )
73
+ roots = Array.new
74
+ if node.is_a? Array
75
+ node.each do |n|
76
+ if n[:root_annotation]
77
+ roots << Root.new( node )
78
+ elsif (subrule = get_rule_by_type( n ) )
79
+ roots.concat( find_roots_in_unnamed( subrule ) ) if subrule
80
+ end
81
+ end
82
+ else
83
+ subrule = get_rule_by_type( node )
84
+ roots.concat( find_roots_in_unnamed( subrule ) ) if subrule
85
+ end
86
+ return roots
87
+ end
88
+
89
+ def self.get_rule_by_type rule
90
+ retval = nil
91
+ case
92
+ when rule[:array_rule]
93
+ retval = rule[:array_rule]
94
+ when rule[:object_rule]
95
+ retval = rule[:object_rule]
96
+ when rule[:member_rule]
97
+ retval = rule[:member_rule]
98
+ when rule[:primitive_rule]
99
+ retval = rule[:primitive_rule]
100
+ when rule[:group_rule]
101
+ retval = rule[:group_rule]
102
+ end
103
+ return retval
104
+ end
105
+
106
+ end