From f896e4df3b06a90e674e8d4bb385067aa4a7d4c6 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Fri, 8 Dec 2017 17:34:27 -0500 Subject: [PATCH 01/16] fix to stop mac builds of jruby on travis ci --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 74c3cd3..dfe74bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ matrix: exclude: - rvm: 2.1.3 os: osx - - rvm: jruby-9.1 + - rvm: jruby-9.1.9.0 os: osx script: - "bundle exec rake test" From 6ca2115c52d39349f16e2778b67865d428d952f3 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sat, 9 Dec 2017 19:11:24 -0500 Subject: [PATCH 02/16] beginning of 0.8.1-alpha1 work --- lib/jcr/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/jcr/version.rb b/lib/jcr/version.rb index 543155c..6bf3ac9 100644 --- a/lib/jcr/version.rb +++ b/lib/jcr/version.rb @@ -15,6 +15,6 @@ module JCR - VERSION = "0.8.0" + VERSION = "0.8.1-alpha1" end \ No newline at end of file From 1940273bb77d468aad44a5c5edd33a5cd872c3f5 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sat, 9 Dec 2017 19:53:37 -0500 Subject: [PATCH 03/16] fixed a bunch of stack trace issues with unhappy paths --- lib/jcr/jcr.rb | 25 ++++++++++++++++++------- lib/jcr/jcr_validator_error.rb | 21 +++++++++++++++++++++ lib/jcr/version.rb | 2 +- spec/jcr_spec.rb | 31 +++++++++++++++++++++++++++++-- 4 files changed, 69 insertions(+), 10 deletions(-) create mode 100644 lib/jcr/jcr_validator_error.rb diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index 1b015d5..dbca2f0 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -17,6 +17,7 @@ require 'json' require 'pp' +require 'jcr/jcr_validator_error' require 'jcr/parser' require 'jcr/evaluate_rules' require 'jcr/check_groups' @@ -114,19 +115,19 @@ def self.evaluate_ruleset( data, ctx, root_name = nil ) roots = [] if root_name root_rule = ctx.mapping[root_name] - raise "No rule by the name of #{root_name} for a root rule has been found" unless root_rule + raise JcrValidatorError, "No rule by the name of #{root_name} for a root rule has been found" unless root_rule root = JCR::Root.new( root_rule, root_name ) roots << root else roots = ctx.roots end - raise "No root rule defined. Specify a root rule name" if roots.empty? + raise JcrValidatorError, "No root rule defined. Specify a root rule name" if roots.empty? retval = nil roots.each do |r| pp "Evaluating Root:", rule_to_s( r.rule, false ) if ctx.trace - raise "Root rules cannot be member rules" if r.rule[:member_rule] + raise JcrValidatorError, "Root rules cannot be member rules" if r.rule[:member_rule] econs = EvalConditions.new( ctx.mapping, ctx.callbacks, ctx.trace ) retval = JCR.evaluate_rule( r.rule, r.rule, data, econs ) break if retval.success @@ -248,15 +249,22 @@ def self.main my_argv=nil opt.separator "" opt.separator "Return codes:" opt.separator " 0 = success" - opt.separator " 1 = parsing or other bad condition" - opt.separator " 2 = fall through bad condition" + opt.separator " 1 = bad JCR parsing or other bad condition" + opt.separator " 2 = invalid option or bad use of command" opt.separator " 3 = unsuccessful evaluation of JSON" opt.separator "" opt.separator "JCR Version " + JCR::VERSION end - opt_parser.parse! my_argv + begin + opt_parser.parse! my_argv + rescue OptionParser::InvalidOption => e + puts "Unable to interpret command or options" + puts e.message + puts "", "Use -h for help" + return 2 + end if options[:help] puts "HELP","----","" @@ -264,7 +272,7 @@ def self.main my_argv=nil return 2 elsif !options[:ruleset] puts "No ruleset passed! Use -R or -r options.", "" - puts opt_parser + puts "Use -h for help" return 2 else @@ -328,6 +336,9 @@ def self.main my_argv=nil return ec end + rescue JCR::JcrValidatorError => jcr_error + puts jcr_error.message + return 1 rescue Parslet::ParseFailed => failure puts failure.parse_failure_cause.ascii_tree unless options[:quiet] return 1 diff --git a/lib/jcr/jcr_validator_error.rb b/lib/jcr/jcr_validator_error.rb new file mode 100644 index 0000000..3a9507b --- /dev/null +++ b/lib/jcr/jcr_validator_error.rb @@ -0,0 +1,21 @@ +# Copyright (c) 2017 American Registry for Internet Numbers +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +module JCR + + class JcrValidatorError < StandardError + + end + +end \ No newline at end of file diff --git a/lib/jcr/version.rb b/lib/jcr/version.rb index 6bf3ac9..677538e 100644 --- a/lib/jcr/version.rb +++ b/lib/jcr/version.rb @@ -1,4 +1,4 @@ -# Copyright (c) 2016 American Registry for Internet Numbers +# Copyright (c) 2016-2017 American Registry for Internet Numbers # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above diff --git a/spec/jcr_spec.rb b/spec/jcr_spec.rb index 37e7667..f887ea4 100644 --- a/spec/jcr_spec.rb +++ b/spec/jcr_spec.rb @@ -491,11 +491,38 @@ end it 'should parse from the command line' do - expect{ JCR.main( ['-R', '$mrule = "mname" : integer', '-J', '["mname",12]'] ) }.to raise_error RuntimeError + expect{ @ec = JCR.main( ['-R', '$mrule = "mname" : integer', '-J', '["mname",12]'] ) }.to output.to_stdout + expect( @ec ).to eq( 1 ) end it 'should parse from the command line and fail' do - expect{ JCR.main( ['-R', '$mrule = "mname" : integer', '-J', '["mname",12]', '-S', '$mrule'] ) }.to raise_error RuntimeError + expect{ @ec = JCR.main( ['-R', '$mrule = "mname" : integer', '-J', '["mname",12]', '-S', 'mrule'] ) }.to output.to_stdout + expect( @ec ).to eq( 1 ) + end + + it 'should fail to parse command line options' do + expect{ @ec = JCR.main( ['-Q'] ) }.to output.to_stdout + expect( @ec ).to eq( 2 ) + end + + it 'should error out when no rule is found' do + expect{ @ec = JCR.main( ['-R', '$mrule = [ integer ]', '-J', '["mname",12]', '-S', 'orule'] ) }.to output.to_stdout + expect( @ec ).to eq( 1 ) + end + + it 'should not be quiet' do + expect{ @ec = JCR.main( ['-R', '$mrule = [ integer ]', '-J', '[12]', '-S', 'mrule'] ) }.to output.to_stdout + expect( @ec ).to eq( 0 ) + end + + it 'should be quiet' do + expect{ @ec = JCR.main( ['-q', '-R', '$mrule = [ integer ]', '-J', '[12]', '-S', 'mrule'] ) }.to_not output.to_stdout + expect( @ec ).to eq( 0 ) + end + + it 'should be quiet even if validation is bad' do + expect{ @ec = JCR.main( ['-q', '-R', '$mrule = [ string ]', '-J', '[12]', '-S', 'mrule'] ) }.to_not output.to_stdout + expect( @ec ).to eq( 3 ) end it 'should use line numbers in unnamed root failures' do From 39306c73698bb5bba246aa53e324812eb98ec0fa Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sun, 10 Dec 2017 07:16:21 -0500 Subject: [PATCH 04/16] --process-parts now creates a file with xml entity refs --- lib/jcr/parts.rb | 14 +++++++++++++- spec/parts_spec.rb | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/jcr/parts.rb b/lib/jcr/parts.rb index e2880a6..309a99e 100644 --- a/lib/jcr/parts.rb +++ b/lib/jcr/parts.rb @@ -69,18 +69,20 @@ def get_end( line ) # processes the lines # ruleset is to be a string read in using File.read def process_ruleset( ruleset ) + all_file_names = [] all_parts = [] all_parts_name = nil current_part = nil current_part_name = nil ruleset.lines do |line| if !all_parts_name && ( all_parts_name = get_all( line ) ) - # do_nothing + all_file_names << all_parts_name elsif ( current_part_name = get_start( line ) ) if current_part current_part.close end current_part = File.open( current_part_name, "w" ) + all_file_names << current_part_name elsif get_end( line ) && current_part current_part.close current_part = nil @@ -101,6 +103,16 @@ def process_ruleset( ruleset ) end f.close end + if all_file_names.length + xml_fn = File.basename( all_file_names[0],".*" ) + "_xml_entity_refs" + xml_fn = File.join( File.dirname( all_file_names[0] ), xml_fn ) + xml = File.open( xml_fn, "w" ) + all_file_names.each do |fn| + bn = File.basename( fn, ".*" ) + xml.puts( "") + end + xml.close + end end end diff --git a/spec/parts_spec.rb b/spec/parts_spec.rb index 31b9ccd..325c487 100644 --- a/spec/parts_spec.rb +++ b/spec/parts_spec.rb @@ -108,6 +108,11 @@ part2 = < + + +XMLREFS r = File.open( rulest_fn, "w" ) r.write( ruleset ) r.close @@ -115,6 +120,8 @@ expect( File.open(all_parts_fn).read ).to eq( all_parts ) expect( File.open(part1_fn).read ).to eq( part1 ) expect( File.open(part2_fn).read ).to eq( part2 ) + xml_fn = File.join( @work_dir, "all_xml_entity_refs" ) + expect( File.open(xml_fn).read ).to eq( xml_refs ) end end \ No newline at end of file From f3fb95834d789a00d67e4c5062075ac378854043 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sun, 10 Dec 2017 08:44:31 -0500 Subject: [PATCH 05/16] --process-parts now works with overrides --- lib/jcr/jcr.rb | 6 +++++ spec/jcr_spec.rb | 3 +++ spec/parts_spec.rb | 66 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index dbca2f0..825e892 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -299,6 +299,12 @@ def self.main my_argv=nil if options[:process_parts] parts = JCR::JcrParts.new parts.process_ruleset( options[:ruleset] ) + if options[:overrides ] + options[:overrides].each do |ov| + parts = JCR::JcrParts.new + parts.process_ruleset( ov ) + end + end end if options[:testjcr] diff --git a/spec/jcr_spec.rb b/spec/jcr_spec.rb index f887ea4..56e4d3b 100644 --- a/spec/jcr_spec.rb +++ b/spec/jcr_spec.rb @@ -525,6 +525,9 @@ expect( @ec ).to eq( 3 ) end + #TODO write test for --test-jcr of ruleset and overrides + #TODO write fail test for --test-jcr of ruleset and overrides + it 'should use line numbers in unnamed root failures' do ctx = JCR::Context.new( '[ 0..2 *2, ( "foo" | "bar" ) ]', false ) data = JSON.parse( '[1,2,"fuz","bar"]') diff --git a/spec/parts_spec.rb b/spec/parts_spec.rb index 325c487..690ff70 100644 --- a/spec/parts_spec.rb +++ b/spec/parts_spec.rb @@ -70,6 +70,8 @@ end it 'should process parts' do + + #setup ruleset rulest_fn = File.join( @work_dir, "ruleset.jcr" ) all_parts_fn = File.join( @work_dir, "all.jcr" ) part1_fn = File.join(@work_dir, "part1.jcr") @@ -116,12 +118,74 @@ r = File.open( rulest_fn, "w" ) r.write( ruleset ) r.close - JCR.main( [ "-r", rulest_fn, "--test-jcr", "--process-parts" ] ) + + #setup override ruleset + override_fn = File.join( @work_dir, "override.jcr" ) + ov_all_fn = File.join( @work_dir, "ov_all.jcr" ) + ov_p1_fn = File.join( @work_dir, "ov_p1.jcr" ) + ov_p2_fn = File.join( @work_dir, "ov_p2.jcr" ) + override = < + + +OV_XMLREFS + o = File.open( override_fn, "w" ) + o.write( override ) + o.close + + # EXECUTE!!! + JCR.main( [ "-r", rulest_fn, "-o", override_fn, "--test-jcr", "--process-parts" ] ) + + # Test the ruleset expect( File.open(all_parts_fn).read ).to eq( all_parts ) expect( File.open(part1_fn).read ).to eq( part1 ) expect( File.open(part2_fn).read ).to eq( part2 ) xml_fn = File.join( @work_dir, "all_xml_entity_refs" ) expect( File.open(xml_fn).read ).to eq( xml_refs ) + + # Test the overrides + expect( File.open(ov_all_fn).read ).to eq( ov_all ) + expect( File.open(ov_p1_fn).read ).to eq( ov_p1 ) + expect( File.open(ov_p2_fn).read ).to eq( ov_p2 ) + ov_xml_fn = File.join( @work_dir, "ov_all_xml_entity_refs" ) + expect( File.open(ov_xml_fn).read ).to eq( ov_xml_refs ) + end + #TODO write tests for --process-parts when a directory is given + end \ No newline at end of file From 4e8dd7fbdaf0d0b68eec8e4228f263623af646b5 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sun, 10 Dec 2017 12:09:36 -0500 Subject: [PATCH 06/16] --process-parts now takes an optional directory --- lib/jcr/jcr.rb | 7 +-- lib/jcr/parts.rb | 4 +- spec/parts_spec.rb | 121 +++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 125 insertions(+), 7 deletions(-) diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index 825e892..4e5a8e7 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -200,8 +200,9 @@ def self.main my_argv=nil options[:testjcr] = true end - opt.on("--process-parts", "creates smaller files for specification writing" ) do |parts| + opt.on("--process-parts [DIRECTORY]", "creates smaller files for specification writing" ) do |directory| options[:process_parts] = true + options[:process_parts_directory] = directory end opt.on("-S STRING","name of root rule. All roots will be tried if none is specified") do |root_name| @@ -298,11 +299,11 @@ def self.main my_argv=nil if options[:process_parts] parts = JCR::JcrParts.new - parts.process_ruleset( options[:ruleset] ) + parts.process_ruleset( options[:ruleset], options[:process_parts_directory] ) if options[:overrides ] options[:overrides].each do |ov| parts = JCR::JcrParts.new - parts.process_ruleset( ov ) + parts.process_ruleset( ov, options[:process_parts_directory] ) end end end diff --git a/lib/jcr/parts.rb b/lib/jcr/parts.rb index 309a99e..4bd6f0e 100644 --- a/lib/jcr/parts.rb +++ b/lib/jcr/parts.rb @@ -68,7 +68,7 @@ def get_end( line ) # processes the lines # ruleset is to be a string read in using File.read - def process_ruleset( ruleset ) + def process_ruleset( ruleset, dirname = nil ) all_file_names = [] all_parts = [] all_parts_name = nil @@ -76,8 +76,10 @@ def process_ruleset( ruleset ) current_part_name = nil ruleset.lines do |line| if !all_parts_name && ( all_parts_name = get_all( line ) ) + all_parts_name = File.join( dirname, all_parts_name ) if dirname all_file_names << all_parts_name elsif ( current_part_name = get_start( line ) ) + current_part_name = File.join( dirname, current_part_name ) if dirname if current_part current_part.close end diff --git a/spec/parts_spec.rb b/spec/parts_spec.rb index 690ff70..18d05d0 100644 --- a/spec/parts_spec.rb +++ b/spec/parts_spec.rb @@ -22,11 +22,11 @@ @work_dir = nil - before(:all) do + before(:each) do @work_dir = Dir.mktmpdir end - after(:all) do + after(:each) do FileUtils.rm_r( @work_dir ) end @@ -186,6 +186,121 @@ end - #TODO write tests for --process-parts when a directory is given + it 'should process parts with a directory' do + + #setup ruleset + rulest_fn = File.join( @work_dir, "ruleset.jcr" ) + all_parts_fn = File.join( @work_dir, "all.jcr" ) + part1_fn = File.join(@work_dir, "part1.jcr") + part2_fn = File.join(@work_dir, "part2.jcr") + ruleset = < + + +XMLREFS + r = File.open( rulest_fn, "w" ) + r.write( ruleset ) + r.close + + #setup override ruleset + override_fn = File.join( @work_dir, "override.jcr" ) + ov_all_fn = File.join( @work_dir, "ov_all.jcr" ) + ov_p1_fn = File.join( @work_dir, "ov_p1.jcr" ) + ov_p2_fn = File.join( @work_dir, "ov_p2.jcr" ) + override = < + + +OV_XMLREFS + o = File.open( override_fn, "w" ) + o.write( override ) + o.close + + # EXECUTE!!! + JCR.main( [ "-r", rulest_fn, "-o", override_fn, "--test-jcr", "--process-parts", @work_dir ] ) + + # Test the ruleset + expect( File.open(all_parts_fn).read ).to eq( all_parts ) + expect( File.open(part1_fn).read ).to eq( part1 ) + expect( File.open(part2_fn).read ).to eq( part2 ) + xml_fn = File.join( @work_dir, "all_xml_entity_refs" ) + expect( File.open(xml_fn).read ).to eq( xml_refs ) + + # Test the overrides + expect( File.open(ov_all_fn).read ).to eq( ov_all ) + expect( File.open(ov_p1_fn).read ).to eq( ov_p1 ) + expect( File.open(ov_p2_fn).read ).to eq( ov_p2 ) + ov_xml_fn = File.join( @work_dir, "ov_all_xml_entity_refs" ) + expect( File.open(ov_xml_fn).read ).to eq( ov_xml_refs ) + + end end \ No newline at end of file From 6d50c98c945f564d5fef88ed6087ce02021c2bd6 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Mon, 11 Dec 2017 13:02:48 -0500 Subject: [PATCH 07/16] work to get override rules to reference rules in the original ruleset --- README.md | 12 +++- lib/jcr/jcr.rb | 17 +++-- lib/jcr/map_rule_names.rb | 10 +-- lib/jcr/process_directives.rb | 2 +- spec/jcr_spec.rb | 121 +++++++++++++++++++++++++++++++++- spec/map_rule_names_spec.rb | 24 ++----- 6 files changed, 151 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 8c223d0..2674aa7 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,11 @@ which can be found [here](https://raw.githubusercontent.com/arineng/jcr/09/draft * Much better CLI and programmatic validation failure information and structures * Fixes to print errors when the JCR fails to parse * 0.8.0 - Adds the --process-parts command line option +* 0.8.1 + * Various issues with stack traces at the command line instead of proper errors + * --process-parts now takes an optional directory + * --process-parts now creates an XML entity reference file snippet + * override rules can now reference rules in the original ruleset The current version of the JCR specification can be found [here](https://raw.githubusercontent.com/arineng/jcr/07/draft-newton-json-content-rules.txt) @@ -222,8 +227,8 @@ The `callback.rb` demonstrates the usage of custom code for evaluation of rules. The `--process-parts` option extracts parts of a JCR file into multiple files based on comments in the file. It can also create a new file without the -comments. This is useful for JCR going into specification documents -where it is nice to break the JCR up for illustrative purposes in +comments. This is useful for rulesets going into specification documents +where it is nice to break the rulesets up for illustrative purposes in the specification but to also have one JCR file for programmatic testing purposes. @@ -241,6 +246,9 @@ though leading whitespace is allowed if desired. To get a new file with all parts but these comments, use this ; all_parts FILENAME + +The `--process-parts` parameter will also take an optional directory name where it +will write the files. ## Building diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index 4e5a8e7..8a277fc 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -64,7 +64,7 @@ def initialize( ruleset = nil, trace = false ) @trace = trace @failed_roots = [] if ruleset - ingested = JCR.ingest_ruleset( ruleset, false, nil ) + ingested = JCR.ingest_ruleset( ruleset, nil, nil ) @mapping = ingested.mapping @callbacks = ingested.callbacks @id = ingested.id @@ -74,7 +74,7 @@ def initialize( ruleset = nil, trace = false ) end def override( ruleset ) - overridden = JCR.ingest_ruleset( ruleset, true, nil ) + overridden = JCR.ingest_ruleset( ruleset, @mapping, nil ) mapping = {} mapping.merge!( @mapping ) mapping.merge!( overridden.mapping ) @@ -88,7 +88,7 @@ def override( ruleset ) end def override!( ruleset ) - overridden = JCR.ingest_ruleset( ruleset, true, nil ) + overridden = JCR.ingest_ruleset( ruleset, @mapping, nil ) @mapping.merge!( overridden.mapping ) @callbacks.merge!( overridden.callbacks ) @roots.concat( overridden.roots ) @@ -96,11 +96,14 @@ def override!( ruleset ) end - def self.ingest_ruleset( ruleset, override = false, ruleset_alias=nil ) + def self.ingest_ruleset( ruleset, existing_mapping = nil, ruleset_alias=nil ) tree = JCR.parse( ruleset ) - mapping = JCR.map_rule_names( tree, override, ruleset_alias ) - JCR.check_rule_target_names( tree, mapping ) - JCR.check_groups( tree, mapping ) + mapping = JCR.map_rule_names( tree, ruleset_alias ) + combined_mapping = {} + combined_mapping.merge!( existing_mapping ) if existing_mapping + combined_mapping.merge!( mapping ) + JCR.check_rule_target_names( tree, combined_mapping ) + JCR.check_groups( tree, combined_mapping ) roots = JCR.find_roots( tree ) ctx = Context.new ctx.tree = tree diff --git a/lib/jcr/map_rule_names.rb b/lib/jcr/map_rule_names.rb index b10f76e..7e8593c 100644 --- a/lib/jcr/map_rule_names.rb +++ b/lib/jcr/map_rule_names.rb @@ -12,11 +12,12 @@ # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +require 'jcr/jcr_validator_error' require 'jcr/parser' module JCR - def self.map_rule_names( tree, override = false, ruleset_alias = nil ) + def self.map_rule_names( tree, ruleset_alias = nil ) prefix = "" if ruleset_alias prefix = ruleset_alias @@ -31,8 +32,8 @@ def self.map_rule_names( tree, override = false, ruleset_alias = nil ) tree.each do |node| if node[:rule] rn = prefix + node[:rule][:rule_name].to_str - if rule_name_maping[ rn ] && !override - raise "Rule #{rn} already exists and is defined more than once" + if rule_name_maping[ rn ] + raise JCR::JcrValidatorError, "Rule #{rn} already exists and is defined more than once" else rule_name_maping[ rn ] = node[:rule] end @@ -76,7 +77,8 @@ def self.get_name_mapping rule_name, mapping def self.raise_rule_name_missing rule_name pos = rule_name.line_and_column name = rule_name.to_str - raise "rule '" + name + "' at line " + pos[0].to_s + " column " + pos[1].to_s + " does not exist" + raise JCR::JcrValidatorError, + "rule '" + name + "' at line " + pos[0].to_s + " column " + pos[1].to_s + " does not exist" end end \ No newline at end of file diff --git a/lib/jcr/process_directives.rb b/lib/jcr/process_directives.rb index 76e9a9d..89f6015 100644 --- a/lib/jcr/process_directives.rb +++ b/lib/jcr/process_directives.rb @@ -74,7 +74,7 @@ def self.process_import( directive, ctx ) ruleset = File.open( uri.path ) end - import_ctx = JCR.ingest_ruleset( ruleset, false, ruleset_alias ) + import_ctx = JCR.ingest_ruleset( ruleset, nil, ruleset_alias ) ctx.mapping.merge!( import_ctx.mapping ) ctx.roots.concat( import_ctx.roots ) diff --git a/spec/jcr_spec.rb b/spec/jcr_spec.rb index 56e4d3b..0c0a5c1 100644 --- a/spec/jcr_spec.rb +++ b/spec/jcr_spec.rb @@ -152,6 +152,53 @@ expect( e.success ).to be_truthy end + it 'should allow an override! rule to reference a ruleset rule' do + ctx = JCR::Context.new( "$b=:2" ) + ctx.override!( "$a=[$b]") + end + + it 'should fail default rule referencing two rules and no override' do + ex = < Date: Mon, 11 Dec 2017 15:16:35 -0500 Subject: [PATCH 08/16] more readable failure report --- README.md | 1 + lib/jcr/evaluate_rules.rb | 23 ++++++++++++++++++++++- lib/jcr/jcr.rb | 22 +++++++++++++++++++--- spec/jcr_spec.rb | 18 +++++++++++++++--- spec/parts_spec.rb | 2 +- 5 files changed, 58 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2674aa7..c9026d5 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,7 @@ which can be found [here](https://raw.githubusercontent.com/arineng/jcr/09/draft * --process-parts now takes an optional directory * --process-parts now creates an XML entity reference file snippet * override rules can now reference rules in the original ruleset + * more readable failure report The current version of the JCR specification can be found [here](https://raw.githubusercontent.com/arineng/jcr/07/draft-newton-json-content-rules.txt) diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index 84815b7..a7729ea 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -409,7 +409,28 @@ def self.slice_to_s slice end def self.raised_rule jcr, rule_atom - " rule at #{slice_to_s(jcr)} [ #{jcr} ] from rule at #{slice_to_s(rule_atom)}" + " rule at #{slice_to_s(jcr)} [ #{jcr_to_s(jcr)} ] from rule at #{slice_to_s(rule_atom)}" + end + + def self.jcr_to_s( jcr, shallow=true ) + if jcr.is_a? Array + retval = "" + jcr.each_with_index do |item,idx| + if idx > 1 + retval = retval + " , " + end + retval = retval + jcr_to_s( item, shallow ) + end + elsif jcr.is_a? Parslet::Slice + retval = slice_to_s( jcr ) + else + if jcr[:q_string] + retval = value_to_s( jcr ) + else + retval = rule_to_s( jcr, shallow ) + end + end + return retval end def self.rule_to_s( rule, shallow=true) diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index 8a277fc..16ee01f 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -147,9 +147,9 @@ def self.failure_report ctx report = [] ctx.failed_roots.each do |failed_root| if failed_root.name - report << "- ** Failures for root rule named '#{failed_root.name}'" + report << "- Failures for root rule named '#{failed_root.name}'" else - report << "- ** Failures for root rule at line #{failed_root.pos[0]}" + report << "- Failures for root rule at line #{failed_root.pos[0]}" end failed_root.failures.sort.map do |stack_level, failures| if failures.length > 1 @@ -158,13 +158,29 @@ def self.failure_report ctx report << " - failure at rule depth #{stack_level} caused by" end failures.each_with_index do |failure, index| - report << " - #{failure.json_elided} failed rule #{failure.definition} at #{failure.pos} because #{failure.reason_elided}" + lines = breakup_message( "#{failure.json_elided} failed rule #{failure.definition} at #{failure.pos} because #{failure.evaluation.reason}", 75 ) + lines.each_with_index do |l,i| + if i == 0 + report << " - #{l}" + else + report << " #{l}" + end + end end end end return report end + def self.breakup_message( message, line_length ) + line = message.gsub(/(.{1,#{line_length}})(\s+|\Z)/, "\\1\n") + lines = [] + line.each_line do |l| + lines << l.strip + end + return lines + end + def self.main my_argv=nil my_argv = ARGV unless my_argv diff --git a/spec/jcr_spec.rb b/spec/jcr_spec.rb index 0c0a5c1..9f19879 100644 --- a/spec/jcr_spec.rb +++ b/spec/jcr_spec.rb @@ -647,21 +647,33 @@ ctx = JCR::Context.new( '[ 0..2 *2, ( "foo" | "bar" ) ]', false ) data = JSON.parse( '[1,2,"fuz","bar"]') e = ctx.evaluate( data ) - expect( ctx.failure_report[0] ).to eq( "- ** Failures for root rule at line 1") + expect( ctx.failure_report[0] ).to eq( "- Failures for root rule at line 1") end it 'should use names in specified root failures' do ctx = JCR::Context.new( '$root = [ 0..2 *2, ( "foo" | "bar" ) ]', false ) data = JSON.parse( '[1,2,"fuz","bar"]') e = ctx.evaluate( data, "root" ) - expect( ctx.failure_report[0] ).to eq( "- ** Failures for root rule named 'root'") + expect( ctx.failure_report[0] ).to eq( "- Failures for root rule named 'root'") end it 'should use names in annotated root failures' do ctx = JCR::Context.new( '@{root} $root = [ 0..2 *2, ( "foo" | "bar" ) ]', false ) data = JSON.parse( '[1,2,"fuz","bar"]') e = ctx.evaluate( data ) - expect( ctx.failure_report[0] ).to eq( "- ** Failures for root rule named 'root'") + expect( ctx.failure_report[0] ).to eq( "- Failures for root rule named 'root'") + end + + it 'should breakup long lines' do + lines = JCR.breakup_message( "12345 abcde ABCD", 6) + expect( lines[0] ).to eq( "12345" ) + expect( lines[1] ).to eq( "abcde" ) + expect( lines[2] ).to eq( "ABCD" ) + end + + it 'should try to break up a long line' do + lines = JCR.breakup_message( "12345abcdeABCD", 6) + expect( lines[0] ).to eq( "12345abcdeABCD" ) end end diff --git a/spec/parts_spec.rb b/spec/parts_spec.rb index 18d05d0..a33f581 100644 --- a/spec/parts_spec.rb +++ b/spec/parts_spec.rb @@ -1,4 +1,4 @@ -# Copyright (C) 2016 American Registry for Internet Numbers (ARIN) +# Copyright (C) 2017 American Registry for Internet Numbers (ARIN) # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above From 61f49a73eac8c5e3ae85b49bbbf9dc46f9eec0d9 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Mon, 11 Dec 2017 16:03:05 -0500 Subject: [PATCH 09/16] still more readable failure report --- lib/jcr/evaluate_array_rules.rb | 20 ++++++++++---------- lib/jcr/evaluate_object_rules.rb | 14 +++++++------- lib/jcr/evaluate_rules.rb | 6 +++--- lib/jcr/evaluate_value_rules.rb | 2 +- lib/jcr/jcr.rb | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/jcr/evaluate_array_rules.rb b/lib/jcr/evaluate_array_rules.rb index 02e97b8..2780ee5 100644 --- a/lib/jcr/evaluate_array_rules.rb +++ b/lib/jcr/evaluate_array_rules.rb @@ -187,7 +187,7 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil behavior.last_index = array_index if data.length > array_index && behavior.extra_prohibited - retval = Evaluation.new( false, "More itmes in array than specified for #{raised_rule(jcr,rule_atom)}" ) + retval = Evaluation.new( false, "More items in array than specified for #{raised_rule(jcr,rule_atom)}" ) end return retval @@ -236,13 +236,13 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n end if successes == 0 && repeat_min > 0 - retval = Evaluation.new( false, "array does not contain #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array does not contain #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif successes < repeat_min - retval = Evaluation.new( false, "array does not have enough #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array does not have enough #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif successes > repeat_max - retval = Evaluation.new( false, "array has too many #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array has too many #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif repeat_step && ( successes - repeat_min ) % repeat_step != 0 - retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") else retval = Evaluation.new( true, nil ) end @@ -263,13 +263,13 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n end if successes == 0 && repeat_min > 0 - retval = Evaluation.new( false, "array does not contain #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array does not contain #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif successes < repeat_min - retval = Evaluation.new( false, "array does not have enough #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array does not have enough #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif successes > repeat_max - retval = Evaluation.new( false, "array has too many #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array has too many #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif repeat_step && ( successes - repeat_min ) % repeat_step != 0 - retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "array matches (#{successes}) do not meet repetition step of #{repeat_max} % #{repeat_step} with #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") else retval = Evaluation.new( true, nil) end @@ -281,7 +281,7 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n behavior.last_index = highest_index if data.length > behavior.checked_hash.length && behavior.extra_prohibited - retval = Evaluation.new( false, "More itmes in array than specified for #{raised_rule(jcr,rule_atom)}" ) + retval = Evaluation.new( false, "More items in array than specified for #{raised_rule(jcr,rule_atom)}" ) end return retval diff --git a/lib/jcr/evaluate_object_rules.rb b/lib/jcr/evaluate_object_rules.rb index 7a364ed..1416b5a 100644 --- a/lib/jcr/evaluate_object_rules.rb +++ b/lib/jcr/evaluate_object_rules.rb @@ -92,11 +92,11 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil end if successes == 0 && repeat_min > 0 - retval = Evaluation.new( false, "object does not contain group #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "object does not contain group #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif successes < repeat_min - retval = Evaluation.new( false, "object does not have contain necessary number of group #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "object does not have contain necessary number of group #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif repeat_step && ( successes - repeat_min ) % repeat_step != 0 - retval = Evaluation.new( false, "object matches (#{successes}) do not have contain repetition #{repeat_max} % #{repeat_step} of group #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "object matches (#{successes}) do not have contain repetition #{repeat_max} % #{repeat_step} of group #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") else retval = Evaluation.new( true, nil ) end @@ -155,13 +155,13 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil trace( econs, "Found #{repeat_results.length} matching members repetitions in object with min #{repeat_min} and max #{repeat_max}" ) if repeat_results.length == 0 && repeat_min > 0 - retval = Evaluation.new( false, "object does not contain #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "object does not contain #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif repeat_results.length < repeat_min - retval = Evaluation.new( false, "object does not have enough #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "object does not have enough #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif repeat_results.length > repeat_max - retval = Evaluation.new( false, "object has too many #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "object has too many #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") elsif repeat_step && ( repeat_results.length - repeat_min ) % repeat_step != 0 - retval = Evaluation.new( false, "object matches (#{repeat_results.length}) does not match repetition step of #{repeat_max} & #{repeat_step} for #{rule} for #{raised_rule(jcr,rule_atom)}") + retval = Evaluation.new( false, "object matches (#{repeat_results.length}) does not match repetition step of #{repeat_max} & #{repeat_step} for #{jcr_to_s(rule)} for #{raised_rule(jcr,rule_atom)}") else retval = Evaluation.new( true, nil) end diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index a7729ea..5d8b179 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -341,7 +341,7 @@ def self.rule_def type, jcr else s = "** unknown rule **" end - return "#{type} definition: #{s}" + return "#{type} definition << #{s} >>" end def self.trace_def econs, type, jcr, data @@ -409,7 +409,7 @@ def self.slice_to_s slice end def self.raised_rule jcr, rule_atom - " rule at #{slice_to_s(jcr)} [ #{jcr_to_s(jcr)} ] from rule at #{slice_to_s(rule_atom)}" + " rule at #{slice_to_s(jcr)} #{jcr_to_s(jcr)} from rule at #{slice_to_s(rule_atom)}" end def self.jcr_to_s( jcr, shallow=true ) @@ -430,7 +430,7 @@ def self.jcr_to_s( jcr, shallow=true ) retval = rule_to_s( jcr, shallow ) end end - return retval + return "<< " + retval + " >>" end def self.rule_to_s( rule, shallow=true) diff --git a/lib/jcr/evaluate_value_rules.rb b/lib/jcr/evaluate_value_rules.rb index c70ab90..9dff162 100644 --- a/lib/jcr/evaluate_value_rules.rb +++ b/lib/jcr/evaluate_value_rules.rb @@ -401,7 +401,7 @@ def self.evaluate_values jcr, rule_atom, data, econs end def self.bad_value jcr, rule_atom, expected, actual - Evaluation.new( false, "expected #{expected} but got #{actual} for #{raised_rule(jcr,rule_atom)}" ) + Evaluation.new( false, "expected << #{expected} >> but got << #{actual} >> for #{raised_rule(jcr,rule_atom)}" ) end def self.value_to_s( jcr, shallow=true ) diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index 16ee01f..37176db 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -158,7 +158,7 @@ def self.failure_report ctx report << " - failure at rule depth #{stack_level} caused by" end failures.each_with_index do |failure, index| - lines = breakup_message( "#{failure.json_elided} failed rule #{failure.definition} at #{failure.pos} because #{failure.evaluation.reason}", 75 ) + lines = breakup_message( "<< #{failure.json_elided} >> failed rule #{failure.definition} at #{failure.pos} because #{failure.evaluation.reason}", 75 ) lines.each_with_index do |l,i| if i == 0 report << " - #{l}" From d1f32b994e24f050eae62aa1a26360a577734641 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Fri, 15 Dec 2017 18:09:13 -0500 Subject: [PATCH 10/16] work on getting @{not} to work with target rule names --- lib/jcr/evaluate_array_rules.rb | 41 +++++--- lib/jcr/evaluate_group_rules.rb | 12 +-- lib/jcr/evaluate_member_rules.rb | 13 +-- lib/jcr/evaluate_object_rules.rb | 42 ++++---- lib/jcr/evaluate_rules.rb | 70 ++++++++----- lib/jcr/evaluate_value_rules.rb | 5 +- spec/evaluate_group_rules_spec.rb | 8 ++ spec/evaluate_member_rules_spec.rb | 32 ++++++ spec/evaluate_object_rules_spec.rb | 153 +++++++++++++++++++++++++++++ spec/evaluate_value_rules_spec.rb | 8 ++ 10 files changed, 312 insertions(+), 72 deletions(-) diff --git a/lib/jcr/evaluate_array_rules.rb b/lib/jcr/evaluate_array_rules.rb index 2780ee5..f94b572 100644 --- a/lib/jcr/evaluate_array_rules.rb +++ b/lib/jcr/evaluate_array_rules.rb @@ -45,19 +45,24 @@ def initialize( current_behavior = nil ) end end - def self.evaluate_array_rule jcr, rule_atom, data, econs, behavior = nil + def self.evaluate_array_rule jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil push_trace_stack( econs, jcr ) - trace( econs, "Evaluating array rule starting at #{slice_to_s(jcr)} against", data ) - trace_def( econs, "array", jcr, data ) - retval = evaluate_array( jcr, rule_atom, data, econs, behavior ) + if behavior + trace( econs, "Evaluating group in array rule starting at #{slice_to_s(jcr)} against", data ) + trace_def( econs, "array group", jcr, data ) + else + trace( econs, "Evaluating array rule starting at #{slice_to_s(jcr)} against", data ) + trace_def( econs, "array", jcr, data ) + end + retval = evaluate_array( jcr, rule_atom, data, econs, behavior, target_annotations ) trace_eval( econs, "Array", retval, jcr, data, "array" ) pop_trace_stack( econs ) return retval end - def self.evaluate_array jcr, rule_atom, data, econs, behavior = nil + def self.evaluate_array jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil rules, annotations = get_rules_and_annotations( jcr ) @@ -76,20 +81,24 @@ def self.evaluate_array jcr, rule_atom, data, econs, behavior = nil # if the data is not an array return evaluate_not( annotations, - Evaluation.new( false, "#{data} is not an array #{raised_rule(jcr,rule_atom)}"), econs ) unless data.is_a? Array + Evaluation.new( false, "#{data} is not an array #{raised_rule(jcr,rule_atom)}"), + econs, target_annotations ) unless data.is_a? Array # if the array is zero length and there are zero sub-rules (it is suppose to be empty) return evaluate_not( annotations, - Evaluation.new( true, nil ), econs ) if rules.empty? && data.empty? + Evaluation.new( true, nil ), econs, target_annotations ) if rules.empty? && data.empty? # if the array is not empty and there are zero sub-rules (it is suppose to be empty) return evaluate_not( annotations, - Evaluation.new( false, "Non-empty array for #{raised_rule(jcr,rule_atom)}" ), econs ) if rules.empty? && data.length != 0 + Evaluation.new( false, "Non-empty array for #{raised_rule(jcr,rule_atom)}" ), + econs, target_annotations ) if rules.empty? && data.length != 0 if ordered - return evaluate_not( annotations, evaluate_array_rule_ordered( rules, rule_atom, data, econs, behavior ), econs ) + return evaluate_not( annotations, evaluate_array_rule_ordered( rules, rule_atom, data, econs, behavior ), + econs, target_annotations ) else - return evaluate_not( annotations, evaluate_array_rule_unordered( rules, rule_atom, data, econs, behavior ), econs ) + return evaluate_not( annotations, evaluate_array_rule_unordered( rules, rule_atom, data, econs, behavior ), + econs, target_annotations ) end end @@ -115,7 +124,8 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil # groups require the effects of the evaluation to be discarded if they are false # groups must also be given the entire array - if (grule = get_group(rule, econs)) + grule, target_annotations = get_group( rule, econs ) + if grule if repeat_min == 0 retval = Evaluation.new( true, nil ) @@ -126,7 +136,7 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil else group_behavior = ArrayBehavior.new( behavior ) group_behavior.last_index = array_index - retval = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) + retval = evaluate_rule( grule, rule_atom, data, econs, group_behavior, target_annotations ) if retval.success behavior.checked_hash.merge!( group_behavior.checked_hash ) array_index = group_behavior.last_index @@ -141,7 +151,7 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil break if array_index == data.length group_behavior = ArrayBehavior.new( behavior ) group_behavior.last_index = array_index - e = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) + e = evaluate_rule( grule, rule_atom, data, econs, group_behavior, target_annotations ) if e.success behavior.checked_hash.merge!( group_behavior.checked_hash ) array_index = group_behavior.last_index @@ -218,14 +228,15 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n # groups require the effects of the evaluation to be discarded if they are false # groups must also be given the entire array - if (grule = get_group(rule, econs)) + grule,target_annotations = get_group(rule, econs) + if grule successes = 0 for i in 0..repeat_max-1 group_behavior = ArrayBehavior.new( behavior ) group_behavior.last_index = highest_index group_behavior.ordered = false - e = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) + e = evaluate_rule( grule, rule_atom, data, econs, group_behavior, target_annotations ) if e.success highest_index = group_behavior.last_index behavior.checked_hash.merge!( group_behavior.checked_hash ) diff --git a/lib/jcr/evaluate_group_rules.rb b/lib/jcr/evaluate_group_rules.rb index 72e1632..6965c61 100644 --- a/lib/jcr/evaluate_group_rules.rb +++ b/lib/jcr/evaluate_group_rules.rb @@ -26,19 +26,19 @@ module JCR - def self.evaluate_group_rule jcr, rule_atom, data, econs, behavior = nil + def self.evaluate_group_rule jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil push_trace_stack( econs, jcr ) trace( econs, "Evaluating group rule against ", data ) trace_def( econs, "group", jcr, data ) - retval = evaluate_group( jcr, rule_atom, data, econs, behavior ) + retval = evaluate_group( jcr, rule_atom, data, econs, behavior, target_annotations ) trace_eval( econs, "Group", retval, jcr, data, "group" ) pop_trace_stack( econs ) return retval end - def self.evaluate_group jcr, rule_atom, data, econs, behavior = nil + def self.evaluate_group jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil rules, annotations = get_rules_and_annotations( jcr ) @@ -46,14 +46,14 @@ def self.evaluate_group jcr, rule_atom, data, econs, behavior = nil rules.each do |rule| if rule[:choice_combiner] && retval && retval.success - return evaluate_not( annotations, retval, econs ) # short circuit + return evaluate_not( annotations, retval, econs, target_annotations ) # short circuit elsif rule[:sequence_combiner] && retval && !retval.success - return evaluate_not( annotations, retval, econs ) # short circuit + return evaluate_not( annotations, retval, econs, target_annotations ) # short circuit end retval = evaluate_rule( rule, rule_atom, data, econs, behavior ) end - return evaluate_not( annotations, retval, econs ) + return evaluate_not( annotations, retval, econs, target_annotations ) end def self.group_to_s( jcr, shallow=true) diff --git a/lib/jcr/evaluate_member_rules.rb b/lib/jcr/evaluate_member_rules.rb index 1fc7ddb..2dcc6ae 100644 --- a/lib/jcr/evaluate_member_rules.rb +++ b/lib/jcr/evaluate_member_rules.rb @@ -25,19 +25,19 @@ module JCR - def self.evaluate_member_rule jcr, rule_atom, data, econs + def self.evaluate_member_rule jcr, rule_atom, data, econs, behavior, target_annotations push_trace_stack( econs, jcr ) trace( econs, "Evaluating member rule for key '#{data[0]}' starting at #{slice_to_s(jcr)} against ", data[1]) trace_def( econs, "member", jcr, data ) - retval = evaluate_member( jcr, rule_atom, data, econs ) + retval = evaluate_member( jcr, rule_atom, data, econs, behavior, target_annotations ) trace_eval( econs, "Member", retval, jcr, data, "member" ) pop_trace_stack( econs ) return retval end - def self.evaluate_member jcr, rule_atom, data, econs + def self.evaluate_member jcr, rule_atom, data, econs, behavior, target_annotations # unlike the other evaluate functions, here data is not just the json data. # it is an array, the first element being the member name or regex and the @@ -68,13 +68,14 @@ def self.evaluate_member jcr, rule_atom, data, econs end if member_match - e = evaluate_rule( rule, rule_atom, data[ 1 ], econs ) + e = evaluate_rule( rule, rule_atom, data[ 1 ], econs, nil, target_annotations ) e.member_found = true - return evaluate_not( annotations, e, econs ) + return evaluate_not( annotations, e, econs, target_annotations ) end return evaluate_not( annotations, - Evaluation.new( false, "#{match_spec} does not match #{data[0]} for #{raised_rule( jcr, rule_atom)}" ), econs ) + Evaluation.new( false, "#{match_spec} does not match #{data[0]} for #{raised_rule( jcr, rule_atom)}" ), + econs, target_annotations ) end diff --git a/lib/jcr/evaluate_object_rules.rb b/lib/jcr/evaluate_object_rules.rb index 1416b5a..4fed250 100644 --- a/lib/jcr/evaluate_object_rules.rb +++ b/lib/jcr/evaluate_object_rules.rb @@ -27,33 +27,40 @@ def initialize end end - def self.evaluate_object_rule jcr, rule_atom, data, econs, behavior = nil + def self.evaluate_object_rule jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil push_trace_stack( econs, jcr ) - trace( econs, "Evaluating object rule starting at #{slice_to_s(jcr)} against", data ) - trace_def( econs, "object", jcr, data ) - retval = evaluate_object( jcr, rule_atom, data, econs, behavior ) + if behavior + trace( econs, "Evaluating group in object rule starting at #{slice_to_s(jcr)} against", data ) + trace_def( econs, "object group", jcr, data ) + else + trace( econs, "Evaluating object rule starting at #{slice_to_s(jcr)} against", data ) + trace_def( econs, "object", jcr, data ) + end + retval = evaluate_object( jcr, rule_atom, data, econs, behavior, target_annotations ) trace_eval( econs, "Object", retval, jcr, data, "object" ) pop_trace_stack( econs ) return retval end - def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil + def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil rules, annotations = get_rules_and_annotations( jcr ) # if the data is not an object (Hash) return evaluate_not( annotations, - Evaluation.new( false, "#{data} is not an object for #{raised_rule(jcr,rule_atom)}"), econs ) unless data.is_a? Hash + Evaluation.new( false, "#{data} is not an object for #{raised_rule(jcr,rule_atom)}"), + econs, target_annotations ) unless data.is_a? Hash # if the object has no members and there are zero sub-rules (it is suppose to be empty) return evaluate_not( annotations, - Evaluation.new( true, nil ), econs ) if rules.empty? && data.length == 0 + Evaluation.new( true, nil ), econs, target_annotations ) if rules.empty? && data.length == 0 # if the object has members and there are zero sub-rules (it is suppose to be empty) return evaluate_not( annotations, - Evaluation.new( false, "Non-empty object for #{raised_rule(jcr,rule_atom)}" ), econs ) if rules.empty? && data.length != 0 + Evaluation.new( false, "Non-empty object for #{raised_rule(jcr,rule_atom)}" ), + econs, target_annotations ) if rules.empty? && data.length != 0 retval = nil behavior = ObjectBehavior.new unless behavior @@ -64,7 +71,7 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil if rule[:choice_combiner] && retval && retval.success next elsif rule[:sequence_combiner] && retval && !retval.success - return evaluate_not( annotations, retval, econs ) # short circuit + return evaluate_not( annotations, retval, econs, target_annotations ) # short circuit end repeat_min, repeat_max, repeat_step = get_repetitions( rule, econs ) @@ -76,13 +83,14 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil # Also, groups must be handed the entire object, not key/values # as member rules use. - if (grule = get_group(rule, econs)) + grule,gtarget_annotations = get_group(rule, econs) + if grule successes = 0 for i in 0..repeat_max-1 group_behavior = ObjectBehavior.new group_behavior.checked_hash.merge!( behavior.checked_hash ) - e = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) + e = evaluate_rule( grule, rule_atom, data, econs, group_behavior, gtarget_annotations ) if e.success behavior.checked_hash.merge!( group_behavior.checked_hash ) successes = successes + 1 @@ -109,7 +117,7 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil # if defined by a name, and not a regex, just pluck it from the object # and short-circuit the enumeration - lookahead = get_leaf_rule( rule, econs ) + lookahead, ltarget_annotations = get_leaf_rule( rule, econs ) lrules, lannotations = get_rules_and_annotations( lookahead[:member_rule] ) if lrules[0][:member_name] @@ -118,13 +126,13 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil v = data[k] if v unless behavior.checked_hash[k] - e = evaluate_rule(rule, rule_atom, [k, v], econs, nil) + e = evaluate_rule(rule, rule_atom, [k, v], econs, nil, nil) behavior.checked_hash[k] = e.success repeat_results[ k ] = v if e.success end else trace( econs, "No member '#{k}' found in object.") - e = evaluate_rule(rule, rule_atom, [nil, nil], econs, nil) + e = evaluate_rule(rule, rule_atom, [nil, nil], econs, nil, nil) repeat_results[ nil ] = nil if e.success end @@ -137,7 +145,7 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil repeat_results = data.select do |k,v| unless behavior.checked_hash[k] if i < repeat_max - e = evaluate_rule(rule, rule_atom, [k, v], econs, nil) + e = evaluate_rule(rule, rule_atom, [k, v], econs, nil, nil) behavior.checked_hash[k] = e.success i = i + 1 if e.success found = true if e.member_found @@ -147,7 +155,7 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil end unless found trace( econs, "No member matching #{regex} found in object.") - e = evaluate_rule(rule, rule_atom, [nil, nil], econs, nil) + e = evaluate_rule(rule, rule_atom, [nil, nil], econs, nil, nil) repeat_results[ nil ] = nil if e.success end @@ -169,7 +177,7 @@ def self.evaluate_object jcr, rule_atom, data, econs, behavior = nil end # end if grule else - return evaluate_not( annotations, retval, econs ) + return evaluate_not( annotations, retval, econs, target_annotations ) end def self.object_to_s( jcr, shallow=true ) diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index 5d8b179..a834c71 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -91,7 +91,7 @@ def initialize data, jcr, type, evaluation, stack_level end end - def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil + def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil, target_annotations = nil if jcr.is_a?( Hash ) if jcr[:rule_name] rn = slice_to_s( jcr[:rule_name] ) @@ -102,26 +102,24 @@ def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil retval = Evaluation.new( false, "failed to evaluate rule properly" ) case when behavior.is_a?( ArrayBehavior ) - retval = evaluate_array_rule( jcr, rule_atom, data, econs, behavior) + retval = evaluate_array_rule( jcr, rule_atom, data, econs, behavior, target_annotations ) when behavior.is_a?( ObjectBehavior ) - retval = evaluate_object_rule( jcr, rule_atom, data, econs, behavior) + retval = evaluate_object_rule( jcr, rule_atom, data, econs, behavior, target_annotations ) when jcr[:rule] - retval = evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) + retval = evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior, target_annotations) when jcr[:target_rule_name] - target = econs.mapping[ jcr[:target_rule_name][:rule_name].to_s ] - raise "Target rule not in mapping. This should have been checked earlier." unless target - trace( econs, "Referencing target rule #{slice_to_s(target)} from #{slice_to_s( jcr[:target_rule_name][:rule_name] )}" ) - retval = evaluate_rule( target, target, data, econs, behavior ) + target, target_annotations = get_target_rule( jcr, econs ) + retval = evaluate_rule( target, target, data, econs, behavior, target_annotations ) when jcr[:primitive_rule] - retval = evaluate_value_rule( jcr[:primitive_rule], rule_atom, data, econs) + retval = evaluate_value_rule( jcr[:primitive_rule], rule_atom, data, econs, nil, target_annotations ) when jcr[:group_rule] - retval = evaluate_group_rule( jcr[:group_rule], rule_atom, data, econs, behavior) + retval = evaluate_group_rule( jcr[:group_rule], rule_atom, data, econs, behavior, target_annotations) when jcr[:array_rule] - retval = evaluate_array_rule( jcr[:array_rule], rule_atom, data, econs, behavior) + retval = evaluate_array_rule( jcr[:array_rule], rule_atom, data, econs, behavior, target_annotations ) when jcr[:object_rule] - retval = evaluate_object_rule( jcr[:object_rule], rule_atom, data, econs, behavior) + retval = evaluate_object_rule( jcr[:object_rule], rule_atom, data, econs, behavior, target_annotations) when jcr[:member_rule] - retval = evaluate_member_rule( jcr[:member_rule], rule_atom, data, econs) + retval = evaluate_member_rule( jcr[:member_rule], rule_atom, data, econs, nil, target_annotations) else retval = Evaluation.new( true, nil ) end @@ -153,6 +151,13 @@ def self.evaluate_callback jcr, data, econs, callback, e return retval end + def self.get_target_rule jcr, econs + target = econs.mapping[ jcr[:target_rule_name][:rule_name].to_s ] + raise "Target rule not in mapping. This should have been checked earlier." unless target + trace( econs, "Referencing target rule #{slice_to_s(target)} from #{slice_to_s( jcr[:target_rule_name][:rule_name] )}" ) + return target,jcr[:target_rule_name][:annotations] + end + def self.get_repetitions rule, econs repeat_min = 1 @@ -245,11 +250,20 @@ def self.merge_rules rules return new_rule end - def self.evaluate_not annotations, evaluation, econs + def self.evaluate_not annotations, evaluation, econs, target_annotations = nil is_not = false + + target_annotations.each do |a| + if a[:not_annotation] + trace( econs, "Not annotation found on reference to rule.") + is_not = !is_not + break + end + end if target_annotations + annotations.each do |a| if a[:not_annotation] - is_not = true + is_not = !is_not break end end @@ -262,27 +276,23 @@ def self.evaluate_not annotations, evaluation, econs end def self.get_group rule, econs - return rule[:group_rule] if rule[:group_rule] + return rule[:group_rule], nil if rule[:group_rule] #else if rule[:target_rule_name] - target = econs.mapping[ rule[:target_rule_name][:rule_name].to_s ] - raise "Target rule not in mapping. This should have been checked earlier." unless target - trace( econs, "Referencing target rule #{slice_to_s(target)} from #{slice_to_s( rule[:target_rule_name][:rule_name] )}" ) - return get_group( target, econs ) + target, target_annotations = get_target_rule( rule, econs ) + return get_group( target, econs )[0], target_annotations end #else - return false + return false, nil end def self.get_leaf_rule rule, econs if rule[:target_rule_name ] - target = econs.mapping[ rule[:target_rule_name][:rule_name].to_s ] - raise "Target rule not in mapping. This should have been checked earlier." unless target - trace( econs, "Referencing target rule #{slice_to_s(target)} from #{slice_to_s( rule[:target_rule_name][:rule_name] )}" ) - return target + target, target_annotations = get_target_rule( rule, econs ) + return target, target_annotations end #else - return rule + return rule, nil end def self.push_trace_stack econs, jcr @@ -334,8 +344,12 @@ def self.rule_def type, jcr s = elide(member_to_s(jcr)) when "object" s = elide(object_to_s(jcr)) + when "object group" + s = elide(group_to_s(jcr)) when "array" s = elide(array_to_s(jcr)) + when "array group" + s = elide(array_to_s(jcr)) when "group" s = elide(group_to_s(jcr)) else @@ -354,8 +368,12 @@ def self.trace_def econs, type, jcr, data s = elide( member_to_s( jcr ) ) when "object" s = elide( object_to_s( jcr ) ) + when "object group" + s = elide( group_to_s( jcr ) ) when "array" s = elide( array_to_s( jcr ) ) + when "array group" + s = elide( array_to_s( jcr ) ) when "group" s = elide( group_to_s( jcr ) ) else diff --git a/lib/jcr/evaluate_value_rules.rb b/lib/jcr/evaluate_value_rules.rb index 9dff162..0d916d5 100644 --- a/lib/jcr/evaluate_value_rules.rb +++ b/lib/jcr/evaluate_value_rules.rb @@ -25,14 +25,15 @@ module JCR - def self.evaluate_value_rule jcr, rule_atom, data, econs + def self.evaluate_value_rule jcr, rule_atom, data, econs, behavior, target_annotations push_trace_stack( econs, jcr ) trace( econs, "Evaluating value rule starting at #{slice_to_s(jcr)}" ) trace_def( econs, "value", jcr, data ) rules, annotations = get_rules_and_annotations( jcr ) - retval = evaluate_not( annotations, evaluate_values( rules[0], rule_atom, data, econs ), econs ) + retval = evaluate_not( annotations, evaluate_values( rules[0], rule_atom, data, econs ), + econs, target_annotations ) trace_eval( econs, "Value", retval, jcr, data, "value") pop_trace_stack( econs ) return retval diff --git a/spec/evaluate_group_rules_spec.rb b/spec/evaluate_group_rules_spec.rb index aef7e3a..1399655 100644 --- a/spec/evaluate_group_rules_spec.rb +++ b/spec/evaluate_group_rules_spec.rb @@ -34,6 +34,14 @@ expect( e.success ).to be_falsey end + it 'should fail a group with string variable refernce with {not} annotation' do + tree = JCR.parse( '$trule = @{not} $g $g=( string )' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], "a string constant", JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + it 'should pass a group with an integer or a string' do tree = JCR.parse( '$trule = ( integer | string )' ) mapping = JCR.map_rule_names( tree ) diff --git a/spec/evaluate_member_rules_spec.rb b/spec/evaluate_member_rules_spec.rb index 0d1e8b0..39c983c 100644 --- a/spec/evaluate_member_rules_spec.rb +++ b/spec/evaluate_member_rules_spec.rb @@ -138,4 +138,36 @@ expect( e.success ).to be_falsey end + it 'should pass a referenced value' do + tree = JCR.parse( '$mrule = "foo":$s $s=:"bar"' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], [ "foo", "bar" ], JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a referenced value' do + tree = JCR.parse( '$mrule = "foo":$s $s=:"bar"' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], [ "foo", "buz" ], JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a referenced @{not} value' do + tree = JCR.parse( '$mrule = "foo":@{not}$s $s=:"bar"' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], [ "foo", "buz" ], JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a referenced @{not} value' do + tree = JCR.parse( '$mrule = "foo":@{not}$s $s=:"bar"' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], [ "foo", "bar" ], JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + end diff --git a/spec/evaluate_object_rules_spec.rb b/spec/evaluate_object_rules_spec.rb index bcd01d7..f6d450b 100644 --- a/spec/evaluate_object_rules_spec.rb +++ b/spec/evaluate_object_rules_spec.rb @@ -658,4 +658,157 @@ expect( e.success ).to be_falsey end + # + # @{not}, groups, and target groups + # + + it 'should pass a @{not} with group group missing' do + tree = JCR.parse( '{ "foo":integer , @{not} ( "bar": string ) }') + # produces the following tree + # [{:object_rule=> + # [{:member_rule=> + # {:member_name=>{:q_string=>"foo"@3}, + # :primitive_rule=>{:integer_v=>"integer"@8}}}, + # {:sequence_combiner=>","@16, + # :group_rule=> + # [{:not_annotation=>"not"@20}, + # {:member_rule=> + # {:member_name=>{:q_string=>"bar"@28}, + # :primitive_rule=>{:string=>"string"@34}}}]}]}] + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a with group missing' do + tree = JCR.parse( '{ "foo":integer , ( "bar": string ) }') + # produces the following tree + # [{:object_rule=> + # [{:member_rule=> + # {:member_name=>{:q_string=>"foo"@3}, + # :primitive_rule=>{:integer_v=>"integer"@8}}}, + # {:sequence_combiner=>","@16, + # :group_rule=> + # {:member_rule=> + # {:member_name=>{:q_string=>"bar"@21}, + # :primitive_rule=>{:string=>"string"@27}}}}]}] + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a @{not} group with unknown element' do + tree = JCR.parse( '{ "foo":integer , @{not} ( "bar": string ) }') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"other"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a group missing with unknown element' do + tree = JCR.parse( '{ "foo":integer , ( "bar": string ) }') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"other"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a @{not} group with bad value' do + tree = JCR.parse( '{ "foo":integer , @{not} ( "bar": string ) }') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a group with bad value' do + tree = JCR.parse( '{ "foo":integer , ( "bar": string ) }') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should fail a @{not} group' do + tree = JCR.parse( '{ "foo":integer , @{not} ( "bar": string ) }') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>"thing"}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a group' do + tree = JCR.parse( '{ "foo":integer , @{not} ( "bar": string ) }') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>"thing"}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a @{not} with target group group missing' do + tree = JCR.parse( '{ "foo":integer , @{not} $g } $g = ("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a with target group group missing' do + tree = JCR.parse( '{ "foo":integer , $g } $g = ("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a @{not} target group with unknown element' do + tree = JCR.parse( '{ "foo":integer , @{not} $g } $g=("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"other"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a target group missing with unknown element' do + tree = JCR.parse( '{ "foo":integer , $g } $g=("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"other"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a @{not} target group with bad value' do + tree = JCR.parse( '{ "foo":integer , @{not} $g } $g=("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a target group with bad value' do + tree = JCR.parse( '{ "foo":integer , $g } $g=("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>2}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should fail a @{not} target group' do + tree = JCR.parse( '{ "foo":integer , @{not} $g } $g=("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>"thing"}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a target group' do + tree = JCR.parse( '{ "foo":integer , @{not} $g } $g=("bar":string)') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>"thing"}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + end diff --git a/spec/evaluate_value_rules_spec.rb b/spec/evaluate_value_rules_spec.rb index 3757f8e..a44a111 100644 --- a/spec/evaluate_value_rules_spec.rb +++ b/spec/evaluate_value_rules_spec.rb @@ -75,6 +75,14 @@ expect( e.success ).to be_falsey end + it 'should fail when a string matches a string constant with {not} annotation on target rule' do + tree = JCR.parse( '$trule= @{not}$c $c=:"a string constant"' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], "a string constant", JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + it 'should fail when a string does not match a string constant' do tree = JCR.parse( '$trule=: "a string constant"' ) mapping = JCR.map_rule_names( tree ) From 683a29c68903949c8e65278063b9cbad7cc8196a Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Fri, 15 Dec 2017 20:04:09 -0500 Subject: [PATCH 11/16] more work on @{not} and target rules --- lib/jcr/evaluate_array_rules.rb | 8 ++++-- lib/jcr/evaluate_object_rules.rb | 6 ++++- spec/evaluate_array_rules_spec.rb | 45 +++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/lib/jcr/evaluate_array_rules.rb b/lib/jcr/evaluate_array_rules.rb index f94b572..731f899 100644 --- a/lib/jcr/evaluate_array_rules.rb +++ b/lib/jcr/evaluate_array_rules.rb @@ -56,7 +56,11 @@ def self.evaluate_array_rule jcr, rule_atom, data, econs, behavior = nil, target trace_def( econs, "array", jcr, data ) end retval = evaluate_array( jcr, rule_atom, data, econs, behavior, target_annotations ) - trace_eval( econs, "Array", retval, jcr, data, "array" ) + if behavior + trace_eval( econs, "Array group", retval, jcr, data, "array" ) + else + trace_eval( econs, "Array", retval, jcr, data, "array" ) + end pop_trace_stack( econs ) return retval @@ -197,7 +201,7 @@ def self.evaluate_array_rule_ordered jcr, rule_atom, data, econs, behavior = nil behavior.last_index = array_index if data.length > array_index && behavior.extra_prohibited - retval = Evaluation.new( false, "More items in array than specified for #{raised_rule(jcr,rule_atom)}" ) + retval = Evaluation.new( false, "More items in array (#{data.length}) than specified (#{array_index}) for #{raised_rule(jcr,rule_atom)}" ) end return retval diff --git a/lib/jcr/evaluate_object_rules.rb b/lib/jcr/evaluate_object_rules.rb index 4fed250..e086ca3 100644 --- a/lib/jcr/evaluate_object_rules.rb +++ b/lib/jcr/evaluate_object_rules.rb @@ -38,7 +38,11 @@ def self.evaluate_object_rule jcr, rule_atom, data, econs, behavior = nil, targe trace_def( econs, "object", jcr, data ) end retval = evaluate_object( jcr, rule_atom, data, econs, behavior, target_annotations ) - trace_eval( econs, "Object", retval, jcr, data, "object" ) + if behavior + trace_eval( econs, "Object group", retval, jcr, data, "object" ) + else + trace_eval( econs, "Object", retval, jcr, data, "object" ) + end pop_trace_stack( econs ) return retval diff --git a/spec/evaluate_array_rules_spec.rb b/spec/evaluate_array_rules_spec.rb index a2538ee..c232688 100644 --- a/spec/evaluate_array_rules_spec.rb +++ b/spec/evaluate_array_rules_spec.rb @@ -819,4 +819,49 @@ expect( JCR.evaluate_rule( tree[0], tree[0], [ 2, 3 ], JCR::EvalConditions.new( mapping, nil ) ).success ).to be_truthy end + + # + # @{not}, groups, and target groups + # + + it 'should pass a missing @{not}' do + tree = JCR.parse( '[ "foo", @{not} "bar" ]') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], ["foo", "buz"], JCR::EvalConditions.new( mapping, nil, false ) ) + expect( e.success ).to be_truthy + end + + it 'should pass a @{not} with group missing' do + tree = JCR.parse( '[ "foo", @{not} ("bar") ? ]') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], ["foo"], JCR::EvalConditions.new( mapping, nil, false ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a @{not} with group' do + tree = JCR.parse( '[ "foo", @{not} ("bar") ]') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], ["foo","bar"], JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + + it 'should pass a @{not} with target group missing' do + tree = JCR.parse( '[ "foo", @{not} $g ? ] $g=("bar")') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], ["foo"], JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_truthy + end + + it 'should fail a @{not} with target group' do + tree = JCR.parse( '[ "foo", @{not} $g ] $g=("bar")') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], ["foo","bar"], JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + end From 8d98189e2e47dd9c7075334b8128b77eb278ca48 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sat, 16 Dec 2017 11:58:57 -0500 Subject: [PATCH 12/16] update to failure report to eliminate false positives --- lib/jcr/evaluate_rules.rb | 20 ++++++++++++++++---- lib/jcr/jcr.rb | 4 ++-- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index a834c71..973828d 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -68,8 +68,14 @@ def initialize mapping, callbacks, trace = false end def report_failure failure - @failures[ failure.stack_level ] = Array.new unless @failures[ failure.stack_level ] - @failures[ failure.stack_level ] << failure + coord = JCR::trace_coord( self ) + @failures[ coord ] = Array.new unless @failures[ coord ] + @failures[ coord ] << failure + end + + def report_success + coord = JCR::trace_coord( self ) + @failures.delete( coord ) end end @@ -330,11 +336,16 @@ def self.trace econs, message, data = nil message = "#{message} data: #{rule_data( data )}" end last = econs.trace_stack.last - pos = "#{last.line_and_column}@#{last.offset}" if last - puts "[ #{econs.trace_stack.length}:#{pos} ] #{message}" + puts "[ depth=#{econs.trace_stack.length}:#{trace_coord(econs)} ] #{message}" end end + def self.trace_coord econs + last = econs.trace_stack.last + pos = "#{last.line_and_column}" if last + return "pos=#{pos}" + end + def self.rule_def type, jcr s = "" case type @@ -385,6 +396,7 @@ def self.trace_def econs, type, jcr, data def self.trace_eval econs, message, evaluation, jcr, data, type if evaluation.success + econs.report_success trace( econs, "#{message} evaluation is true" ) else failure = Failure.new( data, jcr, type, evaluation, econs.trace_stack.length ) diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index 37176db..10173eb 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -153,9 +153,9 @@ def self.failure_report ctx end failed_root.failures.sort.map do |stack_level, failures| if failures.length > 1 - report << " - failure at rule depth #{stack_level} caused by one of the following #{failures.length} reasons" + report << " - failure at rule #{stack_level} caused by one of the following #{failures.length} reasons" else - report << " - failure at rule depth #{stack_level} caused by" + report << " - failure at rule #{stack_level} caused by" end failures.each_with_index do |failure, index| lines = breakup_message( "<< #{failure.json_elided} >> failed rule #{failure.definition} at #{failure.pos} because #{failure.evaluation.reason}", 75 ) From d0752da237b2cf9c4429aa9d706f4a1c1ad29e1d Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sun, 17 Dec 2017 11:34:09 -0500 Subject: [PATCH 13/16] changed runtime errors to jcrvalidator errors --- lib/jcr/check_groups.rb | 4 ++-- spec/check_groups_spec.rb | 30 +++++++++++++++--------------- spec/evaluate_object_rules_spec.rb | 8 ++++++++ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/jcr/check_groups.rb b/lib/jcr/check_groups.rb index 3f7161e..272fa66 100644 --- a/lib/jcr/check_groups.rb +++ b/lib/jcr/check_groups.rb @@ -188,9 +188,9 @@ def self.raise_group_error str, node if node.is_a?( Parslet::Slice ) pos = node.line_and_column name = node.to_str - raise "group rule error at line " + pos[0].to_s + " column " + pos[1].to_s + " name '" + name + "' :" + str + raise JCR::JcrValidatorError, "group rule error at line " + pos[0].to_s + " column " + pos[1].to_s + " name '" + name + "' :" + str else - raise "group rule error with '" + node.to_s + "' :" + str + raise JCR::JcrValidatorError, "group rule error with '" + node.to_s + "' :" + str end end diff --git a/spec/check_groups_spec.rb b/spec/check_groups_spec.rb index c1ff9d3..41c636f 100644 --- a/spec/check_groups_spec.rb +++ b/spec/check_groups_spec.rb @@ -27,7 +27,7 @@ tree = JCR.parse( '$rrule = "m1" :integer $mrule = "thing": $rrule' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should be ok with member with group of two OR values' do @@ -62,21 +62,21 @@ tree = JCR.parse( '$grule = ( ipv4 , ipv6 ) $mrule = "thing" :( integer | $grule ) ' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with group with both member-rule and type-rule inserted into type-rule' do tree = JCR.parse( '$grule = ( "m1" :ipv4 | ipv6 ) $mrule = "thing" :( integer | $grule )' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with group with member included in type-choice' do tree = JCR.parse( '$grule = ( "m1" : (ipv4 | ipv6) ) $mrule = "thing" :( integer | $grule ) ' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should be ok with member with group of value OR rulename' do @@ -121,14 +121,14 @@ tree = JCR.parse( '$grule = ( ipv4 , ipv6 ) $arule = "thing" :( integer | $grule ) ' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with value with group with member' do tree = JCR.parse( '$trule =: any $grule = ( "this" : ipv4 | "that": $trule ) $arule = "thing" :( integer | $grule ) ' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end # @@ -166,7 +166,7 @@ tree = JCR.parse( '$trule =: any $grule = ( "this" : ipv4 | "that": $trule ) $arule =: [ ( integer | $grule ) ]' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end # @@ -204,21 +204,21 @@ tree = JCR.parse( '$trule =: any $grule = ( ipv4 | $trule ) $arule =: { ( "m2" :integer | $grule ) }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with object with group of value OR rulename with member' do tree = JCR.parse( '$trule =: any $grule = ( "this" : ipv4 | "that": $trule ) $arule =: { ( "m2" :integer | "m1": $grule ) }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with object with group of value OR rulename with member 2' do tree = JCR.parse( '$trule =: any $grule = ( "thing": $trule ) $arule =: { ( "m2" :integer | "m1": $grule ) }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should be ok with object with group of value OR rulename with value' do @@ -232,35 +232,35 @@ tree = JCR.parse( '$trule =: any $grule = ( [ ipv4 ], $trule ) $arule =: { ( "m2" :integer | $grule ) }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with object with group of value OR rulename with array 2' do tree = JCR.parse( '$grule = ( [ ipv4 ] ) $arule =: { ( "m2" :integer | $grule ) }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with object with group of value OR rulename with value' do tree = JCR.parse( '$grule = ( ipv4 ) $arule = :{ ( "m2" :integer | $grule ) }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with object with group of value OR rulename with object' do tree = JCR.parse( '$grule = ( { "m1" :ipv4 } ) $arule = :{ ( "m2" :integer | $grule ) }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end it 'should error with group with both member-rule and type-rule inserted into object-rule' do tree = JCR.parse( '$grule = ( "m1" :ipv4 | ipv6 ) $mrule = { $grule }' ) mapping = JCR.map_rule_names( tree ) JCR.check_rule_target_names( tree, mapping ) - expect{ JCR.check_groups( tree, mapping ) }.to raise_error RuntimeError + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end end diff --git a/spec/evaluate_object_rules_spec.rb b/spec/evaluate_object_rules_spec.rb index f6d450b..1f4fbee 100644 --- a/spec/evaluate_object_rules_spec.rb +++ b/spec/evaluate_object_rules_spec.rb @@ -811,4 +811,12 @@ expect( e.success ).to be_falsey end + it 'should pass a target group with its own reference' do + tree = JCR.parse( '{ "foo":integer , @{not} $g } $g=($m) $m="bar":string') + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + e = JCR.evaluate_rule( tree[0], tree[0], {"foo"=>1,"bar"=>"thing"}, JCR::EvalConditions.new( mapping, nil ) ) + expect( e.success ).to be_falsey + end + end From 16c93349cf66258df6806adfe63ba9af1d271103 Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sun, 17 Dec 2017 11:59:12 -0500 Subject: [PATCH 14/16] catching some bad groups in arrays and objects --- lib/jcr/check_groups.rb | 10 +++------- spec/check_groups_spec.rb | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/jcr/check_groups.rb b/lib/jcr/check_groups.rb index 272fa66..17b970d 100644 --- a/lib/jcr/check_groups.rb +++ b/lib/jcr/check_groups.rb @@ -56,7 +56,7 @@ def self.disallowed_group_in_value? node, mapping disallowed_group_in_value?( groupee[:group_rule], mapping ) elsif groupee[:target_rule_name] trule = get_name_mapping( groupee[:target_rule_name][:rule_name], mapping ) - disallowed_group_in_value?( trule[:rule], mapping ) + disallowed_group_in_value?( trule, mapping ) elsif groupee[:member_rule] raise_group_error( "groups in value rules cannot have member rules", groupee[:member_rule] ) elsif groupee[:object_rule] @@ -132,9 +132,7 @@ def self.disallowed_group_in_array? node, mapping disallowed_group_in_array?( groupee[:group_rule], mapping ) elsif groupee[:target_rule_name] trule = get_name_mapping( groupee[:target_rule_name][:rule_name], mapping ) - if trule[:group_rule] - disallowed_group_in_array?( trule[:group_rule], mapping ) - end + disallowed_group_in_array?( trule, mapping ) elsif groupee[:member_rule] raise_group_error( "groups in array rules cannot have member rules", groupee[:member_rule] ) else @@ -169,9 +167,7 @@ def self.disallowed_group_in_object? node, mapping disallowed_group_in_object?( groupee[:group_rule], mapping ) elsif groupee[:target_rule_name] trule = get_name_mapping( groupee[:target_rule_name][:rule_name], mapping ) - if trule[:group_rule] - disallowed_group_in_object?( trule[:group_rule], mapping ) - end + disallowed_group_in_object?( trule, mapping ) elsif groupee[:array_rule] raise_group_error( "groups in object rules cannot have array rules", groupee[:member_rule] ) elsif groupee[:object_rule] diff --git a/spec/check_groups_spec.rb b/spec/check_groups_spec.rb index 41c636f..998bbbd 100644 --- a/spec/check_groups_spec.rb +++ b/spec/check_groups_spec.rb @@ -169,6 +169,13 @@ expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end + it 'should error with array with group referencing a member' do + tree = JCR.parse( '$t=[$g] $g=($m) $m="foo":string' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError + end + # # object rule tests # @@ -263,4 +270,11 @@ expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError end + it 'should error with group with both object-rule' do + tree = JCR.parse( '$t={$g} $g=($m) $m={"foo":string}' ) + mapping = JCR.map_rule_names( tree ) + JCR.check_rule_target_names( tree, mapping ) + expect{ JCR.check_groups( tree, mapping ) }.to raise_error JCR::JcrValidatorError + end + end From e36f93306c039ff74d6455babd86ec1340704fbe Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sun, 17 Dec 2017 12:14:13 -0500 Subject: [PATCH 15/16] found some conditions where check_groups causes a stack exhaustion --- lib/jcr/check_groups.rb | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/jcr/check_groups.rb b/lib/jcr/check_groups.rb index 17b970d..85093f8 100644 --- a/lib/jcr/check_groups.rb +++ b/lib/jcr/check_groups.rb @@ -56,7 +56,7 @@ def self.disallowed_group_in_value? node, mapping disallowed_group_in_value?( groupee[:group_rule], mapping ) elsif groupee[:target_rule_name] trule = get_name_mapping( groupee[:target_rule_name][:rule_name], mapping ) - disallowed_group_in_value?( trule, mapping ) + disallowed_group_in_value?( trule[:rule], mapping ) elsif groupee[:member_rule] raise_group_error( "groups in value rules cannot have member rules", groupee[:member_rule] ) elsif groupee[:object_rule] @@ -78,8 +78,6 @@ def self.check_member_for_group node, mapping disallowed_group_in_member?( trule, mapping ) elsif node[:group_rule] disallowed_group_in_member?( node[:group_rule], mapping ) - else - check_groups( node, mapping ) end end @@ -117,8 +115,6 @@ def self.check_array_for_group node, mapping disallowed_group_in_array?(trule, mapping) elsif node[:group_rule] disallowed_group_in_array?(node[:group_rule], mapping) - else - check_groups(node, mapping) end end end @@ -152,8 +148,6 @@ def self.check_object_for_group node, mapping disallowed_group_in_object?(trule, mapping) elsif node[:group_rule] disallowed_group_in_object?(node[:group_rule], mapping) - else - check_groups(node, mapping) end end end From 285c67d8691af7189b8a39a206160f90675ccc2d Mon Sep 17 00:00:00 2001 From: Andy Newton Date: Sun, 17 Dec 2017 13:29:40 -0500 Subject: [PATCH 16/16] changed version for release of 0.8.1 --- README.md | 11 +++++++---- lib/jcr/version.rb | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c9026d5..03db80f 100644 --- a/README.md +++ b/README.md @@ -128,6 +128,9 @@ which can be found [here](https://raw.githubusercontent.com/arineng/jcr/09/draft * --process-parts now creates an XML entity reference file snippet * override rules can now reference rules in the original ruleset * more readable failure report + * more readable verbose messages + * @{not} annotation on targer rules were not honored but now fixed + * better checking for groups referenced from arrays and objects The current version of the JCR specification can be found [here](https://raw.githubusercontent.com/arineng/jcr/07/draft-newton-json-content-rules.txt) @@ -186,7 +189,7 @@ Options -r FILE file containing ruleset -R STRING string containing ruleset. Should probably be quoted --test-jcr parse and test the JCR only - --process-parts creates smaller files for specification writing + --process-parts [DIRECTORY] creates smaller files for specification writing -S STRING name of root rule. All roots will be tried if none is specified -o FILE file containing overide ruleset (option can be repeated) -O STRING string containing overide rule (option can be repeated) @@ -197,11 +200,11 @@ Options Return codes: 0 = success - 1 = parsing or other bad condition - 2 = fall through bad condition + 1 = bad JCR parsing or other bad condition + 2 = invalid option or bad use of command 3 = unsuccessful evaluation of JSON -JCR Version 0.8.0 +JCR Version 0.8.1 ``` ## Usage as a Library diff --git a/lib/jcr/version.rb b/lib/jcr/version.rb index 677538e..c646ec9 100644 --- a/lib/jcr/version.rb +++ b/lib/jcr/version.rb @@ -15,6 +15,6 @@ module JCR - VERSION = "0.8.1-alpha1" + VERSION = "0.8.1" end \ No newline at end of file