jcrvalidator 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/jcr/jcr.rb ADDED
@@ -0,0 +1,228 @@
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 'optparse'
16
+ require 'rubygems'
17
+ require 'json'
18
+
19
+ require 'jcr/parser'
20
+ require 'jcr/evaluate_rules'
21
+ require 'jcr/check_groups'
22
+ require 'jcr/find_roots'
23
+ require 'jcr/map_rule_names'
24
+ require 'jcr/process_directives'
25
+
26
+ module JCR
27
+
28
+ class Context
29
+ attr_accessor :mapping, :callbacks, :id, :tree, :roots, :catalog
30
+
31
+ def add_ruleset_alias( ruleset_alias, alias_uri )
32
+ unless @catalog
33
+ @catalog = Hash.new
34
+ end
35
+ @catalog[ ruleset_alias ] = alias_uri
36
+ end
37
+
38
+ def remove_ruleset_alias( ruleset_alias )
39
+ if @catalog
40
+ @catalog.delete( ruleset_alias )
41
+ end
42
+ end
43
+
44
+ def map_ruleset_alias( ruleset_alias, alias_uri )
45
+ if @catalog
46
+ a = @catalog[ ruleset_alias ]
47
+ if a
48
+ return a
49
+ end
50
+ end
51
+ #else
52
+ return alias_uri
53
+ end
54
+
55
+ def evaluate( data, root_name = nil )
56
+ JCR.evaluate_ruleset( data, self, root_name )
57
+ end
58
+
59
+ def initialize( ruleset = nil )
60
+ if ruleset
61
+ ingested = JCR.ingest_ruleset( ruleset, false, nil )
62
+ @mapping = ingested.mapping
63
+ @callbacks = ingested.callbacks
64
+ @id = ingested.id
65
+ @tree = ingested.tree
66
+ @roots = ingested.roots
67
+ end
68
+ end
69
+
70
+ def override( ruleset )
71
+ overridden = JCR.ingest_ruleset( ruleset, true, nil )
72
+ mapping = {}
73
+ mapping.merge!( @mapping )
74
+ mapping.merge!( overridden.mapping )
75
+ overridden.mapping=mapping
76
+ callbacks = {}
77
+ callbacks.merge!( @callbacks )
78
+ callbacks.merge!( overridden.callbacks )
79
+ overridden.callbacks = callbacks
80
+ overridden.roots.concat( @roots )
81
+ return overridden
82
+ end
83
+
84
+ def override!( ruleset )
85
+ overridden = JCR.ingest_ruleset( ruleset, true, nil )
86
+ @mapping.merge!( overridden.mapping )
87
+ @callbacks.merge!( overridden.callbacks )
88
+ @roots.concat( overridden.roots )
89
+ end
90
+
91
+ end
92
+
93
+ def self.ingest_ruleset( ruleset, override = false, ruleset_alias=nil )
94
+ tree = JCR.parse( ruleset )
95
+ mapping = JCR.map_rule_names( tree, override, ruleset_alias )
96
+ JCR.check_rule_target_names( tree, mapping )
97
+ roots = JCR.find_roots( tree )
98
+ ctx = Context.new
99
+ ctx.tree = tree
100
+ ctx.mapping = mapping
101
+ ctx.callbacks = {}
102
+ ctx.roots = roots
103
+ JCR.process_directives( ctx )
104
+ return ctx
105
+ end
106
+
107
+ def self.evaluate_ruleset( data, ctx, root_name = nil )
108
+ root_rules = []
109
+ if root_name
110
+ root_rule = ctx.mapping[root_name]
111
+ raise "No rule by the name of #{root_name} for a root rule has been found" unless root_rule
112
+ root_rules << root_rule
113
+ else
114
+ ctx.roots.each do |r|
115
+ root_rules << r.rule
116
+ end
117
+ end
118
+
119
+ raise "No root rule defined. Specify a root rule name" if root_rules.empty?
120
+
121
+ retval = nil
122
+ root_rules.each do |r|
123
+ retval = JCR.evaluate_rule( r, r, data, EvalConditions.new( ctx.mapping, ctx.callbacks ) )
124
+ break if retval.success
125
+ end
126
+
127
+ return retval
128
+ end
129
+
130
+ def self.main
131
+
132
+ options = {}
133
+
134
+ opt_parser = OptionParser.new do |opt|
135
+ opt.banner = "Usage: jcr [OPTIONS] [JSON_FILE]"
136
+ opt.separator ""
137
+ opt.separator "Evaluates JSON against JSON Content Rules (JCR)."
138
+ opt.separator ""
139
+ opt.separator "If JSON_FILE is not specified, standard input (STDIN) is used."
140
+ opt.separator ""
141
+ opt.separator "Use -v to see results, otherwise check the exit code."
142
+ opt.separator ""
143
+ opt.separator "Options"
144
+
145
+ opt.on("-r FILE","file containing ruleset") do |ruleset|
146
+ if options[:ruleset]
147
+ puts "A ruleset has already been specified. Use -h for help.", ""
148
+ return 2
149
+ end
150
+ options[:ruleset] = File.open( ruleset ).read
151
+ end
152
+
153
+ opt.on("-R STRING","string containing ruleset. Should probably be quoted") do |ruleset|
154
+ if options[:ruleset]
155
+ puts "A ruleset has already been specified. Use -h for help.", ""
156
+ return 2
157
+ end
158
+ options[:ruleset] = ruleset
159
+ end
160
+
161
+ opt.on("-s STRING","name of root rule. All roots will be tried if none is specified") do |root_name|
162
+ if options[:root_name]
163
+ puts "A root has already been specified. Use -h for help.", ""
164
+ return 2
165
+ end
166
+ options[:root_name] = root_name
167
+ end
168
+
169
+ opt.on("-o FILE","file containing overide ruleset (option can be repeated)") do |ruleset|
170
+ unless options[:overrides]
171
+ options[:overrides] = Array.new
172
+ end
173
+ options[:overrides] << File.open( ruleset ).read
174
+ end
175
+
176
+ opt.on("-O STRING","string containing overide rule (option can be repeated)") do |rule|
177
+ unless options[:overrides]
178
+ options[:overrides] = Array.new
179
+ end
180
+ options[:overrides] << rule
181
+ end
182
+
183
+ opt.on("-v","verbose") do |verbose|
184
+ options[:verbose] = true
185
+ end
186
+
187
+ opt.on("-h","display help") do |help|
188
+ options[:help] = true
189
+ end
190
+ end
191
+
192
+ opt_parser.parse!
193
+
194
+ if options[:help]
195
+ puts "HELP","----",""
196
+ puts opt_parser
197
+ return 2
198
+ elsif !options[:ruleset]
199
+ puts "No ruleset passed! Use -R or -r options.", ""
200
+ puts opt_parser
201
+ return 2
202
+ else
203
+
204
+ ctx = Context.new( options[:ruleset] )
205
+ if options[:overrides]
206
+ options[:overrides].each do |ov|
207
+ ctx.override!( ov )
208
+ end
209
+ end
210
+ data = JSON.parse( ARGF.read )
211
+ e = ctx.evaluate( data, options[:root_name] )
212
+ if e.success
213
+ if options[:verbose]
214
+ puts "Success!"
215
+ end
216
+ return 0
217
+ else
218
+ if options[:verbose]
219
+ puts "Failure: #{e.reason}"
220
+ end
221
+ return 1
222
+ end
223
+
224
+ end
225
+
226
+ end
227
+
228
+ end
@@ -0,0 +1,82 @@
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
+
17
+ module JCR
18
+
19
+ def self.map_rule_names( tree, override = false, ruleset_alias = nil )
20
+ prefix = ""
21
+ if ruleset_alias
22
+ prefix = ruleset_alias
23
+ unless prefix.end_with?( "." )
24
+ prefix = prefix + "."
25
+ end
26
+ end
27
+ rule_name_maping = Hash.new
28
+ if tree.is_a? Hash
29
+ tree = [ tree ]
30
+ end
31
+ tree.each do |node|
32
+ if node[:rule]
33
+ rn = prefix + node[:rule][:rule_name].to_str
34
+ if rule_name_maping[ rn ] && !override
35
+ raise "Rule #{rn} already exists and is defined more than once"
36
+ else
37
+ rule_name_maping[ rn ] = node[:rule]
38
+ end
39
+ end
40
+ end
41
+ return rule_name_maping
42
+ end
43
+
44
+ def self.check_rule_target_names( node, mapping )
45
+ if node.is_a? Array
46
+ node.each do |child_node|
47
+ check_rule_target_names( child_node, mapping )
48
+ end
49
+ elsif node.is_a? Hash
50
+ if node[:target_rule_name] && !mapping[ node[:target_rule_name][:rule_name].to_str ]
51
+ raise_rule_name_missing node[:target_rule_name][:rule_name]
52
+ else
53
+ if node[:rule]
54
+ check_rule_target_names( node[:rule], mapping )
55
+ elsif node[:group_rule]
56
+ check_rule_target_names( node[:group_rule], mapping )
57
+ elsif node[:primitive_rule]
58
+ check_rule_target_names( node[:primitive_rule], mapping )
59
+ elsif node[:array_rule]
60
+ check_rule_target_names( node[:array_rule], mapping )
61
+ elsif node[:object_rule]
62
+ check_rule_target_names( node[:object_rule], mapping )
63
+ elsif node[:member_rule]
64
+ check_rule_target_names( node[:member_rule], mapping )
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def self.get_name_mapping rule_name, mapping
71
+ trule = mapping[ rule_name.to_str ]
72
+ raise_rule_name_missing( rule_name ) unless trule
73
+ return trule
74
+ end
75
+
76
+ def self.raise_rule_name_missing rule_name
77
+ pos = rule_name.line_and_column
78
+ name = rule_name.to_str
79
+ raise "rule '" + name + "' at line " + pos[0].to_s + " column " + pos[1].to_s + " does not exist"
80
+ end
81
+
82
+ end