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.
- checksums.yaml +7 -0
- data/bin/jcr +26 -0
- data/lib/jcr/check_groups.rb +195 -0
- data/lib/jcr/evaluate_array_rules.rb +270 -0
- data/lib/jcr/evaluate_group_rules.rb +46 -0
- data/lib/jcr/evaluate_member_rules.rb +61 -0
- data/lib/jcr/evaluate_object_rules.rb +115 -0
- data/lib/jcr/evaluate_rules.rb +211 -0
- data/lib/jcr/evaluate_value_rules.rb +279 -0
- data/lib/jcr/find_roots.rb +106 -0
- data/lib/jcr/jcr.rb +228 -0
- data/lib/jcr/map_rule_names.rb +82 -0
- data/lib/jcr/parser.rb +398 -0
- data/lib/jcr/process_directives.rb +83 -0
- data/lib/jcr.rb +1 -0
- metadata +60 -0
@@ -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
|