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.
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