Skip to content

Commit

Permalink
Merge branch 'master' into ruby-abnf-extracter
Browse files Browse the repository at this point in the history
  • Loading branch information
Pete Cordell committed Jan 6, 2016
2 parents a03a1c9 + 763a1bb commit 9e18648
Show file tree
Hide file tree
Showing 19 changed files with 690 additions and 46 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ atlassian-ide-plugin.xml
com_crashlytics_export_strings.xml

Gemfile.lock
*.gem
158 changes: 140 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

{ "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..
}

From there, tell bundler to go get the rest of the gems:

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 ]"
Expand All @@ -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. 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.
## 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
````
2 changes: 1 addition & 1 deletion bin/jcr
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ rescue LoadError
require 'jcr'
end

JCR.main
exit JCR.main

2 changes: 0 additions & 2 deletions example_override.jcr

This file was deleted.

75 changes: 75 additions & 0 deletions examples/callback.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright (C) 2016 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 callbacks
# to perform custom validation

require 'jcr'

ruleset = <<RULESET
# ruleset-id rfcXXXX
# jcr-version 0.5
[ 2 my_integers, 2 my_strings ]
; this will be the rule we custom validate
my_integers :0..4
my_strings ( :"foo" | :"bar" )
RULESET

# Create a JCR context.
ctx = JCR::Context.new( ruleset )

# A local variable used in the callback closure
my_eval_count = 0

# The callback is created using a Proc object
c = Proc.new do |on|
is_even = false

# called if the rule evaluates to true
# jcr is the rule
# data is the data being evaluated against the rule
on.rule_eval_true do |jcr,data|
my_eval_count = my_eval_count + 1
is_even = data.to_i % 2 == 0
end

# called if the rule evaluates to false
# jcr is the rule
# data is the data being evaluated against the rule
# e is the evaluation of the rule
on.rule_eval_false do |jcr,data,e|
my_eval_count = my_eval_count + 1
is_even = data.to_i % 2 == 0
end

# return the custom validation
is_even
end

# register the callback to be called for the "my_integers" rule
ctx.callbacks[ "my_integers" ] = c

data1 = JSON.parse( '[ 2, 4, "foo", "bar" ]')
e = ctx.evaluate( data1 )
puts "Ruleset evaluation of JSON = " + e.success.to_s
puts "my_eval_count = " + my_eval_count.to_s

data2 = JSON.parse( '[ 3, 4, "foo", "bar" ]')
e = ctx.evaluate( data2 )
puts "Ruleset evaluation of JSON = " + e.success.to_s
puts "my_eval_count = " + my_eval_count.to_s
File renamed without changes.
2 changes: 2 additions & 0 deletions examples/example1_override.jcr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
my_integers : 0..2

File renamed without changes.
4 changes: 4 additions & 0 deletions examples/example1b.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
3, 4,
"bar", "foo"
]
File renamed without changes.
93 changes: 93 additions & 0 deletions examples/examples.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
#@IgnoreInspection BashAddShebang
# 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.


# 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 - this is expected"
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

# 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
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 - this is expected"
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 - this is expected"
fi

Loading

0 comments on commit 9e18648

Please sign in to comment.