j2119 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,78 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may
4
+ # not use this file except in compliance with the License. A copy of the
5
+ # License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the LICENSE.txt file accompanying this file. This file is distributed
10
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11
+ # express or implied. See the License for the specific language governing
12
+ # permissions and limitations under the License.
13
+
14
+ module J2119
15
+
16
+ class NodeValidator
17
+
18
+ def initialize(parser)
19
+ @parser = parser
20
+ end
21
+
22
+ def validate_node(node, path, roles, problems)
23
+
24
+ if !node.is_a?(Hash)
25
+ return
26
+ end
27
+
28
+ # may have more roles based on field presence/value etc
29
+ @parser.find_more_roles(node, roles)
30
+
31
+ # constraints are attached per-role
32
+ # TODO - look through the constraints and if there is a
33
+ # "Field should not exist" constraint, then disable
34
+ # type and value checking constraints
35
+ #
36
+ roles.each do |role|
37
+ @parser.get_constraints(role).each do |constraint|
38
+ if constraint.applies(node, roles)
39
+ constraint.check(node, path, problems)
40
+ end
41
+ end
42
+ end
43
+
44
+ # for each field
45
+ node.each do |name, val|
46
+
47
+ if !@parser.field_allowed?(roles, name)
48
+ problems << "Field \"#{name}\" not allowed in #{path}"
49
+ end
50
+
51
+ # only recurse into children if they have roles
52
+ child_roles = @parser.find_child_roles(roles, name)
53
+ if !child_roles.empty?
54
+ validate_node(val, "#{path}.#{name}", child_roles, problems)
55
+ end
56
+
57
+ # find inheritance-based roles for that field
58
+ grandchild_roles = @parser.find_grandchild_roles(roles, name)
59
+
60
+ # recurse into grandkids
61
+ if val.is_a? Hash
62
+ val.each do |child_name, child_val|
63
+ validate_node(child_val, "#{path}.#{name}.#{child_name}",
64
+ grandchild_roles.clone, problems)
65
+ end
66
+ elsif val.is_a? Array
67
+ i = 0
68
+ val.each do |member|
69
+ validate_node(member, "#{path}.#{name}[#{i}]",
70
+ grandchild_roles.clone, problems)
71
+ i += 1
72
+ end
73
+
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,67 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may
4
+ # not use this file except in compliance with the License. A copy of the
5
+ # License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the LICENSE.txt file accompanying this file. This file is distributed
10
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11
+ # express or implied. See the License for the specific language governing
12
+ # permissions and limitations under the License.
13
+
14
+ module J2119
15
+
16
+ # We have to recognize lots of lists like so:
17
+ # X
18
+ # X or X
19
+ # X, X, or X
20
+ # Examples:
21
+ # one of "Foo", "Bar", or "Baz"
22
+ # a Token1, a Token2, or a Token3
23
+ class Oxford
24
+ BASIC = "(?<CAPTURE>X((((,\\s+X)+,)?)?\\s+or\\s+X)?)"
25
+
26
+ def self.re(particle, opts = {})
27
+ has_capture, inter, has_connector, last = BASIC.split 'X'
28
+ has_connector.gsub!('or', opts[:connector]) if opts[:connector]
29
+ if opts[:use_article]
30
+ particle = "an?\\s+(#{particle})"
31
+ else
32
+ particle = "(#{particle})"
33
+ end
34
+ if opts[:capture_name]
35
+ has_capture.gsub!('CAPTURE', opts[:capture_name])
36
+ else
37
+ has_capture.gsub!('?<CAPTURE>', '')
38
+ end
39
+ [ has_capture, inter, has_connector, last].join(particle)
40
+ end
41
+
42
+ def self.break_string_list list
43
+ pieces = []
44
+ re = Regexp.compile "^[^\"]*\"([^\"]*)\""
45
+ while list =~ re
46
+ pieces << $1
47
+ list = $'
48
+ end
49
+ pieces
50
+ end
51
+
52
+ def self.break_role_list(matcher, list)
53
+ pieces = []
54
+ re = Regexp.compile "^an?\\s+(#{matcher.role_matcher})(,\\s+)?"
55
+ while list =~ re
56
+ pieces << $1
57
+ list = $'
58
+ end
59
+ if list =~ /^\s*(and|or)\s+an?\s+(#{matcher.role_matcher})/
60
+ pieces << $2
61
+ end
62
+ pieces
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,101 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may
4
+ # not use this file except in compliance with the License. A copy of the
5
+ # License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the LICENSE.txt file accompanying this file. This file is distributed
10
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11
+ # express or implied. See the License for the specific language governing
12
+ # permissions and limitations under the License.
13
+
14
+ module J2119
15
+
16
+ class Parser
17
+
18
+ ROOT = Regexp.new('This\s+document\s+specifies\s+' +
19
+ 'a\s+JSON\s+object\s+called\s+an?\s+"([^"]+)"\.')
20
+
21
+ attr_reader :root
22
+
23
+ # for debugging
24
+ attr_reader :finder
25
+
26
+ def initialize(j2119_file)
27
+
28
+ have_root = false
29
+ @failed = false
30
+ @constraints = RoleConstraints.new
31
+ @finder = RoleFinder.new
32
+ @allowed_fields = AllowedFields.new
33
+
34
+ j2119_file.each_line do |line|
35
+ if line =~ ROOT
36
+ if have_root
37
+ fail "Only one root declaration"
38
+ else
39
+ @root = $1
40
+ @matcher = Matcher.new(root)
41
+ @assigner =
42
+ Assigner.new(@constraints, @finder, @matcher, @allowed_fields)
43
+ have_root = true
44
+ end
45
+ else
46
+ if !have_root
47
+ fail "Root declaration must come first"
48
+ else
49
+ proc_line(line)
50
+ end
51
+ end
52
+ end
53
+ if @failed
54
+ raise "Could not create parser"
55
+ end
56
+ end
57
+
58
+ def proc_line(line)
59
+ if @matcher.is_constraint_line(line)
60
+ @assigner.assign_constraints @matcher.build_constraint(line)
61
+ elsif @matcher.is_only_one_match_line(line)
62
+ @assigner.assign_only_one_of(@matcher.build_only_one(line))
63
+ elsif line =~ /^Each of a/
64
+ eaches_line = @matcher.eachof_match.match(line)
65
+ eaches = Oxford.break_role_list(@matcher, eaches_line['each_of'])
66
+ eaches.each do |each|
67
+ proc_line("A #{each} #{eaches_line['trailer']}")
68
+ end
69
+ elsif @matcher.is_role_def_line(line)
70
+ @assigner.assign_roles @matcher.build_role_def(line)
71
+ else
72
+ fail "Unrecognized line: #{line}"
73
+ end
74
+ end
75
+
76
+ def fail(message)
77
+ @failed = true
78
+ STDERR.puts message
79
+ end
80
+
81
+ def find_more_roles(node, roles)
82
+ @finder.find_more_roles(node, roles)
83
+ end
84
+
85
+ def find_grandchild_roles(roles, name)
86
+ @finder.find_grandchild_roles(roles, name)
87
+ end
88
+
89
+ def find_child_roles(roles, name)
90
+ @finder.find_child_roles(roles, name)
91
+ end
92
+
93
+ def get_constraints(role)
94
+ @constraints.get_constraints(role)
95
+ end
96
+
97
+ def field_allowed?(roles, child)
98
+ @allowed_fields.allowed?(roles, child)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,32 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may
4
+ # not use this file except in compliance with the License. A copy of the
5
+ # License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the LICENSE.txt file accompanying this file. This file is distributed
10
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11
+ # express or implied. See the License for the specific language governing
12
+ # permissions and limitations under the License.
13
+
14
+ module J2119
15
+
16
+ # Just a hash to remember constraints
17
+ class RoleConstraints
18
+ def initialize
19
+ @constraints = {}
20
+ end
21
+
22
+ def add(role, constraint)
23
+ @constraints[role] ||= []
24
+ @constraints[role] << constraint
25
+ end
26
+
27
+ def get_constraints(role)
28
+ @constraints[role] || []
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,143 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may
4
+ # not use this file except in compliance with the License. A copy of the
5
+ # License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the LICENSE.txt file accompanying this file. This file is distributed
10
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11
+ # express or implied. See the License for the specific language governing
12
+ # permissions and limitations under the License.
13
+
14
+ module J2119
15
+
16
+ # This is about figuring out which roles apply to a node and
17
+ # potentially to its children in object and array valued fields
18
+ #
19
+ class RoleFinder
20
+
21
+ # for debugging
22
+ attr_reader :field_value_roles
23
+
24
+ def initialize
25
+ # roles of the form: If an object with role X has field Y which
26
+ # is an object, that object has role R
27
+ @child_roles = {}
28
+
29
+ # roles of the form: If an object with role X has field Y which
30
+ # is an object/array, the object-files/array-elements have role R
31
+ @grandchild_roles = {}
32
+
33
+ # roles of the form: If an object with role X has a field Y with
34
+ # value Z, it has role R
35
+ # map[role][field_name][field_val] => child_role
36
+ @field_value_roles = {}
37
+
38
+ # roles of the form: If an object with role X has a field Y, then
39
+ # it has role R
40
+ # map[role][field_name] => child_role
41
+ @field_presence_roles = {}
42
+
43
+ # roles of the form: A Foo is a Bar
44
+ @is_a_roles = {}
45
+ end
46
+
47
+ def add_is_a_role(role, other_role)
48
+ @is_a_roles[role] ||= []
49
+ @is_a_roles[role] << other_role
50
+ end
51
+
52
+ def add_field_value_role(role, field_name, field_value, new_role)
53
+ @field_value_roles[role] ||= {}
54
+ @field_value_roles[role][field_name] ||= {}
55
+ field_value = Deduce.value(field_value)
56
+
57
+ @field_value_roles[role][field_name][field_value] = new_role
58
+ end
59
+
60
+ def add_field_presence_role(role, field_name, new_role)
61
+ @field_presence_roles[role] ||= {}
62
+ @field_presence_roles[role][field_name] = new_role
63
+ end
64
+
65
+ def add_child_role(role, field_name, child_role)
66
+ @child_roles[role] ||= {}
67
+ @child_roles[role][field_name] = child_role
68
+ end
69
+
70
+ def add_grandchild_role(role, field_name, child_role)
71
+ @grandchild_roles[role] ||= {}
72
+ @grandchild_roles[role][field_name] = child_role
73
+ end
74
+
75
+ # Consider a node which has one or more roles. It may have more
76
+ # roles based on the presence or value of child nodes. This method
77
+ # addes any such roles to the "roles" list
78
+ #
79
+ def find_more_roles(node, roles)
80
+
81
+ # find roles depending on field values
82
+ roles.each do |role|
83
+ per_field_name = @field_value_roles[role]
84
+ if per_field_name
85
+ per_field_name.each do |field_name, value_roles|
86
+ value_roles.each do |field_value, child_role|
87
+ if field_value == node[field_name]
88
+ roles << child_role
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # find roles depending on field presence
96
+ roles.each do |role|
97
+ per_field_name = @field_presence_roles[role]
98
+ if per_field_name
99
+ per_field_name.each do |field_name, child_role|
100
+ if node.key? field_name
101
+ roles << child_role
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # is_a roles
108
+ roles.each do |role|
109
+ other_roles = @is_a_roles[role]
110
+ if other_roles
111
+ other_roles.each { |o| roles << o }
112
+ end
113
+ end
114
+ end
115
+
116
+ # A node has a role, and one of its fields might be object-valued
117
+ # and that value is given a role
118
+ def find_child_roles(roles, field_name)
119
+ newroles = []
120
+ roles.each do |role|
121
+ if @child_roles[role] && @child_roles[role][field_name]
122
+ newroles << @child_roles[role][field_name]
123
+ end
124
+ end
125
+ newroles
126
+ end
127
+
128
+ # A node has a role, and one of its field is an object or an
129
+ # array whose fields or elements are given a role
130
+ #
131
+ def find_grandchild_roles(roles, field_name)
132
+ newroles = []
133
+ roles.each do |role|
134
+ if @grandchild_roles[role] && @grandchild_roles[role][field_name]
135
+ newroles << @grandchild_roles[role][field_name]
136
+ end
137
+ end
138
+ newroles
139
+ end
140
+
141
+ end
142
+
143
+ end
data/lib/j2119.rb ADDED
@@ -0,0 +1,77 @@
1
+ # Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You may
4
+ # not use this file except in compliance with the License. A copy of the
5
+ # License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the LICENSE.txt file accompanying this file. This file is distributed
10
+ # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11
+ # express or implied. See the License for the specific language governing
12
+ # permissions and limitations under the License.
13
+
14
+ require 'json'
15
+ $:.unshift("#{File.expand_path(File.dirname(__FILE__))}/../lib")
16
+ require 'j2119/assigner'
17
+ require 'j2119/conditional'
18
+ require 'j2119/constraints'
19
+ require 'j2119/deduce'
20
+ require 'j2119/matcher'
21
+ require 'j2119/node_validator'
22
+ require 'j2119/oxford'
23
+ require 'j2119/parser'
24
+ require 'j2119/role_constraints'
25
+ require 'j2119/role_finder'
26
+ require 'j2119/allowed_fields'
27
+ require 'j2119/json_path_checker'
28
+
29
+ module J2119
30
+
31
+ class Validator
32
+
33
+ attr_reader :parsed
34
+
35
+ def initialize assertions_source
36
+ assertions = File.open(assertions_source, "r")
37
+ @parser = Parser.new assertions
38
+ end
39
+
40
+ def root
41
+ @parser.root
42
+ end
43
+
44
+ def validate json_source
45
+ # already parsed?
46
+ if json_source.is_a?(Hash)
47
+ @parsed = json_source
48
+ else
49
+ if json_source.respond_to?(:read)
50
+ text = json_source.read
51
+ elsif File.readable? json_source
52
+ text = File.read json_source
53
+ else
54
+ text = json_source
55
+ end
56
+ begin
57
+ @parsed = JSON.parse text
58
+ rescue Exception => e
59
+ return [ "Problem reading/parsing JSON: #{e.to_s}" ]
60
+ end
61
+ end
62
+
63
+ problems = []
64
+ validator = NodeValidator.new(@parser)
65
+ validator.validate_node(@parsed,
66
+ @parser.root,
67
+ [ @parser.root ],
68
+ problems)
69
+ problems
70
+ end
71
+
72
+ def to_s
73
+ "J2119 validator for instances of \"#{@parser.root}\""
74
+ end
75
+ end
76
+
77
+ end
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: j2119
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tim Bray
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Validates JSON objects based on constraints in RFC2119-like language
28
+ email: timbray@amazon.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - .gitignore
34
+ - Gemfile
35
+ - LICENSE
36
+ - NOTICE.txt
37
+ - README.md
38
+ - Rakefile
39
+ - j2119.gemspec
40
+ - lib/j2119.rb
41
+ - lib/j2119/allowed_fields.rb
42
+ - lib/j2119/assigner.rb
43
+ - lib/j2119/conditional.rb
44
+ - lib/j2119/constraints.rb
45
+ - lib/j2119/deduce.rb
46
+ - lib/j2119/json_path_checker.rb
47
+ - lib/j2119/matcher.rb
48
+ - lib/j2119/node_validator.rb
49
+ - lib/j2119/oxford.rb
50
+ - lib/j2119/parser.rb
51
+ - lib/j2119/role_constraints.rb
52
+ - lib/j2119/role_finder.rb
53
+ homepage: http://rubygems.org/gems/j2119
54
+ licenses:
55
+ - Apache 2.0
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options: []
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ requirements: []
72
+ rubyforge_project:
73
+ rubygems_version: 2.0.14.1
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: JSON DSL Validator
77
+ test_files: []