From 0d9af5418012b355e9dc17b3d7ecc78909af6538 Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Thu, 31 Dec 2015 10:32:56 -0500 Subject: [PATCH 01/16] some code cleanup to centralize the calling of evaluation code. --- lib/jcr/evaluate_array_rules.rb | 6 +++--- lib/jcr/evaluate_rules.rb | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/jcr/evaluate_array_rules.rb b/lib/jcr/evaluate_array_rules.rb index 168f217..3704c63 100644 --- a/lib/jcr/evaluate_array_rules.rb +++ b/lib/jcr/evaluate_array_rules.rb @@ -114,7 +114,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_array_rule( grule, rule_atom, data, econs, group_behavior ) + retval = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) if retval.success behavior.checked_hash.merge!( group_behavior.checked_hash ) array_index = group_behavior.last_index @@ -129,7 +129,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_array_rule( grule, rule_atom, data, econs, group_behavior ) + e = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) if e.success behavior.checked_hash.merge!( group_behavior.checked_hash ) array_index = group_behavior.last_index @@ -209,7 +209,7 @@ def self.evaluate_array_rule_unordered jcr, rule_atom, data, econs, behavior = n group_behavior = ArrayBehavior.new( behavior ) group_behavior.last_index = highest_index group_behavior.ordered = false - e = evaluate_array_rule( grule, rule_atom, data, econs, group_behavior ) + e = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) if e.success highest_index = group_behavior.last_index behavior.checked_hash.merge!( group_behavior.checked_hash ) diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index 5e1f105..2bffad2 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -51,6 +51,8 @@ def initialize mapping, callbacks def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil case + when behavior.is_a?( ArrayBehavior ) + return evaluate_array_rule( jcr, rule_atom, data, econs, behavior) when jcr[:rule] return evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) when jcr[:target_rule_name] From 95567377b16c884ff602bfe13c435dee634d1353 Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Thu, 31 Dec 2015 10:36:04 -0500 Subject: [PATCH 02/16] some code cleanup to centralize the calling of evaluation code for object rules. --- lib/jcr/evaluate_object_rules.rb | 2 +- lib/jcr/evaluate_rules.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/jcr/evaluate_object_rules.rb b/lib/jcr/evaluate_object_rules.rb index 2c5b143..eeb5edc 100644 --- a/lib/jcr/evaluate_object_rules.rb +++ b/lib/jcr/evaluate_object_rules.rb @@ -69,7 +69,7 @@ def self.evaluate_object_rule jcr, rule_atom, data, econs, behavior = nil successes = 0 for i in 0..repeat_max group_behavior = ObjectBehavior.new - e = evaluate_object_rule( grule, rule_atom, data, econs, group_behavior ) + e = evaluate_rule( grule, rule_atom, data, econs, group_behavior ) if e.success behavior.checked_hash.merge!( group_behavior.checked_hash ) successes = successes + 1 diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index 2bffad2..9cd5daa 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -53,6 +53,8 @@ def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil case when behavior.is_a?( ArrayBehavior ) return evaluate_array_rule( jcr, rule_atom, data, econs, behavior) + when behavior.is_a?( ObjectBehavior ) + return evaluate_object_rule( jcr, rule_atom, data, econs, behavior) when jcr[:rule] return evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) when jcr[:target_rule_name] From 9c78fcbef478ded9f5a1b053e2cb186cd457ad2d Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Thu, 31 Dec 2015 14:50:29 -0500 Subject: [PATCH 03/16] work toward adding callbacks --- lib/jcr/evaluate_rules.rb | 41 ++++++++++++++++++++++++++++++++++++++- lib/jcr/jcr.rb | 11 +++++++++-- spec/jcr_spec.rb | 26 +++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index 9cd5daa..9bacd38 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -23,10 +23,24 @@ require 'jcr/map_rule_names' require 'jcr/check_groups' require 'jcr/evaluate_array_rules' +require 'jcr/evaluate_object_rules' require 'jcr/evaluate_group_rules' require 'jcr/evaluate_member_rules' require 'jcr/evaluate_value_rules' + +# Adapted from Matt Sears +class Proc + def jcr_callback(callable, *args) + self === Class.new do + method_name = callable.to_sym + define_method(method_name) { |&block| block.nil? ? true : block.call(*args) } + define_method("#{method_name}?") { true } + def method_missing(method_name, *args, &block) false; end + end.new + end +end + module JCR class Evaluation @@ -56,7 +70,14 @@ def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil when behavior.is_a?( ObjectBehavior ) return evaluate_object_rule( jcr, rule_atom, data, econs, behavior) when jcr[:rule] - return evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) + rn = jcr[:rule][:rule_name] + rn = rn.to_s if rn + if rn && econs.callbacks[ rn ] + e = evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) + return evaluate_callback( jcr, data, econs, rn, e ) + else + return evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) + end 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 @@ -76,6 +97,24 @@ def self.evaluate_rule jcr, rule_atom, data, econs, behavior = nil end end + def self.evaluate_callback jcr, data, econs, callback, e + retval = e + c = econs.callbacks[ callback ] + if e.success + retval = c.jcr_callback :rule_eval_true, jcr, data + else + retval = c.jcr_callback :rule_eval_fale, jcr, data, e + end + if retval.is_a? TrueClass + retval = Evaluation.new( true, nil ) + elsif retval.is_a? FalseClass + retval = Evaluation.new( false, nil ) + elsif retval.is_a? String + retval = Evaluation.new( false, retval ) + end + return retval + end + def self.get_repetitions rule repeat_min = 1 diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index cb80901..e538331 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -26,7 +26,7 @@ module JCR class Context - attr_accessor :mapping, :id, :tree, :roots, :catalog + attr_accessor :mapping, :callbacks, :id, :tree, :roots, :catalog def add_ruleset_alias( ruleset_alias, alias_uri ) unless @catalog @@ -60,6 +60,7 @@ def initialize( ruleset = nil ) if ruleset ingested = JCR.ingest_ruleset( ruleset, false, nil ) @mapping = ingested.mapping + @callbacks = ingested.callbacks @id = ingested.id @tree = ingested.tree @roots = ingested.roots @@ -72,6 +73,10 @@ def override( ruleset ) mapping.merge!( @mapping ) mapping.merge!( overridden.mapping ) overridden.mapping=mapping + callbacks = {} + callbacks.merge!( @callbacks ) + callbacks.merge!( overridden.callbacks ) + overridden.callbacks = callbacks overridden.roots.concat( @roots ) return overridden end @@ -79,6 +84,7 @@ def override( ruleset ) def override!( ruleset ) overridden = JCR.ingest_ruleset( ruleset, true, nil ) @mapping.merge!( overridden.mapping ) + @callbacks.merge!( overridden.callbacks ) @roots.concat( overridden.roots ) end @@ -92,6 +98,7 @@ def self.ingest_ruleset( ruleset, override = false, ruleset_alias=nil ) ctx = Context.new ctx.tree = tree ctx.mapping = mapping + ctx.callbacks = {} ctx.roots = roots JCR.process_directives( ctx ) return ctx @@ -113,7 +120,7 @@ def self.evaluate_ruleset( data, ctx, root_name = nil ) retval = nil root_rules.each do |r| - retval = JCR.evaluate_rule( r, r, data, EvalConditions.new( ctx.mapping, nil ) ) + retval = JCR.evaluate_rule( r, r, data, EvalConditions.new( ctx.mapping, ctx.callbacks ) ) break if retval.success end diff --git a/spec/jcr_spec.rb b/spec/jcr_spec.rb index 9b52744..cf4b07c 100644 --- a/spec/jcr_spec.rb +++ b/spec/jcr_spec.rb @@ -216,4 +216,30 @@ expect( e.success ).to be_truthy end + xit 'should callback eval_true' do + ex = < Date: Thu, 31 Dec 2015 20:21:41 -0500 Subject: [PATCH 04/16] callbacks for named rules --- lib/jcr/evaluate_rules.rb | 37 ++++++++-------- spec/jcr_spec.rb | 88 +++++++++++++++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 25 deletions(-) diff --git a/lib/jcr/evaluate_rules.rb b/lib/jcr/evaluate_rules.rb index 9bacd38..6d701c3 100644 --- a/lib/jcr/evaluate_rules.rb +++ b/lib/jcr/evaluate_rules.rb @@ -64,37 +64,38 @@ def initialize mapping, callbacks end 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 ) - return evaluate_array_rule( jcr, rule_atom, data, econs, behavior) + retval = evaluate_array_rule( jcr, rule_atom, data, econs, behavior) when behavior.is_a?( ObjectBehavior ) - return evaluate_object_rule( jcr, rule_atom, data, econs, behavior) + retval = evaluate_object_rule( jcr, rule_atom, data, econs, behavior) when jcr[:rule] - rn = jcr[:rule][:rule_name] - rn = rn.to_s if rn - if rn && econs.callbacks[ rn ] - e = evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) - return evaluate_callback( jcr, data, econs, rn, e ) - else - return evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) - end + retval = evaluate_rule( jcr[:rule], rule_atom, data, econs, behavior) 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 - return evaluate_rule( target, target, data, econs, behavior ) + retval = evaluate_rule( target, target, data, econs, behavior ) when jcr[:primitive_rule] - return evaluate_value_rule( jcr[:primitive_rule], rule_atom, data, econs) + retval = evaluate_value_rule( jcr[:primitive_rule], rule_atom, data, econs) when jcr[:group_rule] - return evaluate_group_rule( jcr[:group_rule], rule_atom, data, econs, behavior) + retval = evaluate_group_rule( jcr[:group_rule], rule_atom, data, econs, behavior) when jcr[:array_rule] - return evaluate_array_rule( jcr[:array_rule], rule_atom, data, econs, behavior) + retval = evaluate_array_rule( jcr[:array_rule], rule_atom, data, econs, behavior) when jcr[:object_rule] - return evaluate_object_rule( jcr[:object_rule], rule_atom, data, econs, behavior) + retval = evaluate_object_rule( jcr[:object_rule], rule_atom, data, econs, behavior) when jcr[:member_rule] - return evaluate_member_rule( jcr[:member_rule], rule_atom, data, econs) + retval = evaluate_member_rule( jcr[:member_rule], rule_atom, data, econs) else - return Evaluation.new( true, nil ) + retval = Evaluation.new( true, nil ) + end + if jcr.is_a?( Hash ) && jcr[:rule_name] + rn = jcr[:rule_name].to_s + if econs.callbacks[ rn ] + retval = evaluate_callback( jcr, data, econs, rn, retval ) + end end + return retval end def self.evaluate_callback jcr, data, econs, callback, e @@ -103,7 +104,7 @@ def self.evaluate_callback jcr, data, econs, callback, e if e.success retval = c.jcr_callback :rule_eval_true, jcr, data else - retval = c.jcr_callback :rule_eval_fale, jcr, data, e + retval = c.jcr_callback :rule_eval_false, jcr, data, e end if retval.is_a? TrueClass retval = Evaluation.new( true, nil ) diff --git a/spec/jcr_spec.rb b/spec/jcr_spec.rb index cf4b07c..aa82a1f 100644 --- a/spec/jcr_spec.rb +++ b/spec/jcr_spec.rb @@ -216,7 +216,32 @@ expect( e.success ).to be_truthy end - xit 'should callback eval_true' do + it 'should callback eval_true once' do + ex = < Date: Thu, 31 Dec 2015 20:47:48 -0500 Subject: [PATCH 05/16] housekeeping --- example.jcr => examples/example.jcr | 0 example.json => examples/example.json | 0 example2.jcr => examples/example2.jcr | 0 example_override.jcr => examples/example_override.jcr | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename example.jcr => examples/example.jcr (100%) rename example.json => examples/example.json (100%) rename example2.jcr => examples/example2.jcr (100%) rename example_override.jcr => examples/example_override.jcr (100%) diff --git a/example.jcr b/examples/example.jcr similarity index 100% rename from example.jcr rename to examples/example.jcr diff --git a/example.json b/examples/example.json similarity index 100% rename from example.json rename to examples/example.json diff --git a/example2.jcr b/examples/example2.jcr similarity index 100% rename from example2.jcr rename to examples/example2.jcr diff --git a/example_override.jcr b/examples/example_override.jcr similarity index 100% rename from example_override.jcr rename to examples/example_override.jcr From a86a219510b13d1bcc8ffe5f9a30cdf15b2bbe9d Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Thu, 31 Dec 2015 21:03:06 -0500 Subject: [PATCH 06/16] first gemspec --- .gitignore | 1 + jcrvalidator.gemspec | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 jcrvalidator.gemspec diff --git a/.gitignore b/.gitignore index e3857c4..f3db545 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ atlassian-ide-plugin.xml com_crashlytics_export_strings.xml Gemfile.lock +*.gem diff --git a/jcrvalidator.gemspec b/jcrvalidator.gemspec new file mode 100644 index 0000000..39d31bf --- /dev/null +++ b/jcrvalidator.gemspec @@ -0,0 +1,17 @@ +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) + +Gem::Specification.new do |s| + s.name = 'jcrvalidator' + s.version = '0.5.0' + s.date = '2015-12-31' + s.summary = "JCR Validator" + s.description = "A JSON Content Rules (JCR) Validator library and command line utility." + s.authors = ["Andrew Newton","Pete Cordell"] + s.email = 'andy@arin.net' + s.files = Dir["lib/**/*"].entries + s.homepage = + 'https://github.com/arineng/jcrvalidator' + s.license = 'ISC' + s.executables << 'jcr' +end \ No newline at end of file From dcde7a5d2241b9a234a29d8a40b9443de49f3639 Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Thu, 31 Dec 2015 21:05:13 -0500 Subject: [PATCH 07/16] simple doc clarification --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f229bd..0326754 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,6 @@ Options -h display help ``` -This code was written and tested on Ruby 2.0. At present it can only run its unit -tests. Feel free to fiddle with it. To run the unit tests, simply run the `rspec` command. +This code was written and tested on Ruby 2.0. +Feel free to fiddle with it. To run the unit tests, simply run the `rspec` command. From 5d64ee9365d8fa54a8d0e79a3d4504cc2599fa7d Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Thu, 31 Dec 2015 21:20:54 -0500 Subject: [PATCH 08/16] example simple library usage --- examples/simple.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 examples/simple.rb diff --git a/examples/simple.rb b/examples/simple.rb new file mode 100644 index 0000000..df05c1b --- /dev/null +++ b/examples/simple.rb @@ -0,0 +1,42 @@ +# Copyright (C) 2015 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 +# 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. + +# This example demonstrates using the JCR Validator with a ruleset to +# evaluate two different sets of JSON data + +require 'jcr' + +ruleset = < Date: Thu, 31 Dec 2015 21:54:21 -0500 Subject: [PATCH 10/16] shell script with examples more refined --- examples/{example.jcr => example1.jcr} | 0 examples/example1_override.jcr | 2 ++ examples/{example.json => example1a.json} | 0 examples/example1b.json | 4 ++++ examples/example_override.jcr | 2 -- examples/examples.sh | 25 ++++++++++++++++++++++- 6 files changed, 30 insertions(+), 3 deletions(-) rename examples/{example.jcr => example1.jcr} (100%) create mode 100644 examples/example1_override.jcr rename examples/{example.json => example1a.json} (100%) create mode 100644 examples/example1b.json delete mode 100644 examples/example_override.jcr diff --git a/examples/example.jcr b/examples/example1.jcr similarity index 100% rename from examples/example.jcr rename to examples/example1.jcr diff --git a/examples/example1_override.jcr b/examples/example1_override.jcr new file mode 100644 index 0000000..befe66f --- /dev/null +++ b/examples/example1_override.jcr @@ -0,0 +1,2 @@ +my_integers : 0..2 + diff --git a/examples/example.json b/examples/example1a.json similarity index 100% rename from examples/example.json rename to examples/example1a.json diff --git a/examples/example1b.json b/examples/example1b.json new file mode 100644 index 0000000..b753e74 --- /dev/null +++ b/examples/example1b.json @@ -0,0 +1,4 @@ +[ + 3, 4, + "bar", "foo" +] \ No newline at end of file diff --git a/examples/example_override.jcr b/examples/example_override.jcr deleted file mode 100644 index 07cb17a..0000000 --- a/examples/example_override.jcr +++ /dev/null @@ -1,2 +0,0 @@ -my_integers : 0..1 - diff --git a/examples/examples.sh b/examples/examples.sh index 3c924e2..9c2365c 100755 --- a/examples/examples.sh +++ b/examples/examples.sh @@ -24,4 +24,27 @@ echo "[ 1, 2]" | jcr -v -R "[ *:string ]" # Pass JSON into the JCR validator from a file with a ruleset specified in a file # This one should succeed -jcr -v -r example.jcr example.json +jcr -v -r example1.jcr example1a.json + +# Pass JSON into the JCR validator from a file with a ruleset specified in a file +# This one should succeed +jcr -v -r example1.jcr example1b.json + +# This isn't possible because the globbing concatentates the files, which is not JSON legal +# jcr -v -r example1.jcr example1*.json + +# Override a rule from the command line +# Should succeed +jcr -v -r example1.jcr -O "my_integers :0..2" example1a.json + +# Override a rule from the command line +# Should fail +jcr -v -r example1.jcr -O "my_integers :0..2" example1b.json + +# Override a rule from a file +# Should succeed +jcr -v -r example1.jcr -o example1_override.jcr example1a.json + +# Override a rule from a file +# Should fail +jcr -v -r example1.jcr -o example1_override.jcr example1b.json From b3e1b267ff31f06b0baa123032dc7eb1095c2e86 Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Thu, 31 Dec 2015 22:01:46 -0500 Subject: [PATCH 11/16] example library override --- examples/override.rb | 51 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 examples/override.rb diff --git a/examples/override.rb b/examples/override.rb new file mode 100644 index 0000000..cac4f00 --- /dev/null +++ b/examples/override.rb @@ -0,0 +1,51 @@ +# Copyright (C) 2015 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 +# 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. + +# This example demonstrates overriding rules in a ruleset with a secondary ruleset + +require 'jcr' + +ruleset = < Date: Fri, 1 Jan 2016 10:31:01 -0500 Subject: [PATCH 14/16] bug fix on return value for command line --- bin/jcr | 2 +- examples/examples.sh | 39 +++++++++++++++++++++++++++++++++++++++ jcrvalidator.gemspec | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/bin/jcr b/bin/jcr index 39e559d..5568f26 100755 --- a/bin/jcr +++ b/bin/jcr @@ -22,5 +22,5 @@ rescue LoadError require 'jcr' end -JCR.main +exit JCR.main diff --git a/examples/examples.sh b/examples/examples.sh index 9c2365c..2d4679c 100755 --- a/examples/examples.sh +++ b/examples/examples.sh @@ -16,35 +16,74 @@ # Pass JSON into the JCR validator against a ruleset given on the command line # This one should succeed +echo echo "[ 1, 2]" | jcr -v -R "[ *:integer ]" +if [ $? != 0 ]; then + echo "** Unexpected return value" +fi # Pass JSON into the JCR validator against a ruleset given on the command line # This one should fail +echo echo "[ 1, 2]" | jcr -v -R "[ *:string ]" +if [ $? != 1 ]; then + echo "** Unexpected return value" +else + echo "Failed to validate" +fi # Pass JSON into the JCR validator from a file with a ruleset specified in a file # This one should succeed +echo jcr -v -r example1.jcr example1a.json +if [ $? != 0 ]; then + echo "** Unexpected return value" +fi # Pass JSON into the JCR validator from a file with a ruleset specified in a file # This one should succeed +echo jcr -v -r example1.jcr example1b.json +if [ $? != 0 ]; then + echo "** Unexpected return value" +fi # This isn't possible because the globbing concatentates the files, which is not JSON legal # jcr -v -r example1.jcr example1*.json # Override a rule from the command line # Should succeed +echo jcr -v -r example1.jcr -O "my_integers :0..2" example1a.json +if [ $? != 0 ]; then + echo "** Unexpected return value" +fi # Override a rule from the command line # Should fail +echo jcr -v -r example1.jcr -O "my_integers :0..2" example1b.json +if [ $? != 1 ]; then + echo "** Unexpected return value" +else + echo "Failed to validate" +fi # Override a rule from a file # Should succeed +echo jcr -v -r example1.jcr -o example1_override.jcr example1a.json +if [ $? != 0 ]; then + echo "** Unexpected return value" +fi # Override a rule from a file # Should fail +echo jcr -v -r example1.jcr -o example1_override.jcr example1b.json +if [ $? != 1 ]; then + echo "** Unexpected return value" +else + echo "Failed to validate" +fi + diff --git a/jcrvalidator.gemspec b/jcrvalidator.gemspec index 39d31bf..be2755f 100644 --- a/jcrvalidator.gemspec +++ b/jcrvalidator.gemspec @@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) Gem::Specification.new do |s| s.name = 'jcrvalidator' - s.version = '0.5.0' + s.version = '0.5.1' s.date = '2015-12-31' s.summary = "JCR Validator" s.description = "A JSON Content Rules (JCR) Validator library and command line utility." From 2515935b931f291bd163c30c8b455bd98d665621 Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Fri, 1 Jan 2016 10:43:15 -0500 Subject: [PATCH 15/16] ability to parse multple files --- examples/examples.sh | 14 +++++++++----- lib/jcr/jcr.rb | 40 +++++++++++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/examples/examples.sh b/examples/examples.sh index 2d4679c..e64cf8d 100755 --- a/examples/examples.sh +++ b/examples/examples.sh @@ -29,7 +29,7 @@ echo "[ 1, 2]" | jcr -v -R "[ *:string ]" if [ $? != 1 ]; then echo "** Unexpected return value" else - echo "Failed to validate" + echo "Failed to validate - this is expected" fi # Pass JSON into the JCR validator from a file with a ruleset specified in a file @@ -48,8 +48,12 @@ if [ $? != 0 ]; then echo "** Unexpected return value" fi -# This isn't possible because the globbing concatentates the files, which is not JSON legal -# jcr -v -r example1.jcr example1*.json +# Pass multiple JSON files into the JCR validator using a ruleset specified in a file +echo +jcr -v -r example1.jcr example1*.json +if [ $? != 0 ]; then + echo "** Unexpected return value" +fi # Override a rule from the command line # Should succeed @@ -66,7 +70,7 @@ jcr -v -r example1.jcr -O "my_integers :0..2" example1b.json if [ $? != 1 ]; then echo "** Unexpected return value" else - echo "Failed to validate" + echo "Failed to validate - this is expected" fi # Override a rule from a file @@ -84,6 +88,6 @@ jcr -v -r example1.jcr -o example1_override.jcr example1b.json if [ $? != 1 ]; then echo "** Unexpected return value" else - echo "Failed to validate" + echo "Failed to validate - this is expected" fi diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index e538331..d4a5894 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -207,18 +207,40 @@ def self.main ctx.override!( ov ) end end - data = JSON.parse( ARGF.read ) - e = ctx.evaluate( data, options[:root_name] ) - if e.success - if options[:verbose] - puts "Success!" + + if $stdin.tty? + ec = 2 + ARGV.each do |fn| + data = JSON.parse( File.open( fn ).read ) + e = ctx.evaluate( data, options[:root_name] ) + if e.success + if options[:verbose] + puts "Success!" + end + ec = 0 + else + if options[:verbose] + puts "Failure: #{e.reason}" + end + ec = 1 + end end - return 0 + return ec else - if options[:verbose] - puts "Failure: #{e.reason}" + data = JSON.parse( ARGF.read ) + e = ctx.evaluate( data, options[:root_name] ) + if e.success + if options[:verbose] + puts "Success!" + end + return 0 + else + if options[:verbose] + puts "Failure: #{e.reason}" + end + return 1 end - return 1 + end end From 763a1bb577a228128709faa8ec8e27550da45b7f Mon Sep 17 00:00:00 2001 From: Andrew Newton Date: Fri, 1 Jan 2016 14:28:53 -0500 Subject: [PATCH 16/16] documentation changes. --- README.md | 158 +++++++++++++++++++++++++++++++++++++++++++------ lib/jcr/jcr.rb | 4 +- 2 files changed, 142 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 0326754..960d2d3 100644 --- a/README.md +++ b/README.md @@ -4,30 +4,113 @@ ## Background -JSON Content Rules (JCR) is a JSON-specific schema language. JCR was created by ARIN in an effort -to better describe JSON structures in RDAP. +JSON Content Rules (JCR) is a language for specifying and testing the interchange of data in JSON format used by computer protocols and processes. The syntax of JCR is not JSON but is "JSON-like", possessing the conciseness and utility that has made JSON popular. It was created by the American Registry for Internet Numbers (ARIN) in an effort to better describe the JSON structures in protocols such as RDAP. -At present, this software is ahead of the specification. -The current version of the JCR specification can be found here: -http://tools.ietf.org/html/draft-newton-json-content-rules-05 +### A First Example: Specifying Content -## Usage + The following JSON data describes a JSON object with two members, + "line-count" and "word-count", each containing an integer. -Use bundler to install all the dependencies. + { "line-count" : 3426, "word-count" : 27886 } -If you do not have bundler, it is simple to install: + This is also JCR that describes a JSON object with a member named + "line-count" that is an integer that is exactly 3426 and a member + named "word-count" that is an integer that is exactly 27886. -``` -$ gem install bundler -``` + For a protocol specification, it is probably more useful to specify + that each member is any integer and not specific, exact integers: -From there, tell bundler to go get the rest of the gems: + { "line-count" : integer, "word-count" : integer } + + Since line counts and word counts should be either zero or a positive + integer, the specification may be further narrowed: + + { "line-count" : 0.. , "word-count" : 0.. } + +### A Second Example: Testing Content + + Building on the first example, this second example describes the same + object but with the addition of another member, "file-name". + + { + "file-name" : "rfc7159.txt", + "line-count" : 3426, + "word-count" : 27886 + } + + The following JCR describes objects like it. + + { + "file-name" : string, + "line-count" : 0.., + "word-count" : 0.. + } + + + For the purposes of writing a protocol specification, JCR may be + broken down into named rules to reduce complexity and to enable re-use. The following example takes the JCR from above and rewrites the + members as named rules. + + { + fn, + lc, + wc + } + + fn "file-name" : string + lc "line-count" : 0.. + wc "word-count" : 0.. + + With each member specified as a named rule, software testers can + override them locally for specific test cases. In the following + example, the named rules are locally overridden for the test case + where the file name is "rfc4627.txt". + + fn "file-name" : "rfc4627.txt" + lc "line-count" : 2102 + wc "word-count" : 16714 + + In this example, the protocol specification describes the JSON object + in general and an implementation overrides the rules for testing + specific cases. + +### More Information on JCR + +More information on JCR can be found at [json-content-rules.org](http://json-content-rules.org/). The current specification is an IETF Internet Draft (I-D) versioned as -05. It can be found [here](http://tools.ietf.org/html/draft-newton-json-content-rules-05). + +## Version History + +* 0.5.0 - First test GEM push. +* 0.5.1 - First public beta. + * Small fix to bin/jcr to capture exit codes properly + * Small enhancement to bin/jcr to suck in multiple JSON files + * At present, this software is ahead of the specification. +The current version of the JCR specification can be found +[here](http://tools.ietf.org/html/draft-newton-json-content-rules-05) + +## Features + +This JCR Validator can be used by other Ruby code directly, or it may be invoked on the command line using the `jcr` command. + +The command line utility can be given specific rulesets to override a primary testing for the purposes of local testing. If no root rule is given, it will test against all roots. + +The library has all the features of the command line utility, and also has the ability to allow for custom validation of rules using Ruby code. + +## Installation + +To install the JCR Validator: ``` -$ bundle install +gem install jcrvalidator ``` -Now you can validate JSON against JCR. +This code was written and tested on Ruby 2.0. + +## Command Line Usage + +You can find a bunch of command line examples in `examples/examples.sh` + +Here are some quick nibbles: ``` $ echo "[ 1, 2]" | bin/jcr -v -R "[ *:integer ]" @@ -43,22 +126,61 @@ $ bin/jcr -h HELP ---- -Usage: jcr [OPTIONS] [JSON_FILE] +Usage: jcr [OPTIONS] [JSON_FILES] Evaluates JSON against JSON Content Rules (JCR). -If JSON_FILE is not specified, standard input (STDIN) is used. +If JSON_FILES is not specified, standard input (STDIN) is used. Use -v to see results, otherwise check the exit code. Options -r FILE file containing ruleset -R STRING string containing ruleset. Should probably be quoted + -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) -v verbose -h display help ``` -This code was written and tested on Ruby 2.0. -Feel free to fiddle with it. To run the unit tests, simply run the `rspec` command. +## Usage as a Library + +It is easy to call the JCR Validator from Ruby programs. The `examples` directory contains some good examples: + +* `simple.rb` is a simple and basic example +* `override.rb` shows how to override specific rules in a ruleset. +* `callback.rb` demonstrates how to do custom validation with callbacks + +### Custom Validation Using Callbacks + +The `callback.rb` demonstrates the usage of custom code for evaluation of rules. There are a few important things to note about how callbacks work: + +1. The validator will first evaluate a rule with internal validation before calling the callback code. This means child rules are evaluated by the validators own internal logic before a callback is invoked, and also that a callback for a child rule is called before the callback for its parent. +2. Depending on the internal evaluation, the callback is either invoked at the `rule_eval_true` or `rule_eval_false` methods. +3. The callback can return a `JCR::Evaluation` object to signify if the evaluation passed or not. +4. If the callback simply returns true, this is turned into a `JCR::Evaluation` signifying a passed evaluation. +5. If the callback returns false or a string, this is turned into a `JCR::Evaluation` signifying a failed evaluation. In cases where a string is returned, the string is used as the reason for failing the evaluation. +6. For validation of rules inside arrays and objects, a failed evaluation will usually result in the terminating the evaluation of the rest of the sibling rules of the containing array or object. + +## Building + +Use bundler to install all the dependencies. + +If you do not have bundler, it is simple to install: + +``` +$ gem install bundler +``` + +From there, tell bundler to go get the rest of the gems: +``` +$ bundle install +``` + +To run the unit tests: + +``` +rspec +```` diff --git a/lib/jcr/jcr.rb b/lib/jcr/jcr.rb index d4a5894..d5425a4 100644 --- a/lib/jcr/jcr.rb +++ b/lib/jcr/jcr.rb @@ -132,11 +132,11 @@ def self.main options = {} opt_parser = OptionParser.new do |opt| - opt.banner = "Usage: jcr [OPTIONS] [JSON_FILE]" + opt.banner = "Usage: jcr [OPTIONS] [JSON_FILES]" opt.separator "" opt.separator "Evaluates JSON against JSON Content Rules (JCR)." opt.separator "" - opt.separator "If JSON_FILE is not specified, standard input (STDIN) is used." + opt.separator "If JSON_FILES is not specified, standard input (STDIN) is used." opt.separator "" opt.separator "Use -v to see results, otherwise check the exit code." opt.separator ""