lacerda 0.12.5 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +4 -0
- data/lacerda.gemspec +8 -4
- data/lib/lacerda/compare/json_schema.rb +123 -45
- data/lib/lacerda/consume_specification.rb +1 -1
- data/lib/lacerda/conversion/data_structure.rb +24 -9
- data/lib/lacerda/publish_specification.rb +5 -1
- data/lib/lacerda/version.rb +1 -1
- metadata +6 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf9cdcab9b0b20b1febda478e06f431a48250ec8
|
4
|
+
data.tar.gz: 12499578a02c7c5e57c6393d93312a3a21284e9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbcffea2beb291540f0fe39203f1e50fabbe76ba8ec1021e98b39dd335224dfd9accf4a2457fd49403b63371a99caf945ef6d5ba86c03fda3438093ff9c4dec9
|
7
|
+
data.tar.gz: f0e514af65dea7e6b16ea849ff0bfc3d1539eeb1a79fbf943142749829c01d32806fbb228adad90803c384e6737995021bb9437da60b63f274a099712aabd16c
|
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# 0.13.0
|
2
|
+
- enhance some error messages (invalid object types)
|
3
|
+
- make non required properties nullable https://github.com/moviepilot/lacerda/pull/21
|
4
|
+
|
1
5
|
# 0.12.5
|
2
6
|
- fix https://github.com/moviepilot/zeta/issues/18 (infinite loops
|
3
7
|
for some cases where json pointers are used)
|
data/lacerda.gemspec
CHANGED
@@ -21,8 +21,11 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.add_runtime_dependency "activesupport"
|
23
23
|
spec.add_runtime_dependency "rake", ["~> 10.2"]
|
24
|
-
|
25
|
-
|
24
|
+
|
25
|
+
# json-schema 2.6.0 validates differently than 2.5.1
|
26
|
+
spec.add_runtime_dependency "json-schema", ["~> 2.5.1"]
|
27
|
+
|
28
|
+
spec.add_runtime_dependency "redsnow", ["~> 0.4.3"]
|
26
29
|
spec.add_runtime_dependency "colorize"
|
27
30
|
spec.add_runtime_dependency "blumquist", ["~> 0.3"]
|
28
31
|
|
@@ -37,6 +40,7 @@ Gem::Specification.new do |spec|
|
|
37
40
|
spec.add_development_dependency "coveralls", ["~> 0.8"]
|
38
41
|
spec.add_development_dependency "codeclimate-test-reporter"
|
39
42
|
spec.add_development_dependency 'pry'
|
40
|
-
spec.add_development_dependency 'pry-
|
41
|
-
spec.add_development_dependency 'pry-
|
43
|
+
# spec.add_development_dependency 'pry-byebug'
|
44
|
+
# spec.add_development_dependency 'pry-rescue'
|
45
|
+
# spec.add_development_dependency 'pry-stack_explorer'
|
42
46
|
end
|
@@ -25,36 +25,10 @@ module Lacerda
|
|
25
25
|
properties_contained?
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
resolved_contained_property = data_for_pointer(property, @contained_schema)
|
33
|
-
containing_property = @containing_schema['properties'][property]
|
34
|
-
if !containing_property
|
35
|
-
_e(:ERR_MISSING_DEFINITION, [@initial_location, property])
|
36
|
-
else
|
37
|
-
resolved_containing_property = data_for_pointer(
|
38
|
-
containing_property,
|
39
|
-
@containing_schema
|
40
|
-
)
|
41
|
-
schema_contains?(
|
42
|
-
resolved_containing_property,
|
43
|
-
resolved_contained_property,
|
44
|
-
[property]
|
45
|
-
)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
@errors.empty?
|
49
|
-
end
|
50
|
-
|
51
|
-
def _e(error, location, extra = nil)
|
52
|
-
message = [ERRORS[error], extra].compact.join(": ")
|
53
|
-
@errors.push(error: error, message: message, location: location.compact.join("/"))
|
54
|
-
false
|
55
|
-
end
|
56
|
-
|
57
|
-
def schema_contains?(publish, consume, location = [])
|
28
|
+
def schema_contains?(options)
|
29
|
+
publish = options[:publish]
|
30
|
+
consume = options[:consume]
|
31
|
+
location = options[:location] || []
|
58
32
|
|
59
33
|
# We can only compare types and $refs, so let's make
|
60
34
|
# sure they're there
|
@@ -76,31 +50,33 @@ module Lacerda
|
|
76
50
|
# Let's go:
|
77
51
|
|
78
52
|
# 1)
|
79
|
-
if
|
80
|
-
|
81
|
-
|
53
|
+
if consume['type'] and publish['type']
|
54
|
+
consume_types = ([consume['type']].flatten - ["null"]).sort
|
55
|
+
publish_types = [publish['type']].flatten.sort
|
56
|
+
if consume_types != publish_types
|
57
|
+
return _e(:ERR_TYPE_MISMATCH, location, "Consume types #{consume_types.to_json} not compatible with publish types #{publish_types.to_json}")
|
82
58
|
end
|
83
59
|
|
84
60
|
# 2)
|
85
|
-
elsif
|
61
|
+
elsif consume['$ref'] and publish['$ref']
|
86
62
|
resolved_consume = resolve_pointer(consume['$ref'], @contained_schema)
|
87
63
|
resolved_publish = resolve_pointer(publish['$ref'], @containing_schema)
|
88
64
|
|
89
65
|
return _e(:ERR_MISSING_POINTER, location, consume['$ref']) unless resolved_consume
|
90
66
|
return _e(:ERR_MISSING_POINTER, location, publish['$ref']) unless resolved_publish
|
91
|
-
return schema_contains?(resolved_publish, resolved_consume, location)
|
67
|
+
return schema_contains?(publish: resolved_publish, consume: resolved_consume, location: location)
|
92
68
|
|
93
69
|
# 3)
|
94
|
-
elsif
|
70
|
+
elsif consume['type'] and publish['$ref']
|
95
71
|
if resolved_ref = resolve_pointer(publish['$ref'], @containing_schema)
|
96
|
-
return schema_contains?(resolved_ref, consume, location)
|
72
|
+
return schema_contains?(publish: resolved_ref, consume: consume, location: location)
|
97
73
|
else
|
98
74
|
return _e(:ERR_MISSING_POINTER, location, publish['$ref'])
|
99
75
|
end
|
100
76
|
|
101
77
|
# 4)
|
102
|
-
elsif
|
103
|
-
return _e(:ERR_NOT_SUPPORTED, location)
|
78
|
+
elsif consume['$ref'] and publish['type']
|
79
|
+
return _e(:ERR_NOT_SUPPORTED, location, nil)
|
104
80
|
end
|
105
81
|
|
106
82
|
# Make sure required properties in consume are required in publish
|
@@ -111,24 +87,126 @@ module Lacerda
|
|
111
87
|
|
112
88
|
# We already know that publish and consume's type are equal
|
113
89
|
# but if they're objects, we need to do some recursion
|
114
|
-
if consume['type']
|
115
|
-
|
116
|
-
|
117
|
-
|
90
|
+
if [consume['type']].flatten.include?('object')
|
91
|
+
|
92
|
+
# An object can either be described by its properties
|
93
|
+
# like this:
|
94
|
+
#
|
95
|
+
# (1) { "type": "object", "properties": { "active": { "type": "boolean" } }
|
96
|
+
#
|
97
|
+
# or by allowing a bunch of other types like this:
|
98
|
+
#
|
99
|
+
# (2) { "type": "object", "oneOf": [ {"$ref": "#/definitions/foo"}, {"type": "null"} ]
|
100
|
+
#
|
101
|
+
# So we need to take care of both cases for both "sides"
|
102
|
+
# (publish and consume), so 4 cases in total.
|
103
|
+
#
|
104
|
+
# First, the easy case:
|
105
|
+
if consume['properties'] and publish['properties']
|
106
|
+
consume['properties'].each do |property, schema|
|
107
|
+
return _e(:ERR_MISSING_PROPERTY, location, property) unless publish['properties'][property]
|
108
|
+
return false unless schema_contains?(publish: publish['properties'][property], consume: schema, location: location + [property])
|
109
|
+
end
|
110
|
+
|
111
|
+
# Now on to the trickier case, both have 'oneOf's:
|
112
|
+
#
|
113
|
+
# For each possible object type from the publish schema we have
|
114
|
+
# to check if we find a compatible type in the consume schema.
|
115
|
+
#
|
116
|
+
# It's not sufficient to just compare the names of the objects,
|
117
|
+
# because they might be different in the publish and consume
|
118
|
+
# schemas.
|
119
|
+
elsif publish['oneOf'] and consume['oneOf']
|
120
|
+
publish_types = publish['oneOf']
|
121
|
+
consume_types = [consume['oneOf']].flatten.compact
|
122
|
+
|
123
|
+
# Check all publish types for a compatible consume type
|
124
|
+
publish_types.each do |publish_type|
|
125
|
+
original_errors = @errors
|
126
|
+
@errors = []
|
127
|
+
compatible_consume_type_found = false
|
128
|
+
consume_types.each do |consume_type|
|
129
|
+
next unless publish_type == consume_type or # Because null types are stripped in schema_contains
|
130
|
+
schema_contains?(publish: publish_type, consume: consume_type, location: location + [publish_type])
|
131
|
+
compatible_consume_type_found = true
|
132
|
+
end
|
133
|
+
@errors = original_errors
|
134
|
+
return _e(:ERR_MISSING_MULTI_PUBLISH_MULTI_CONSUME, location, publish_type) unless compatible_consume_type_found
|
135
|
+
end
|
136
|
+
|
137
|
+
# Mixed case 1/2:
|
138
|
+
elsif consume['oneOf'] and publish['properties']
|
139
|
+
consume_types = ([consume['oneOf']].flatten - [{"type" => "null"}]).sort
|
140
|
+
compatible_consume_type_found = false
|
141
|
+
original_errors = @errors
|
142
|
+
@errors = []
|
143
|
+
consume_types.each do |consume_type|
|
144
|
+
next unless schema_contains?(publish: publish, consume: consume_type, location: location)
|
145
|
+
compatible_consume_type_found = true
|
146
|
+
end
|
147
|
+
@errors = original_errors
|
148
|
+
return _e(:ERR_MISSING_SINGLE_PUBLISH_MULTI_CONSUME, location, publish['type']) unless compatible_consume_type_found
|
149
|
+
|
150
|
+
# Mixed case 2/2:
|
151
|
+
elsif consume['properties'] and publish['oneOf']
|
152
|
+
publish_types = ([publish['oneOf']].flatten - [{"type" => "null"}]).sort
|
153
|
+
incompatible_publish_type= nil
|
154
|
+
original_errors = @errors
|
155
|
+
@errors = []
|
156
|
+
publish_types.each do |publish_type|
|
157
|
+
next if schema_contains?(publish: publish_type, consume: consume, location: location)
|
158
|
+
incompatible_publish_type = publish_type
|
159
|
+
end
|
160
|
+
@errors = original_errors
|
161
|
+
return _e(:ERR_MISSING_MULTI_PUBLISH_SINGLE_CONSUME, location, incompatible_publish_type) if incompatible_publish_type
|
162
|
+
|
163
|
+
# We don't know how to handle this 😳
|
164
|
+
# an object can either have "properties" or "oneOf", if the schema has anything else, we break
|
165
|
+
else
|
166
|
+
return _e(:ERR_NOT_SUPPORTED, location, "Consume schema didn't have properties defined and publish schema no oneOf")
|
118
167
|
end
|
119
168
|
end
|
120
169
|
|
121
170
|
if consume['type'] == 'array'
|
122
171
|
sorted_publish = publish['items'].sort
|
123
172
|
consume['items'].sort.each_with_index do |item, i|
|
124
|
-
next if schema_contains?(sorted_publish[i], item)
|
125
|
-
return _e(:ERR_ARRAY_ITEM_MISMATCH, location)
|
173
|
+
next if schema_contains?(publish: sorted_publish[i], consume: item)
|
174
|
+
return _e(:ERR_ARRAY_ITEM_MISMATCH, location, nil)
|
126
175
|
end
|
127
176
|
end
|
128
177
|
|
129
178
|
true
|
130
179
|
end
|
131
180
|
|
181
|
+
private
|
182
|
+
|
183
|
+
def properties_contained?
|
184
|
+
@contained_schema['properties'].each do |property, contained_property|
|
185
|
+
resolved_contained_property = data_for_pointer(property, @contained_schema)
|
186
|
+
containing_property = @containing_schema['properties'][property]
|
187
|
+
if !containing_property
|
188
|
+
_e(:ERR_MISSING_DEFINITION, [@initial_location, property], "(in publish.mson)")
|
189
|
+
else
|
190
|
+
resolved_containing_property = data_for_pointer(
|
191
|
+
containing_property,
|
192
|
+
@containing_schema
|
193
|
+
)
|
194
|
+
schema_contains?(
|
195
|
+
publish: resolved_containing_property,
|
196
|
+
consume: resolved_contained_property,
|
197
|
+
location: [property]
|
198
|
+
)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
@errors.empty?
|
202
|
+
end
|
203
|
+
|
204
|
+
def _e(error, location, extra = nil)
|
205
|
+
message = [ERRORS[error], extra].compact.join(": ")
|
206
|
+
@errors.push(error: error, message: message, location: location.compact.join("/"))
|
207
|
+
false
|
208
|
+
end
|
209
|
+
|
132
210
|
# Resolve pointer data idempotent(ally?). It will resolve
|
133
211
|
#
|
134
212
|
# "foobar"
|
@@ -56,7 +56,7 @@ module Lacerda
|
|
56
56
|
# or else the json validator gem will go into an endless loop
|
57
57
|
object_schema =schema_dup['definitions'].delete underscored_name.to_s
|
58
58
|
|
59
|
-
raise Lacerda::Service::InvalidObjectTypeError.new("
|
59
|
+
raise Lacerda::Service::InvalidObjectTypeError.new("Unknown object type: #{underscored_name.to_s.to_json} - did you specify it in consume.mson?") unless object_schema
|
60
60
|
|
61
61
|
# Copy the definitions of our schema into the schema for the
|
62
62
|
# object in case its properties include json pointers
|
@@ -57,16 +57,23 @@ module Lacerda
|
|
57
57
|
return unless @data['sections']
|
58
58
|
return unless @data['sections'].length > 0
|
59
59
|
members = @data['sections'].select{|d| d['class'] == 'memberType' }.first['content'].select{|d| d['class'] == 'property' }
|
60
|
+
|
61
|
+
# Iterate over each property
|
60
62
|
members.each do |s|
|
63
|
+
|
64
|
+
# Pluck some things out of the AST
|
61
65
|
content = s['content']
|
62
66
|
type_definition = content['valueDefinition']['typeDefinition']
|
63
67
|
type = type_definition['typeSpecification']['name']
|
68
|
+
attributes = type_definition['attributes'] || []
|
69
|
+
is_required = attributes.include?('required')
|
64
70
|
|
71
|
+
# Prepare the json schema fragment
|
65
72
|
spec = {}
|
66
73
|
name = Lacerda.underscore(content['name']['literal'])
|
67
74
|
|
68
75
|
# This is either type: primimtive or $ref: reference_name
|
69
|
-
spec.merge!(primitive_or_reference(type))
|
76
|
+
spec.merge!(primitive_or_reference(type, is_required))
|
70
77
|
|
71
78
|
# We might have a description
|
72
79
|
spec['description'] = content['description']
|
@@ -74,7 +81,7 @@ module Lacerda
|
|
74
81
|
# If it's an array, we need to pluck out the item types
|
75
82
|
if type == 'array'
|
76
83
|
nestedTypes = type_definition['typeSpecification']['nestedTypes']
|
77
|
-
spec['items'] = nestedTypes.map{|t| primitive_or_reference(t) }
|
84
|
+
spec['items'] = nestedTypes.map{|t| primitive_or_reference(t, is_required) }
|
78
85
|
|
79
86
|
# If it's an object, we need recursion
|
80
87
|
elsif type == 'object'
|
@@ -85,20 +92,28 @@ module Lacerda
|
|
85
92
|
end
|
86
93
|
end
|
87
94
|
|
95
|
+
# Add the specification of this property to the schema
|
88
96
|
@schema['properties'][name] = spec
|
89
|
-
|
90
|
-
|
91
|
-
|
97
|
+
|
98
|
+
# Mark the property as required
|
99
|
+
@schema['required'] << name if is_required
|
92
100
|
end
|
93
101
|
end
|
94
102
|
|
95
|
-
def primitive_or_reference(type)
|
96
|
-
|
103
|
+
def primitive_or_reference(type, is_required)
|
104
|
+
return { 'type' => 'object' } if type.blank?
|
97
105
|
|
98
106
|
if PRIMITIVES.include?(type)
|
99
|
-
|
107
|
+
types = [type]
|
108
|
+
types << 'null' unless is_required
|
109
|
+
{ 'type' => types }
|
100
110
|
else
|
101
|
-
{
|
111
|
+
types = [{'$ref' => "#/definitions/#{self.class.scope(@scope, type['literal'])}" }]
|
112
|
+
types << { 'type' => 'null' } unless is_required
|
113
|
+
{
|
114
|
+
'type' => 'object',
|
115
|
+
'oneOf' => types
|
116
|
+
}
|
102
117
|
end
|
103
118
|
end
|
104
119
|
|
@@ -32,7 +32,11 @@ module Lacerda
|
|
32
32
|
# It's critical to delete this object from the definitions
|
33
33
|
# or else the json validator gem will go into an endless loop
|
34
34
|
object_schema = schema_dup['definitions'].delete scoped_name.to_s
|
35
|
-
|
35
|
+
|
36
|
+
unless object_schema
|
37
|
+
msg = "Unknown object type: #{scoped_name.to_s.to_json} - did you specify it in publish.mson?"
|
38
|
+
raise Lacerda::Service::InvalidObjectTypeError.new(msg)
|
39
|
+
end
|
36
40
|
|
37
41
|
# Copy the definitions of our schema into the schema for the
|
38
42
|
# object in case its properties include json pointers
|
data/lib/lacerda/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lacerda
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jannis Hermanns
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -44,28 +44,28 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
47
|
+
version: 2.5.1
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 2.5.1
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: redsnow
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.4.3
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 0.4.3
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: colorize
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -220,34 +220,6 @@ dependencies:
|
|
220
220
|
- - ">="
|
221
221
|
- !ruby/object:Gem::Version
|
222
222
|
version: '0'
|
223
|
-
- !ruby/object:Gem::Dependency
|
224
|
-
name: pry-rescue
|
225
|
-
requirement: !ruby/object:Gem::Requirement
|
226
|
-
requirements:
|
227
|
-
- - ">="
|
228
|
-
- !ruby/object:Gem::Version
|
229
|
-
version: '0'
|
230
|
-
type: :development
|
231
|
-
prerelease: false
|
232
|
-
version_requirements: !ruby/object:Gem::Requirement
|
233
|
-
requirements:
|
234
|
-
- - ">="
|
235
|
-
- !ruby/object:Gem::Version
|
236
|
-
version: '0'
|
237
|
-
- !ruby/object:Gem::Dependency
|
238
|
-
name: pry-stack_explorer
|
239
|
-
requirement: !ruby/object:Gem::Requirement
|
240
|
-
requirements:
|
241
|
-
- - ">="
|
242
|
-
- !ruby/object:Gem::Version
|
243
|
-
version: '0'
|
244
|
-
type: :development
|
245
|
-
prerelease: false
|
246
|
-
version_requirements: !ruby/object:Gem::Requirement
|
247
|
-
requirements:
|
248
|
-
- - ">="
|
249
|
-
- !ruby/object:Gem::Version
|
250
|
-
version: '0'
|
251
223
|
description: Specify which objects your services publish or consume in MSON (markdown)
|
252
224
|
and let this gem validate these contracts.
|
253
225
|
email:
|