j2119 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []