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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eb103cbaef02d7242cb5e6fca4394709496aa9b4
4
- data.tar.gz: 133a8e88e4f034ddf4b1026355e240010a3f687c
3
+ metadata.gz: cf9cdcab9b0b20b1febda478e06f431a48250ec8
4
+ data.tar.gz: 12499578a02c7c5e57c6393d93312a3a21284e9d
5
5
  SHA512:
6
- metadata.gz: 3fa4c39961f7ffc8f39011661fbd2096e994c01e9a072e0a45c56bade9341fa90d725bd60adc41a78b986672eb0cab157fd6339b4d62f7469b590c1ed22b5ef3
7
- data.tar.gz: 31b516bbad9d48abf2e61decb50f719a1faf46da940431369ba297f50adcc345be21e0dfdadab5823593f8c157e837a29f7e07412367c8649cc60cc903fd65f0
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
- spec.add_runtime_dependency "json-schema", ["~> 2.5"]
25
- spec.add_runtime_dependency "redsnow", ["~> 0.4"]
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-rescue'
41
- spec.add_development_dependency 'pry-stack_explorer'
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
- private
29
-
30
- def properties_contained?
31
- @contained_schema['properties'].each do |property, contained_property|
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 (consume['type'] and publish['type'])
80
- if consume['type'] != publish['type']
81
- return _e(:ERR_TYPE_MISMATCH, location, "#{consume['type']} != #{publish['type']}")
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(consume['$ref'] and publish['$ref'])
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(consume['type'] and publish['$ref'])
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(consume['$ref'] and publish['type'])
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'] == 'object'
115
- consume['properties'].each do |property, schema|
116
- return _e(:ERR_MISSING_PROPERTY, location, property) unless publish['properties'][property]
117
- return false unless schema_contains?(publish['properties'][property], schema, location + [property])
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("Invalid object type: #{underscored_name.to_s.to_json}") unless object_schema
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
- if attributes = type_definition['attributes']
90
- @schema['required'] << name if attributes.include?('required')
91
- end
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
- return { 'type' => 'object' } if type.blank?
103
+ def primitive_or_reference(type, is_required)
104
+ return { 'type' => 'object' } if type.blank?
97
105
 
98
106
  if PRIMITIVES.include?(type)
99
- { 'type' => type }
107
+ types = [type]
108
+ types << 'null' unless is_required
109
+ { 'type' => types }
100
110
  else
101
- { '$ref' => "#/definitions/#{self.class.scope(@scope, type['literal'])}" }
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
- raise Lacerda::Service::InvalidObjectTypeError.new(scoped_name) unless object_schema
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
@@ -1,3 +1,3 @@
1
1
  module Lacerda
2
- VERSION = '0.12.5'
2
+ VERSION = '0.13.0'
3
3
  end
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.12.5
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: 2015-12-09 00:00:00.000000000 Z
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: '2.5'
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: '2.5'
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: '0.4'
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: '0.4'
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: