lacerda 0.12.5 → 0.13.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.
- 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:
|