ms_rest 0.2.1 → 0.3.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: edb23269524d72595eea5bf57e7296344cd013ec
4
- data.tar.gz: e9b039be71e5b171d22b2fbd07b8600e781d53ac
3
+ metadata.gz: 0a076ad1f650d77705233430c3ab8a8bff76d568
4
+ data.tar.gz: 4cc31d2b65c4e22720ec440cf401da166456f156
5
5
  SHA512:
6
- metadata.gz: ad5055678904d0874bf5edb051b3829b070884a3c18ea78491422f6441fd95609612f02b10fe03f596295c8b3d9c35779c7153d5fa901bba7d9a27a49de9ed7f
7
- data.tar.gz: 31caa6ef155fbf6427a0a27df21c9180a6be097da214aaa7fafb933d3fff4d45cefc861b15dcd884489eceaa3741387f7c418ec46bf43c5130ae8ee80d238109
6
+ metadata.gz: 02968944245aca783affdf02d4ad74daaaf43f24b2650b31fce9422de7bfcb9ce41a1db510e0f96a0d8f3e389d6b0aaadc91eed67d75c86fd56b1191fa2d6c01
7
+ data.tar.gz: 00a25077f3f286751de7b557f1d8e5bb77c8ba316f78cf25c02e349157a02444a546ee9444aa7558b47fe3bf837ae7f13530b9956d7870c93ab94d56bb7e73f5
data/ChangeLog.md ADDED
@@ -0,0 +1,2 @@
1
+ ##2016.07.14 ms_rest version 0.3.0
2
+ * Moving serialization/deserializaiton code from sdk models to client runtime. [#1106](https://github.com/Azure/autorest/pull/1106)
data/README.md CHANGED
@@ -38,7 +38,7 @@ To start working on the gem the only additional dev dependecy is required - rspe
38
38
  Reference it in the gemfile and also add this line to your client's gemspec file:
39
39
 
40
40
  ```ruby
41
- spec.add_runtime_dependency 'ms_rest', '~> 0.1.0'
41
+ spec.add_runtime_dependency 'ms_rest', '~> 0.3.0'
42
42
  ```
43
43
 
44
44
  Don't forget to correct the version.
data/lib/ms_rest.rb CHANGED
@@ -24,4 +24,5 @@ require 'ms_rest/http_operation_error'
24
24
  require 'ms_rest/retry_policy_middleware'
25
25
  require 'ms_rest/service_client'
26
26
 
27
- module MsRest; end
27
+ module MsRest end
28
+ module MsRest::Serialization end
@@ -68,8 +68,8 @@ module MsRest
68
68
  # @return [URI] body the HTTP response body.
69
69
  def run_promise(&block)
70
70
  Concurrent::Promise.new do
71
- connection = Faraday.new(:url => base_uri) do |faraday|
72
- middlewares.each{ |args| faraday.use(*args) }
71
+ @connection ||= Faraday.new(:url => base_uri) do |faraday|
72
+ middlewares.each{ |args| faraday.use(*args) } unless middlewares.nil?
73
73
  faraday.adapter Faraday.default_adapter
74
74
  logging = ENV['AZURE_HTTP_LOGGING'] || log
75
75
  if logging
@@ -77,7 +77,7 @@ module MsRest
77
77
  end
78
78
  end
79
79
 
80
- connection.run_request(:"#{method}", build_path, body, {'User-Agent' => user_agent}.merge(headers)) do |req|
80
+ @connection.run_request(:"#{method}", build_path, body, {'User-Agent' => user_agent}.merge(headers)) do |req|
81
81
  req.params = query_params.reject{|_, v| v.nil?} unless query_params.nil?
82
82
  yield(req) if block_given?
83
83
  end
@@ -3,20 +3,420 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for license information.
4
4
 
5
5
  module MsRest
6
+ # Base module for Ruby serialization and deserialization.
6
7
  #
7
- # Class which keeps the auxiliary for (de)serializing JSON requests and responses from server.
8
- #
9
- class Serialization
8
+ # Provides methods to serialize Ruby object into Ruby Hash and
9
+ # to deserialize Ruby Hash into Ruby object.
10
+ module Serialization
11
+ #
12
+ # Deserialize the response from the server using the mapper.
13
+ #
14
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the response_body.
15
+ # @param response_body [Hash] Ruby Hash object to deserialize.
16
+ # @param object_name [String] Name of the deserialized object.
17
+ #
18
+ def deserialize(mapper, response_body, object_name)
19
+ serialization = Serialization.new(self)
20
+ serialization.deserialize(mapper, response_body, object_name)
21
+ end
10
22
 
11
23
  #
12
- # Deserializes given string value into Ruby Date object.
13
- # @param [String] string_value string value to deserialize.
24
+ # Serialize the Ruby object into Ruby Hash to send it to the server using the mapper.
25
+ #
26
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the object.
27
+ # @param object [Object] Ruby object to serialize.
28
+ # @param object_name [String] Name of the serialized object.
14
29
  #
15
- # @return [Date] deserialized Date object.
16
- def self.deserialize_date(string_value)
17
- result = Timeliness.parse(string_value, :strict => true)
18
- fail DeserializationError.new('Error occured in deserializing the response', nil, nil, string_value) if result.nil?
19
- return ::Date.parse(result.to_s)
30
+ def serialize(mapper, object, object_name)
31
+ serialization = Serialization.new(self)
32
+ serialization.serialize(mapper, object, object_name)
33
+ end
34
+
35
+ #
36
+ # Class to handle serialization & deserialization.
37
+ #
38
+ class Serialization
39
+ def initialize(context)
40
+ @context = context
41
+ end
42
+
43
+ #
44
+ # Deserialize the response from the server using the mapper.
45
+ #
46
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the response_body.
47
+ # @param response_body [Hash] Ruby Hash object to deserialize.
48
+ # @param object_name [String] Name of the deserialized object.
49
+ #
50
+ def deserialize(mapper, response_body, object_name)
51
+ return response_body if response_body.nil?
52
+
53
+ object_name = mapper[:serialized_name] unless object_name.nil?
54
+ mapper_type = mapper[:type][:name]
55
+
56
+ if !mapper_type.match(/^(Number|Double|ByteArray|Boolean|Date|DateTime|DateTimeRfc1123|UnixTime|Enum|String|Object|Stream)$/i).nil?
57
+ payload = deserialize_primary_type(mapper, response_body)
58
+ elsif !mapper_type.match(/^Dictionary$/i).nil?
59
+ payload = deserialize_dictionary_type(mapper, response_body, object_name)
60
+ elsif !mapper_type.match(/^Composite$/i).nil?
61
+ payload = deserialize_composite_type(mapper, response_body, object_name)
62
+ elsif !mapper_type.match(/^Sequence$/i).nil?
63
+ payload = deserialize_sequence_type(mapper, response_body, object_name)
64
+ else
65
+ payload = ""
66
+ end
67
+
68
+ payload = mapper[:default_value] if mapper[:is_constant]
69
+
70
+ payload
71
+ end
72
+
73
+ #
74
+ # Deserialize the response of known primary type from the server using the mapper.
75
+ #
76
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the response_body.
77
+ # @param response_body [Hash] Ruby Hash object to deserialize.
78
+ #
79
+ def deserialize_primary_type(mapper, response_body)
80
+ result = ""
81
+ case mapper[:type][:name]
82
+ when 'Number'
83
+ result = Integer(response_body) unless response_body.to_s.empty?
84
+ when 'Double'
85
+ result = Float(response_body) unless response_body.to_s.empty?
86
+ when 'ByteArray'
87
+ result = Base64.strict_decode64(response_body).unpack('C*') unless response_body.to_s.empty?
88
+ when 'String', 'Boolean', 'Object', 'Stream'
89
+ result = response_body
90
+ when 'Enum'
91
+ unless response_body.nil? || response_body.empty?
92
+ unless enum_is_valid(mapper, response_body)
93
+ warn "Enum #{model} does not contain #{response_body.downcase}, but was received from the server."
94
+ end
95
+ end
96
+ result = response_body
97
+ when 'Date'
98
+ unless response_body.to_s.empty?
99
+ result = Timeliness.parse(response_body, :strict => true)
100
+ fail DeserializationError.new('Error occured in deserializing the response_body', nil, nil, response_body) if result.nil?
101
+ result = ::Date.parse(result.to_s)
102
+ end
103
+ when 'DateTime', 'DateTimeRfc1123'
104
+ result = DateTime.parse(response_body) unless response_body.to_s.empty?
105
+ when 'UnixTime'
106
+ result = DateTime.strptime(response_body.to_s, '%s') unless response_body.to_s.empty?
107
+ else
108
+ result
109
+ end
110
+ result
111
+ end
112
+
113
+ #
114
+ # Deserialize the response of dictionary type from the server using the mapper.
115
+ #
116
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the response_body.
117
+ # @param response_body [Hash] Ruby Hash object to deserialize.
118
+ # @param object_name [String] Name of the deserialized object.
119
+ #
120
+ def deserialize_dictionary_type(mapper, response_body, object_name)
121
+ if mapper[:type][:value].nil? || !mapper[:type][:value].is_a?(Hash)
122
+ fail DeserializationError.new("'value' metadata for a dictionary type must be defined in the mapper and it must be of type Hash in #{object_name}", nil, nil, response_body)
123
+ end
124
+
125
+ result = Hash.new
126
+ response_body.each do |key, val|
127
+ result[key] = deserialize(mapper[:type][:value], val, object_name)
128
+ end
129
+ result
130
+ end
131
+
132
+ #
133
+ # Deserialize the response of composite type from the server using the mapper.
134
+ #
135
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the response_body.
136
+ # @param response_body [Hash] Ruby Hash object to deserialize.
137
+ # @param object_name [String] Name of the deserialized object.
138
+ #
139
+ def deserialize_composite_type(mapper, response_body, object_name)
140
+ if mapper[:type][:class_name].nil?
141
+ fail DeserializationError.new("'class_name' metadata for a composite type must be defined in the mapper and it must be of type Hash in #{object_name}", nil, nil, response_body)
142
+ end
143
+
144
+ if !mapper[:type][:polymorphic_discriminator].nil?
145
+ # Handle polymorphic types
146
+ parent_class = get_model(mapper[:type][:class_name])
147
+ discriminator = parent_class.class_eval("@@discriminatorMap")
148
+ model_name = response_body["#{mapper[:type][:polymorphic_discriminator]}"]
149
+ model_class = get_model(discriminator[model_name])
150
+ else
151
+ model_class = get_model(mapper[:type][:class_name])
152
+ end
153
+
154
+ result = model_class.new
155
+
156
+ model_mapper = model_class.mapper()
157
+ model_props = model_mapper[:type][:model_properties]
158
+
159
+ unless model_props.nil?
160
+ model_props.each do |key, val|
161
+ sub_response_body = nil
162
+ unless val[:serialized_name].to_s.include? '.'
163
+ sub_response_body = response_body[val[:serialized_name].to_s]
164
+ else
165
+ # Flattened properties will be dicovered at deeper level in payload but must be deserialized to higher levels in model class
166
+ sub_response_body = response_body
167
+ levels = split_serialized_name(val[:serialized_name].to_s)
168
+ levels.each { |level| sub_response_body = sub_response_body.nil? ? nil : sub_response_body[level.to_s] }
169
+ end
170
+
171
+ result.instance_variable_set("@#{key}", deserialize(val, sub_response_body, object_name)) unless sub_response_body.nil?
172
+ end
173
+ end
174
+ result
175
+ end
176
+
177
+ #
178
+ # Deserialize the response of sequence type from the server using the mapper.
179
+ #
180
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the response_body.
181
+ # @param response_body [Hash] Ruby Hash object to deserialize.
182
+ # @param object_name [String] Name of the deserialized object.
183
+ #
184
+ def deserialize_sequence_type(mapper, response_body, object_name)
185
+ if mapper[:type][:element].nil? || !mapper[:type][:element].is_a?(Hash)
186
+ fail DeserializationError.new("'element' metadata for a sequence type must be defined in the mapper and it must be of type Hash in #{object_name}", nil, nil, response_body)
187
+ end
188
+
189
+ return response_body if response_body.nil?
190
+
191
+ result = []
192
+ response_body.each do |element|
193
+ result.push(deserialize(mapper[:type][:element], element, object_name))
194
+ end
195
+
196
+ result
197
+ end
198
+
199
+ #
200
+ # Serialize the Ruby object into Ruby Hash to send it to the server using the mapper.
201
+ #
202
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the object.
203
+ # @param object [Object] Ruby object to serialize.
204
+ # @param object_name [String] Name of the serialized object.
205
+ #
206
+ def serialize(mapper, object, object_name)
207
+ object_name = mapper[:serialized_name] unless object_name.nil?
208
+
209
+ # Set defaults
210
+ unless mapper[:default_value].nil?
211
+ object = mapper[:default_value] if object.nil?
212
+ end
213
+ object = mapper[:default_value] if mapper[:is_constant]
214
+
215
+ # Throw if required & non-constant object is nil
216
+ if mapper[:required] && object.nil? && !mapper[:is_constant]
217
+ fail ValidationError, "#{object_name} is required and cannot be nil"
218
+ end
219
+
220
+ if !mapper[:required] && object.nil?
221
+ return object
222
+ end
223
+
224
+ payload = Hash.new
225
+ mapper_type = mapper[:type][:name]
226
+ if !mapper_type.match(/^(Number|Double|ByteArray|Boolean|Date|DateTime|DateTimeRfc1123|UnixTime|Enum|String|Object|Stream)$/i).nil?
227
+ payload = serialize_primary_type(mapper, object)
228
+ elsif !mapper_type.match(/^Dictionary$/i).nil?
229
+ payload = serialize_dictionary_type(mapper, object, object_name)
230
+ elsif !mapper_type.match(/^Composite$/i).nil?
231
+ payload = serialize_composite_type(mapper, object, object_name)
232
+ elsif !mapper_type.match(/^Sequence$/i).nil?
233
+ payload = serialize_sequence_type(mapper, object, object_name)
234
+ end
235
+ payload
236
+ end
237
+
238
+ #
239
+ # Serialize the Ruby object of known primary type into Ruby Hash to send it to the server using the mapper.
240
+ #
241
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the object.
242
+ # @param object [Object] Ruby object to serialize.
243
+ #
244
+ def serialize_primary_type(mapper, object)
245
+ mapper_type = mapper[:type][:name]
246
+ payload = nil
247
+ case mapper_type
248
+ when 'Number', 'Double', 'String', 'Date', 'Boolean', 'Object', 'Stream'
249
+ payload = object != nil ? object : nil
250
+ when 'Enum'
251
+ unless object.nil? || object.empty?
252
+ unless enum_is_valid(mapper, object)
253
+ fail ValidationError, "Enum #{mapper[:type][:module]} does not contain #{object.to_s}, but trying to send it to the server."
254
+ end
255
+ end
256
+ payload = object != nil ? object : nil
257
+ when 'ByteArray'
258
+ payload = Base64.strict_encode64(object.pack('c*'))
259
+ when 'DateTime'
260
+ payload = object.new_offset(0).strftime('%FT%TZ')
261
+ when 'DateTimeRfc1123'
262
+ payload = object.new_offset(0).strftime('%a, %d %b %Y %H:%M:%S GMT')
263
+ when 'UnixTime'
264
+ payload = object.new_offset(0).strftime('%s') unless object.nil?
265
+ end
266
+ payload
267
+ end
268
+
269
+ #
270
+ # Serialize the Ruby object of dictionary type into Ruby Hash to send it to the server using the mapper.
271
+ #
272
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the object.
273
+ # @param object [Object] Ruby object to serialize.
274
+ # @param object_name [String] Name of the serialized object.
275
+ #
276
+ def serialize_dictionary_type(mapper, object, object_name)
277
+ unless object.is_a?(Hash)
278
+ fail DeserializationError.new("#{object_name} must be of type Hash", nil, nil, object)
279
+ end
280
+
281
+ unless mapper[:type][:value].nil? || mapper[:type][:value].is_a?(Hash)
282
+ fail DeserializationError.new("'value' metadata for a dictionary type must be defined in the mapper and it must be of type Hash in #{object_name}", nil, nil, object)
283
+ end
284
+
285
+ payload = Hash.new
286
+ object.each do |key, value|
287
+ if !value.nil? && value.respond_to?(:validate)
288
+ value.validate
289
+ end
290
+
291
+ payload[key] = serialize(mapper[:type][:value], value, object_name)
292
+ end
293
+ payload
294
+ end
295
+
296
+ #
297
+ # Serialize the Ruby object of composite type into Ruby Hash to send it to the server using the mapper.
298
+ #
299
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the object.
300
+ # @param object [Object] Ruby object to serialize.
301
+ # @param object_name [String] Name of the serialized object.
302
+ #
303
+ def serialize_composite_type(mapper, object, object_name)
304
+ if !mapper[:type][:polymorphic_discriminator].nil?
305
+ # Handle polymorphic types
306
+ model_name = object.class.to_s.split('::')[-1]
307
+ model_class = get_model(model_name)
308
+ else
309
+ model_class = get_model(mapper[:type][:class_name])
310
+ end
311
+
312
+ payload = Hash.new
313
+ model_mapper = model_class.mapper()
314
+ model_props = model_mapper[:type][:model_properties]
315
+
316
+ unless model_props.nil?
317
+ model_props.each do |key, value|
318
+ begin
319
+ instance_variable = object.instance_variable_get("@#{key}")
320
+ rescue NameError
321
+ warn("Instance variable '#{key}' is expected on '#{object.class}'.")
322
+ end
323
+
324
+ if !instance_variable.nil? && instance_variable.respond_to?(:validate)
325
+ instance_variable.validate
326
+ end
327
+
328
+ # Read only properties should not be sent on wire
329
+ if !model_props[key][:read_only].nil? && model_props[key][:read_only]
330
+ next
331
+ end
332
+
333
+ sub_payload = serialize(value, instance_variable, object_name)
334
+
335
+ unless value[:serialized_name].to_s.include? '.'
336
+ payload[value[:serialized_name].to_s] = sub_payload unless sub_payload.nil?
337
+ else
338
+ # Flattened properties will be discovered at higher levels in model class but must be serialized to deeper level in payload
339
+ levels = split_serialized_name(value[:serialized_name].to_s)
340
+ last_level = levels.pop
341
+ temp_payload = payload
342
+ levels.each do |level|
343
+ temp_payload[level] = Hash.new unless temp_payload.key?(level)
344
+ temp_payload = temp_payload[level]
345
+ end
346
+ temp_payload[last_level] = sub_payload unless sub_payload.nil?
347
+ end
348
+ end
349
+ end
350
+ payload
351
+ end
352
+
353
+ #
354
+ # Serialize the Ruby object of sequence type into Ruby Hash to send it to the server using the mapper.
355
+ #
356
+ # @param mapper [Hash] Ruby Hash object to represent expected structure of the object.
357
+ # @param object [Object] Ruby object to serialize.
358
+ # @param object_name [String] Name of the serialized object.
359
+ #
360
+ def serialize_sequence_type(mapper, object, object_name)
361
+ unless object.is_a?(Array)
362
+ fail DeserializationError.new("#{object_name} must be of type of Array", nil, nil, object)
363
+ end
364
+
365
+ unless mapper[:type][:element].nil? || mapper[:type][:element].is_a?(Hash)
366
+ fail DeserializationError.new("'element' metadata for a sequence type must be defined in the mapper and it must be of type Hash in #{object_name}", nil, nil, object)
367
+ end
368
+
369
+ payload = Array.new
370
+ object.each do |element|
371
+ if !element.nil? && element.respond_to?(:validate)
372
+ element.validate
373
+ end
374
+ payload.push(serialize(mapper[:type][:element], element, object_name))
375
+ end
376
+ payload
377
+ end
378
+
379
+ #
380
+ # Retrieves model of the model_name
381
+ #
382
+ # @param model_name [String] Name of the model to retrieve.
383
+ #
384
+ def get_model(model_name)
385
+ Object.const_get(@context.class.to_s.split('::')[0...-1].join('::') + "::Models::#{model_name}")
386
+ end
387
+
388
+ #
389
+ # Checks whether given enum_value is valid for the mapper or not
390
+ #
391
+ # @param mapper [Hash] Ruby Hash object containing meta data
392
+ # @param enum_value [String] Enum value to validate
393
+ #
394
+ def enum_is_valid(mapper, enum_value)
395
+ model = get_model(mapper[:type][:module])
396
+ model.constants.any? { |e| model.const_get(e).to_s.downcase == enum_value.downcase }
397
+ end
398
+
399
+ # Splits serialized_name with '.' to compute levels of object hierarchy
400
+ #
401
+ # @param serialized_name [String] Name to split
402
+ #
403
+ def split_serialized_name(serialized_name)
404
+ result = Array.new
405
+ element = ''
406
+
407
+ levels = serialized_name.to_s.split('.')
408
+ levels.each do |level|
409
+ unless level.match(/.*\\$/).nil?
410
+ # Flattened properties will be discovered at different levels in model class and response body
411
+ element = "#{element}#{level.gsub!('\\','')}."
412
+ else
413
+ element = "#{element}#{level}"
414
+ result.push(element) unless element.empty?
415
+ element = ''
416
+ end
417
+ end
418
+ result
419
+ end
20
420
  end
21
421
  end
22
422
  end
@@ -3,5 +3,5 @@
3
3
  # Licensed under the MIT License. See License.txt in the project root for license information.
4
4
 
5
5
  module MsRest
6
- VERSION = '0.2.1'
6
+ VERSION = '0.3.0'
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ms_rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Microsoft Corporation
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-07 00:00:00.000000000 Z
11
+ date: 2016-07-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -116,6 +116,7 @@ extra_rdoc_files: []
116
116
  files:
117
117
  - ".gitignore"
118
118
  - ".travis.yml"
119
+ - ChangeLog.md
119
120
  - Gemfile
120
121
  - LICENSE.txt
121
122
  - README.md
@@ -162,4 +163,3 @@ signing_key:
162
163
  specification_version: 4
163
164
  summary: Azure Client Library for Ruby.
164
165
  test_files: []
165
- has_rdoc: