ms_rest 0.2.1 → 0.3.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 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: