schemata-cloud_controller 0.0.1.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/schemata/cloud_controller/droplet_updated_message/droplet_updated_message_v1.rb +35 -0
- data/lib/schemata/cloud_controller/droplet_updated_message.rb +13 -0
- data/lib/schemata/cloud_controller/hm_start_request/hm_start_request_v1.rb +41 -0
- data/lib/schemata/cloud_controller/hm_start_request.rb +13 -0
- data/lib/schemata/cloud_controller/hm_stop_request/hm_stop_request_v1.rb +39 -0
- data/lib/schemata/cloud_controller/hm_stop_request.rb +13 -0
- data/lib/schemata/cloud_controller/version.rb +5 -0
- data/lib/schemata/cloud_controller.rb +19 -0
- data/lib/schemata/common/error.rb +18 -0
- data/lib/schemata/common/msgbase.rb +282 -0
- data/lib/schemata/common/msgtypebase.rb +144 -0
- data/lib/schemata/common/parsed_msg.rb +43 -0
- data/lib/schemata/helpers/hash_copy.rb +28 -0
- data/lib/schemata/helpers/stringify.rb +26 -0
- data/spec/cloud_controller/cloud_controller_spec.rb +6 -0
- data/spec/cloud_controller/droplet_updated_message_spec.rb +10 -0
- data/spec/cloud_controller/hm_start_request_spec.rb +11 -0
- data/spec/cloud_controller/hm_stop_request_spec.rb +12 -0
- data/spec/common/helpers_spec.rb +115 -0
- data/spec/common/parsed_msg_spec.rb +46 -0
- data/spec/component/aux_data_spec.rb +37 -0
- data/spec/component/component_bar_spec.rb +140 -0
- data/spec/component/component_foo_spec.rb +630 -0
- data/spec/component/foo_spec.rb +214 -0
- data/spec/component2/component2_bar_spec.rb +140 -0
- data/spec/dea/advertise_message_spec.rb +10 -0
- data/spec/dea/dea_spec.rb +6 -0
- data/spec/dea/dea_status_response_spec.rb +10 -0
- data/spec/dea/discover_request_spec.rb +10 -0
- data/spec/dea/droplet_status_response_spec.rb +10 -0
- data/spec/dea/exit_message_spec.rb +10 -0
- data/spec/dea/find_droplet_request_spec.rb +27 -0
- data/spec/dea/find_droplet_response_spec.rb +10 -0
- data/spec/dea/heartbeat_response_spec.rb +10 -0
- data/spec/dea/hello_message_spec.rb +10 -0
- data/spec/dea/start_request_spec.rb +10 -0
- data/spec/dea/stop_request_spec.rb +10 -0
- data/spec/dea/update_request_spec.rb +0 -0
- data/spec/health_manager/health_manager_spec.rb +6 -0
- data/spec/health_manager/health_request_spec.rb +12 -0
- data/spec/health_manager/health_response_spec.rb +12 -0
- data/spec/health_manager/status_crashed_response_spec.rb +12 -0
- data/spec/health_manager/status_flapping_response_spec.rb +12 -0
- data/spec/health_manager/status_request_spec.rb +12 -0
- data/spec/router/register_request_spec.rb +10 -0
- data/spec/router/router_spec.rb +6 -0
- data/spec/router/start_message_spec.rb +10 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/staging/staging_message_spec.rb +14 -0
- data/spec/staging/staging_spec.rb +6 -0
- data/spec/support/component_helpers.rb +51 -0
- data/spec/support/helpers.rb +102 -0
- data/spec/support/message_helpers.rb +135 -0
- data/spec/support/message_type_helpers.rb +171 -0
- metadata +220 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'vcap/common'
|
2
|
+
|
3
|
+
module Schemata
|
4
|
+
module CloudController
|
5
|
+
module DropletUpdatedMessage
|
6
|
+
version 1 do
|
7
|
+
include_preschemata
|
8
|
+
|
9
|
+
define_schema do
|
10
|
+
{
|
11
|
+
"droplet" => String,
|
12
|
+
optional("cc_partition") => String,
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
define_min_version 1
|
17
|
+
|
18
|
+
define_upvert do |old_data|
|
19
|
+
raise NotImplementedError.new
|
20
|
+
end
|
21
|
+
|
22
|
+
define_generate_old_fields do |msg_obj|
|
23
|
+
raise NotImplementedError.new
|
24
|
+
end
|
25
|
+
|
26
|
+
define_mock_values do
|
27
|
+
{
|
28
|
+
"droplet" => proc { VCAP.secure_uuid },
|
29
|
+
"cc_partition" => "default",
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'schemata/common/msgtypebase'
|
2
|
+
|
3
|
+
module Schemata
|
4
|
+
module CloudController
|
5
|
+
module DropletUpdatedMessage
|
6
|
+
extend Schemata::MessageTypeBase
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Dir[File.dirname(__FILE__) + '/droplet_updated_message/*.rb'].each do |file|
|
12
|
+
require file
|
13
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'vcap/common'
|
2
|
+
|
3
|
+
module Schemata
|
4
|
+
module CloudController
|
5
|
+
module HmStartRequest
|
6
|
+
version 1 do
|
7
|
+
include_preschemata
|
8
|
+
|
9
|
+
define_schema do
|
10
|
+
{
|
11
|
+
"droplet" => String,
|
12
|
+
"op" => "START",
|
13
|
+
"last_updated" => Integer,
|
14
|
+
"version" => String,
|
15
|
+
"indices" => [Integer],
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
define_min_version 1
|
20
|
+
|
21
|
+
define_upvert do |old_data|
|
22
|
+
raise NotImplementedError.new
|
23
|
+
end
|
24
|
+
|
25
|
+
define_generate_old_fields do |msg_obj|
|
26
|
+
raise NotImplementedError.new
|
27
|
+
end
|
28
|
+
|
29
|
+
define_mock_values do
|
30
|
+
{
|
31
|
+
"droplet" => proc { VCAP.secure_uuid },
|
32
|
+
"op" => "START",
|
33
|
+
"last_updated" => proc { Time.now.to_i },
|
34
|
+
"version" => proc { VCAP.secure_uuid },
|
35
|
+
"indices" => [0, 1]
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'schemata/common/msgtypebase'
|
2
|
+
|
3
|
+
module Schemata
|
4
|
+
module CloudController
|
5
|
+
module HmStartRequest
|
6
|
+
extend Schemata::MessageTypeBase
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Dir[File.dirname(__FILE__) + '/hm_start_request/*.rb'].each do |file|
|
12
|
+
require file
|
13
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'vcap/common'
|
2
|
+
|
3
|
+
module Schemata
|
4
|
+
module CloudController
|
5
|
+
module HmStopRequest
|
6
|
+
version 1 do
|
7
|
+
include_preschemata
|
8
|
+
|
9
|
+
define_schema do
|
10
|
+
{
|
11
|
+
"droplet" => String,
|
12
|
+
"op" => "STOP",
|
13
|
+
"last_updated" => Integer,
|
14
|
+
"instances" => [String]
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
define_min_version 1
|
19
|
+
|
20
|
+
define_upvert do |old_data|
|
21
|
+
raise NotImplementedError.new
|
22
|
+
end
|
23
|
+
|
24
|
+
define_generate_old_fields do |msg_obj|
|
25
|
+
raise NotImplementedError.new
|
26
|
+
end
|
27
|
+
|
28
|
+
define_mock_values do
|
29
|
+
{
|
30
|
+
"droplet" => proc { VCAP.secure_uuid },
|
31
|
+
"op" => "STOP",
|
32
|
+
"last_updated" => proc { Time.now.to_i},
|
33
|
+
"instances" => proc {[ VCAP.secure_uuid ]}
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'schemata/common/msgtypebase'
|
2
|
+
|
3
|
+
module Schemata
|
4
|
+
module CloudController
|
5
|
+
module HmStopRequest
|
6
|
+
extend Schemata::MessageTypeBase
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Dir[File.dirname(__FILE__) + '/hm_stop_request/*.rb'].each do |file|
|
12
|
+
require file
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'schemata/cloud_controller/droplet_updated_message'
|
2
|
+
require 'schemata/cloud_controller/hm_start_request'
|
3
|
+
require 'schemata/cloud_controller/hm_stop_request'
|
4
|
+
|
5
|
+
module Schemata
|
6
|
+
module CloudController
|
7
|
+
def self.mock_droplet_updated_message(version=DropletUpdatedMessage.current_version)
|
8
|
+
DropletUpdatedMessage::const_get("V#{version}").mock
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.mock_hm_start_request(version=HmStartRequest.current_version)
|
12
|
+
HmStartRequest::const_get("V#{version}").mock
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.mock_hm_stop_request(version=HmStopRequest.current_version)
|
16
|
+
HmStopRequest::const_get("V#{version}").mock
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Schemata
|
2
|
+
class DecodeError < StandardError; end
|
3
|
+
class EncodeError < StandardError; end
|
4
|
+
class SchemaDefinitionError < StandardError; end
|
5
|
+
|
6
|
+
class UpdateAttributeError < StandardError
|
7
|
+
def initialize(key, message)
|
8
|
+
super("#{key}: #{message}")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class IncompatibleVersionError < DecodeError
|
13
|
+
def initialize(msg_version, component_version)
|
14
|
+
super("min message version #{msg_version} too high for component ver\
|
15
|
+
sion #{component_version}")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'yajl'
|
2
|
+
require 'membrane'
|
3
|
+
require 'schemata/common/error'
|
4
|
+
require 'schemata/helpers/stringify'
|
5
|
+
require 'membrane'
|
6
|
+
|
7
|
+
module Schemata
|
8
|
+
module MessageBase
|
9
|
+
|
10
|
+
class ValidatingContainer
|
11
|
+
def initialize(data = {})
|
12
|
+
data ||= {}
|
13
|
+
@schema = self.class.const_get(:SCHEMA)
|
14
|
+
@contents = {}
|
15
|
+
|
16
|
+
data.each do |key, field_value|
|
17
|
+
field_schema = @schema.schemas[key]
|
18
|
+
next unless field_schema
|
19
|
+
|
20
|
+
# TODO This call to stringify should be removed when cc/dea stop using
|
21
|
+
# Symbols.
|
22
|
+
#
|
23
|
+
# Currently, some fields (for example, 'states' in requests sent
|
24
|
+
# on dea.find.droplet), are are symbols, During Yajl decoding, however,
|
25
|
+
# they become strings. Thus, on the encoding side, Schemata should expect
|
26
|
+
# symbols, but on the decoding side, it should expect strings. To allow
|
27
|
+
# for this in the schema definition, Schemata stringifies all symbols during
|
28
|
+
# construction of Schemata objects.
|
29
|
+
field_value = Schemata::HashCopyHelpers.stringify(field_value)
|
30
|
+
|
31
|
+
begin
|
32
|
+
field_schema.validate(field_value)
|
33
|
+
rescue Membrane::SchemaValidationError => e
|
34
|
+
raise Schemata::UpdateAttributeError.new(key, e.message)
|
35
|
+
end
|
36
|
+
|
37
|
+
@contents[key] = Schemata::HashCopyHelpers.deep_copy(field_value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.define(schema)
|
42
|
+
vc_klass = Class.new(self)
|
43
|
+
vc_klass.const_set(:SCHEMA, schema)
|
44
|
+
schema.schemas.each do |key, field_schema|
|
45
|
+
vc_klass.send(:define_method, key) do
|
46
|
+
unless @contents[key].nil?
|
47
|
+
return Schemata::HashCopyHelpers.deep_copy(@contents[key])
|
48
|
+
end
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# TODO This call to stringify should be removed when cc/dea stops using
|
53
|
+
# symbols. See comment above for a better description.
|
54
|
+
vc_klass.send(:define_method, "#{key}=") do |field_value|
|
55
|
+
field_value = Schemata::HashCopyHelpers.stringify(field_value)
|
56
|
+
begin
|
57
|
+
field_schema.validate(field_value)
|
58
|
+
rescue Membrane::SchemaValidationError => e
|
59
|
+
raise Schemata::UpdateAttributeError.new(key, e.message)
|
60
|
+
end
|
61
|
+
@contents[key] = Schemata::HashCopyHelpers.deep_copy(field_value)
|
62
|
+
field_value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
vc_klass
|
66
|
+
end
|
67
|
+
|
68
|
+
def contents
|
69
|
+
Schemata::HashCopyHelpers.deep_copy(@contents)
|
70
|
+
end
|
71
|
+
|
72
|
+
def empty?
|
73
|
+
@contents.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate
|
77
|
+
@schema.validate(@contents)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def vc_klass
|
82
|
+
self.class.const_get(:VC_KLASS)
|
83
|
+
end
|
84
|
+
|
85
|
+
def aux_vc_klass
|
86
|
+
return self.class.const_get(:AUX_VC_KLASS) if self.class.aux_schema
|
87
|
+
end
|
88
|
+
|
89
|
+
def initialize(msg_data_hash = nil, aux_data_hash = nil)
|
90
|
+
@contents = vc_klass.new(msg_data_hash)
|
91
|
+
if self.class.aux_schema
|
92
|
+
@aux_contents = aux_vc_klass.new(aux_data_hash)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def encode
|
97
|
+
begin
|
98
|
+
validate_contents
|
99
|
+
validate_aux_data
|
100
|
+
rescue Membrane::SchemaValidationError => e
|
101
|
+
raise Schemata::EncodeError.new(e.message)
|
102
|
+
end
|
103
|
+
|
104
|
+
msg_type = message_type
|
105
|
+
curr_version = self.class.version
|
106
|
+
min_version = self.class::MIN_VERSION_ALLOWED
|
107
|
+
|
108
|
+
msg = { "V#{curr_version}" => contents }
|
109
|
+
curr_msg_obj = self
|
110
|
+
(min_version...curr_version).reverse_each do |v|
|
111
|
+
curr_msg_obj, old_fields =
|
112
|
+
curr_msg_obj.generate_old_fields
|
113
|
+
msg["V#{v}"] = old_fields
|
114
|
+
end
|
115
|
+
msg["min_version"] = min_version
|
116
|
+
|
117
|
+
if include_preschemata?
|
118
|
+
msg["V#{curr_version}"].each do |k, v|
|
119
|
+
msg[k] = v
|
120
|
+
end
|
121
|
+
end
|
122
|
+
Yajl::Encoder.encode(msg)
|
123
|
+
end
|
124
|
+
|
125
|
+
def include_preschemata?
|
126
|
+
self.class.const_get(:INCLUDE_PRESCHEMATA)
|
127
|
+
end
|
128
|
+
|
129
|
+
def validate_contents
|
130
|
+
@contents.validate
|
131
|
+
end
|
132
|
+
|
133
|
+
def validate_aux_data
|
134
|
+
@aux_contents.validate if self.class.aux_schema
|
135
|
+
end
|
136
|
+
|
137
|
+
def contents
|
138
|
+
@contents.contents
|
139
|
+
end
|
140
|
+
|
141
|
+
def aux_data
|
142
|
+
@aux_contents
|
143
|
+
end
|
144
|
+
|
145
|
+
def message_type
|
146
|
+
_, component, msg_type, version = self.class.name.split("::")
|
147
|
+
Schemata::const_get(component)::const_get(msg_type)
|
148
|
+
end
|
149
|
+
|
150
|
+
def component
|
151
|
+
_, component, msg_type, version = self.class.name.split("::")
|
152
|
+
Schemata::const_get(component)
|
153
|
+
end
|
154
|
+
|
155
|
+
def self.included(klass)
|
156
|
+
klass.extend(Schemata::ClassMethods)
|
157
|
+
klass.extend(Dsl)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
module ClassMethods
|
162
|
+
def mock
|
163
|
+
mock = {}
|
164
|
+
mock_values.keys.each do |k|
|
165
|
+
value = mock_values[k]
|
166
|
+
mock[k] = value.respond_to?("call") ? value.call : value
|
167
|
+
end
|
168
|
+
self.new(mock)
|
169
|
+
end
|
170
|
+
|
171
|
+
def schema
|
172
|
+
self::SCHEMA
|
173
|
+
end
|
174
|
+
|
175
|
+
def aux_schema
|
176
|
+
return self::AUX_SCHEMA if defined?(self::AUX_SCHEMA)
|
177
|
+
end
|
178
|
+
|
179
|
+
def mock_values
|
180
|
+
self::MOCK_VALUES
|
181
|
+
end
|
182
|
+
|
183
|
+
def version
|
184
|
+
_, component, msg_type, version = self.name.split("::")
|
185
|
+
version[1..-1].to_i
|
186
|
+
end
|
187
|
+
|
188
|
+
def previous_version
|
189
|
+
_, component, msg_type, version = self.name.split("::")
|
190
|
+
version = version[1..-1].to_i - 1
|
191
|
+
Schemata::const_get(component)::const_get(msg_type)::
|
192
|
+
const_get("V#{version}")
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
module Dsl
|
197
|
+
def define_schema(&blk)
|
198
|
+
schema = Membrane::SchemaParser.parse(&blk)
|
199
|
+
unless schema.kind_of? Membrane::Schema::Record
|
200
|
+
Schemata::SchemaDefinitionError.new("Schema must be a hash")
|
201
|
+
end
|
202
|
+
self::const_set(:SCHEMA, schema)
|
203
|
+
end
|
204
|
+
|
205
|
+
def define_aux_schema(&blk)
|
206
|
+
aux_schema = Membrane::SchemaParser.parse(&blk)
|
207
|
+
unless aux_schema.kind_of? Membrane::Schema::Record
|
208
|
+
Schemata::SchemaDefinitionError.new("Schema must be a hash")
|
209
|
+
end
|
210
|
+
|
211
|
+
self::const_set(:AUX_SCHEMA, aux_schema)
|
212
|
+
end
|
213
|
+
|
214
|
+
def define_min_version(min_version)
|
215
|
+
unless min_version.is_a? Integer
|
216
|
+
raise SchemaDefinitionError.new("Min version must be an integer")
|
217
|
+
end
|
218
|
+
const_set(:MIN_VERSION_ALLOWED, min_version)
|
219
|
+
end
|
220
|
+
|
221
|
+
def define_upvert(&blk)
|
222
|
+
eigenclass.send(:define_method, :upvert) do |old_data|
|
223
|
+
# No need to validate aux_data because upvert is only called during
|
224
|
+
# decode, when aux_data is irrelevant
|
225
|
+
begin
|
226
|
+
previous_version::SCHEMA.validate(old_data)
|
227
|
+
rescue Membrane::SchemaValidationError => e
|
228
|
+
raise Schemata::DecodeError.new(e.message)
|
229
|
+
end
|
230
|
+
|
231
|
+
blk.call(old_data)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def define_generate_old_fields(&blk)
|
236
|
+
self.send(:define_method, :generate_old_fields) do
|
237
|
+
if self.class.aux_schema && aux_data.empty?
|
238
|
+
raise Schemata::DecodeError.new("Necessary aux_data missing")
|
239
|
+
end
|
240
|
+
old_fields = blk.call(self)
|
241
|
+
|
242
|
+
msg_contents = contents
|
243
|
+
msg_contents.update(old_fields)
|
244
|
+
msg_obj = self.class.previous_version.new(msg_contents)
|
245
|
+
|
246
|
+
msg_obj.validate_contents
|
247
|
+
return msg_obj, old_fields
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def define_mock_values(hash=nil, &blk)
|
252
|
+
if (hash && blk) || (!hash && !blk)
|
253
|
+
# value defined twice or not at all
|
254
|
+
raise SchemaDefinitionError.new("Mock values incorrectly defined")
|
255
|
+
end
|
256
|
+
|
257
|
+
hash = blk.call if blk
|
258
|
+
|
259
|
+
# Validate a sample of the mock values.
|
260
|
+
mock = {}
|
261
|
+
hash.each do |key, value|
|
262
|
+
mock[key] = value.respond_to?("call") ? value.call : value
|
263
|
+
end
|
264
|
+
|
265
|
+
begin
|
266
|
+
self.schema.validate(mock)
|
267
|
+
define_constant(:MOCK_VALUES, hash)
|
268
|
+
rescue Membrane::SchemaValidationError => e
|
269
|
+
raise SchemaDefinitionError.new("Sample mock values do not match schema: #{e}")
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def define_constant(constant_name, constant_value)
|
274
|
+
self.const_set(constant_name, constant_value)
|
275
|
+
end
|
276
|
+
|
277
|
+
def include_preschemata
|
278
|
+
define_constant(:INCLUDE_PRESCHEMATA, true)
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'schemata/common/msgbase'
|
2
|
+
require 'schemata/common/parsed_msg'
|
3
|
+
|
4
|
+
module Schemata
|
5
|
+
module MessageTypeBase
|
6
|
+
def current_version
|
7
|
+
return @current_version if @current_version
|
8
|
+
@current_version = versions.max
|
9
|
+
end
|
10
|
+
|
11
|
+
def versions
|
12
|
+
str_versions = self.constants.select { |x| x =~ /^V[0-9]+$/ }
|
13
|
+
str_versions.map { |x| x[1..-1].to_i}
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_class
|
17
|
+
self::const_get("V#{current_version}")
|
18
|
+
end
|
19
|
+
|
20
|
+
def decode(json_msg)
|
21
|
+
begin
|
22
|
+
if versions.size == 2
|
23
|
+
parsed = Schemata::ParsedMessage.new(cleanup(json_msg))
|
24
|
+
else
|
25
|
+
parsed = Schemata::ParsedMessage.new(json_msg)
|
26
|
+
end
|
27
|
+
rescue Schemata::DecodeError => e
|
28
|
+
return decode_raw_payload(json_msg) if versions.size == 1
|
29
|
+
raise e
|
30
|
+
end
|
31
|
+
message_version = parsed.version
|
32
|
+
|
33
|
+
curr_version = current_version
|
34
|
+
curr_class = current_class
|
35
|
+
|
36
|
+
if curr_version < parsed.min_version
|
37
|
+
# TODO - Perhaps we should add
|
38
|
+
# || message_version < msg_type::Current::MIN_VERSION_ALLOWED
|
39
|
+
raise IncompatibleVersionError.new(
|
40
|
+
parsed.min_version,
|
41
|
+
curr_version)
|
42
|
+
end
|
43
|
+
|
44
|
+
msg_contents = parsed.contents["V#{message_version}"]
|
45
|
+
if curr_version <= message_version
|
46
|
+
(message_version - 1).downto(curr_version) do |v|
|
47
|
+
msg_contents.update(parsed.contents["V#{v}"])
|
48
|
+
end
|
49
|
+
else
|
50
|
+
(message_version + 1).upto(curr_version) do |v|
|
51
|
+
msg_contents = const_get("V#{v}")
|
52
|
+
.upvert(msg_contents)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
msg_obj = curr_class.new(msg_contents)
|
58
|
+
msg_obj.validate_contents
|
59
|
+
# We don't validate aux data in decode.
|
60
|
+
return msg_obj
|
61
|
+
rescue Schemata::UpdateAttributeError => e
|
62
|
+
raise Schemata::DecodeError.new(e.message)
|
63
|
+
rescue Membrane::SchemaValidationError => e
|
64
|
+
raise Schemata::DecodeError.new(e.message)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def component
|
69
|
+
_, component, message_type = self.name.split("::")
|
70
|
+
Schemata::const_get(component)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.extended(o)
|
74
|
+
o.extend Dsl
|
75
|
+
end
|
76
|
+
|
77
|
+
module Dsl
|
78
|
+
def version(v, &blk)
|
79
|
+
klass = Class.new
|
80
|
+
klass.instance_eval do
|
81
|
+
def eigenclass
|
82
|
+
class << self; self; end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
klass.send(:include, Schemata::MessageBase)
|
86
|
+
klass.instance_eval(&blk)
|
87
|
+
|
88
|
+
if !defined? klass::INCLUDE_PRESCHEMATA
|
89
|
+
klass.const_set(:INCLUDE_PRESCHEMATA, false)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Create the necessary ValidatingContainer subclasses (one for schema
|
93
|
+
# and, optionally, one for aux_schema
|
94
|
+
klass.instance_eval do
|
95
|
+
vc_klass = self::ValidatingContainer.define(self.schema)
|
96
|
+
self.const_set(:VC_KLASS, vc_klass)
|
97
|
+
if self.aux_schema
|
98
|
+
aux_vc_klass = self::ValidatingContainer.define(self.aux_schema)
|
99
|
+
self.const_set(:AUX_VC_KLASS, aux_vc_klass)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Define attribute accessors for the message class
|
104
|
+
klass.schema.schemas.each do |key, field_schema|
|
105
|
+
klass.send(:define_method, key) do
|
106
|
+
@contents.send(key)
|
107
|
+
end
|
108
|
+
klass.send(:define_method, "#{key}=") do |field_value|
|
109
|
+
@contents.send("#{key}=", field_value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
self::const_set("V#{v}", klass)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def decode_raw_payload(json)
|
119
|
+
begin
|
120
|
+
msg_contents = Yajl::Parser.parse(json)
|
121
|
+
msg_obj = current_class.new(msg_contents)
|
122
|
+
msg_obj.validate_contents
|
123
|
+
return msg_obj
|
124
|
+
rescue Schemata::UpdateAttributeError,
|
125
|
+
Membrane::SchemaValidationError => e
|
126
|
+
raise Schemata::DecodeError.new(e.message)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def cleanup(json)
|
131
|
+
msg_contents = Yajl::Parser.parse(json)
|
132
|
+
clean_msg = {}
|
133
|
+
|
134
|
+
msg_contents.keys.each do |key|
|
135
|
+
if key == "min_version" || key =~ /^V[0-9]+$/
|
136
|
+
clean_msg[key] = msg_contents[key]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
Yajl::Encoder.encode(clean_msg)
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'schemata/helpers/hash_copy'
|
2
|
+
require 'set'
|
3
|
+
require 'yajl'
|
4
|
+
|
5
|
+
module Schemata
|
6
|
+
class ParsedMessage
|
7
|
+
|
8
|
+
attr_reader :version, :min_version
|
9
|
+
|
10
|
+
def initialize(json)
|
11
|
+
@contents = Yajl::Parser.parse(json)
|
12
|
+
|
13
|
+
@min_version = @contents['min_version']
|
14
|
+
if !@min_version
|
15
|
+
raise DecodeError.new("Field 'min_version' abset from message")
|
16
|
+
end
|
17
|
+
|
18
|
+
versions = []
|
19
|
+
@contents.keys.each do |k|
|
20
|
+
next if k == 'min_version'
|
21
|
+
unless k =~ /^V[0-9]+$/
|
22
|
+
raise DecodeError.new("Invalid key: #{k}")
|
23
|
+
end
|
24
|
+
versions << k[1..-1].to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
if versions.empty?
|
28
|
+
raise DecodeError.new("Message contains no versioned hashes")
|
29
|
+
end
|
30
|
+
|
31
|
+
if Set.new(versions.min..versions.max) != Set.new(versions)
|
32
|
+
raise DecodeError.new("There are versions missing between\
|
33
|
+
#{versions.min} and #{versions.max}")
|
34
|
+
end
|
35
|
+
|
36
|
+
@version = versions.max
|
37
|
+
end
|
38
|
+
|
39
|
+
def contents
|
40
|
+
Schemata::HashCopyHelpers.deep_copy(@contents)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Schemata
|
2
|
+
module HashCopyHelpers
|
3
|
+
class CopyError < StandardError; end
|
4
|
+
|
5
|
+
def self.deep_copy(node)
|
6
|
+
case node
|
7
|
+
when String
|
8
|
+
return node.dup
|
9
|
+
when Numeric, TrueClass, FalseClass
|
10
|
+
return node
|
11
|
+
when Hash
|
12
|
+
copy = {}
|
13
|
+
# XXX NB: The 'to_s' below was included because some components use
|
14
|
+
# symbols as keys instead of strings. This fix is temporary; in the
|
15
|
+
# long term, we should change all components to use the same type for
|
16
|
+
# their keys
|
17
|
+
node.each { |k, v| copy[k.to_s] = deep_copy(v) }
|
18
|
+
return copy
|
19
|
+
when Array
|
20
|
+
return node.map { |v| deep_copy(v) }
|
21
|
+
when NilClass
|
22
|
+
return nil
|
23
|
+
else
|
24
|
+
raise CopyError.new("Unexpected class: #{node.class}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|