google-ads-common 0.2.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.
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Code template for wrappers generation for Savon backend
21
+
22
+ require 'savon'
23
+ require 'ads_common/build/savon_abstract_generator'
24
+
25
+ module AdsCommon
26
+ module Build
27
+ class SavonServiceGenerator < SavonAbstractGenerator
28
+ SERVICE_TEMPLATE = %q{<% %>
29
+ #!/usr/bin/ruby
30
+ # This is auto-generated code, changes will be overwritten.
31
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
32
+ # License:: Licensed under the Apache License,Version 2.0 (the "License").
33
+ #
34
+ # <%= @generator_stamp %>
35
+
36
+ require 'savon'
37
+ require '<%= @require_path %>/<%= @service_name.to_s.snakecase %>_registry'
38
+
39
+ <%= @modules_open_string %>
40
+
41
+ class <%= @service_name %>
42
+ attr_accessor :headerhandler, :wiredump_dev, :options
43
+ def initialize(endpoint)
44
+ @headerhandler = []
45
+ @wiredump_dev = nil
46
+ @options = {}
47
+ @client = Savon::Client.new do |wsdl|
48
+ wsdl.namespace = '<%= @namespace %>'
49
+ wsdl.endpoint = endpoint
50
+ end
51
+ end
52
+
53
+ <% @actions.each do |action| %>
54
+ def <%= action %>(args = nil)
55
+ validate_args(:<%= action %>, args)
56
+ response = @client.request(:<%= action %>) {|soap|
57
+ set_headers(soap, args)
58
+ }
59
+ handle_errors(response)
60
+ return extract_result(response, '<%= action %>')
61
+ end
62
+ <% end %>
63
+ private
64
+
65
+ def validate_args(action_symbol, args = nil)
66
+ true
67
+ end
68
+
69
+ def set_headers(soap, args)
70
+ @headerhandler.each { |handler| handler.prepare_soap(soap, args) }
71
+ end
72
+
73
+ # Extracts the finest results possible for the given result. Returns
74
+ # the response itself in worst case (contents unknown).
75
+ def extract_result(response, action_name)
76
+ method = <%= @service_name %>Registry::get_method_signature(action_name)
77
+ action = method[:output][:name].to_sym
78
+ result = response.to_hash
79
+ result = result[action] if result.include?(action)
80
+ result = result[:rval] if result.include?(:rval)
81
+ return result
82
+ end
83
+
84
+ # Checks for errors in response and raises appropriate exception
85
+ def handle_errors(response)
86
+ if response.soap_fault?
87
+ exception = exception_for_soap_fault(response)
88
+ raise exception
89
+ end
90
+
91
+ if response.http_error?
92
+ raise AdsCommon::Errors::HttpError,
93
+ "HTTP Error occurred: %s" % response.http_error
94
+ end
95
+ end
96
+
97
+ # Finds an exception object for a given response
98
+ def exception_for_soap_fault(response)
99
+ begin
100
+ exception_fault =
101
+ response.to_hash[:fault][:detail][:api_exception_fault]
102
+ exception_name = exception_fault[:application_exception_type]
103
+ exception_class = <%= @module_name %>::const_get(exception_name)
104
+ return exception_class.new(exception_fault)
105
+ rescue Exception => e
106
+ return AdsCommon::Errors::ApiException.new(
107
+ "Failed to resolve exception (%s)\n SOAP fault: %s" %
108
+ [e.message, response.soap_fault])
109
+ end
110
+ end
111
+ end
112
+ <%= @modules_close_string %>
113
+
114
+ }.gsub(/^ /, '')
115
+
116
+ def initialize(args)
117
+ super(args)
118
+ @actions = []
119
+ end
120
+
121
+ def add_actions(actions)
122
+ @actions += actions
123
+ end
124
+
125
+ def get_code_template()
126
+ SERVICE_TEMPLATE
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,565 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # Author:: api.sgomes@gmail.com (Sérgio Gomes)
4
+ #
5
+ # Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
6
+ #
7
+ # License:: Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
16
+ # implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+ # Generates the wrappers for API services. Only used during the
21
+ # 'rake generate' step of library setup.
22
+
23
+ module AdsCommon
24
+ module Build
25
+
26
+ # Contains the methods that handle wrapper code generation.
27
+ module Soap4rGenerator
28
+ ARRAY_CLASSNAME = 'SOAP::SOAPArray'
29
+
30
+ # Should be overriden for specific APIs, to contain the API config
31
+ # module.
32
+ def api_config
33
+ nil
34
+ end
35
+
36
+ # Should be overriden for specific APIs, to contain the extension config
37
+ # module.
38
+ def extension_config
39
+ nil
40
+ end
41
+
42
+ # Should be overriden for specific APIs, to contain an instance of
43
+ # AdsCommon::Config with the configs for the appropriate library.
44
+ def config
45
+ nil
46
+ end
47
+
48
+ # Converts from camelCase names to underscore_separated names.
49
+ #
50
+ # Args:
51
+ # - text: the text to be converted
52
+ #
53
+ def underscore(text)
54
+ text.gsub(/[a-z0-9][A-Z]/) do |match|
55
+ match[0,1] + '_' + match[1,1].downcase
56
+ end
57
+ end
58
+
59
+ # Generate the wrapper class for a given service.
60
+ # These classes make it easier to invoke the API methods, by removing the
61
+ # need to instance a <MethodName> object, instead allowing passing of the
62
+ # call parameters directly.
63
+ #
64
+ # Args:
65
+ # - version: the API version (as an integer)
66
+ # - service: the service name (as a string)
67
+ #
68
+ # Returns:
69
+ # The Ruby code for the class, as a string.
70
+ #
71
+ def generate_wrapper_class(version, service)
72
+ wrapper = service.to_s + "Wrapper"
73
+ module_name = api_config.module_name(version, service)
74
+ driver = api_config.interface_name(version, service)
75
+ driver_class = eval(driver)
76
+ api_name = api_config.api_name
77
+
78
+ registry =
79
+ eval("#{module_name}::DefaultMappingRegistry::LiteralRegistry")
80
+
81
+ class_def = <<-EOS
82
+ # This file was automatically generated during the "rake generate" step of
83
+ # library setup.
84
+ require '#{api_config.api_path}/#{version}/#{service}Driver.rb'
85
+
86
+ module #{api_name}
87
+ module #{version.to_s.upcase}
88
+ module #{service}
89
+
90
+ # Wrapper class for the #{version.to_s} #{service} service.
91
+ # This class is automatically generated.
92
+ class #{wrapper}
93
+
94
+ # Holds the API object to which the wrapper belongs.
95
+ attr_reader :api
96
+
97
+ # Version and service utility fields.
98
+ attr_reader :version, :service
99
+
100
+ REGISTRY = #{module_name}::DefaultMappingRegistry::LiteralRegistry
101
+ # This takes advantage of the code generated by soap4r to get the
102
+ # correct namespace for a given service. It accesses one of the fields
103
+ # in the description of the service's methods, which indicates the
104
+ # namespace.
105
+ # Since we're using a fixed version of soap4r (1.5.8), and this is
106
+ # automatically generated as part of the stub generation, it will
107
+ # always point to what we want.
108
+ NAMESPACE = '#{driver_class::Methods[0][2][0][2][1]}'
109
+
110
+ # Holds a shortcut to the parent module.
111
+ # Use this to avoid typing the full class name when creating classes
112
+ # belonging to this service, e.g.
113
+ # service_object.module::ClassName
114
+ # instead of
115
+ # #{api_name}::#{version.to_s.upcase}::#{service}::ClassName
116
+ # This will make it easier to migrate your code between API versions.
117
+ attr_reader :module
118
+
119
+ public
120
+
121
+ # Constructor for #{wrapper}.
122
+ #
123
+ # Args:
124
+ # - driver: SOAP::RPC::Driver object with the remote SOAP methods for
125
+ # this service
126
+ # - api: the API object to which the wrapper belongs
127
+ #
128
+ def initialize(driver, api)
129
+ @driver = driver
130
+ @api = api
131
+ @module = #{api_name}::#{version.to_s.upcase}::#{service}
132
+ @version = :#{version}
133
+ @service = :#{service}
134
+ end
135
+
136
+ # Returns the namespace for this service.
137
+ def namespace
138
+ return NAMESPACE
139
+ end
140
+
141
+ private
142
+
143
+ # Converts from underscore_separated names to camelCase names.
144
+ #
145
+ # Args:
146
+ # - text: the text to be converted
147
+ #
148
+ def camel_case(text)
149
+ text.gsub(/_\\w/) {|match| match[1..-1].upcase}
150
+ end
151
+
152
+ # Converts from camelCase names to underscore_separated names.
153
+ #
154
+ # Args:
155
+ # - text: the text to be converted
156
+ #
157
+ def underscore(text)
158
+ text.gsub(/[a-z0-9][A-Z]/) do |match|
159
+ match[0,1] + '_' + match[1,1].downcase
160
+ end
161
+ end
162
+
163
+ # Validates whether an object is of the correct type.
164
+ # This method is invoked by the hash to object converter during
165
+ # runtime to check the type validity of every object.
166
+ #
167
+ # Args:
168
+ # - object: the hash "object" being evaluated
169
+ # - type: the expected type (the class object itself)
170
+ #
171
+ # Returns:
172
+ # nil, upon success
173
+ #
174
+ # Raises:
175
+ # - ArgumentError: in case of an unexpected type
176
+ #
177
+ def validate_object(object, type)
178
+ return nil if object.is_a? type
179
+
180
+ wsdl_type_obj = type.new
181
+
182
+ if object.is_a? Hash
183
+ xsi_type = object[:xsi_type] or object['xsi_type']
184
+ if xsi_type
185
+ begin
186
+ subtype = @module.class_eval(xsi_type)
187
+ user_type_obj = subtype.new
188
+ rescue
189
+ raise ArgumentError, "Specified xsi_type '" + xsi_type +
190
+ "' is unknown"
191
+ end
192
+ unless user_type_obj.is_a? type
193
+ raise ArgumentError, "Specified xsi_type '" + xsi_type +
194
+ "' is not a subclass of " + type.to_s
195
+ end
196
+ else
197
+ object.each do |key, value|
198
+ if key.to_s != 'xsi_type'
199
+ if !wsdl_type_obj.respond_to?(camel_case(key.to_s).to_sym)
200
+ raise ArgumentError, "Unknown property '" + key.to_s +
201
+ "' for type " + type.to_s
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
207
+ return nil
208
+ end
209
+
210
+ # Sets a property on a real (soap4r-generated) object.
211
+ #
212
+ # Args:
213
+ # - object: the object being modified
214
+ # - property: the property being set
215
+ # - value: the value it's being set to
216
+ #
217
+ def set_object_property(object, property, value)
218
+ begin
219
+ object.send(property.to_s + '=', value)
220
+ rescue
221
+ object_class = object.class.name.split('::').last
222
+ error = AdsCommon::Errors::MissingPropertyError.new(
223
+ property, object_class)
224
+ message = "'Missing property `" + property.to_s +
225
+ "' for object class `" + object_class + "'"
226
+ raise(error, message)
227
+ end
228
+ end
229
+
230
+ public
231
+
232
+ # Converts dynamic objects (property hashes) into real soap4r objects.
233
+ # This is meant to be called when setting properties on a class, so
234
+ # the method receives an optional parameter specifying the class and
235
+ # property. This way, it's possible to determine the default type for
236
+ # the object if none is provided.
237
+ #
238
+ # Args:
239
+ # - object: the object being converted
240
+ # - parent_class: the class whose property is being set
241
+ # - property: the property being set
242
+ #
243
+ def convert_to_object(object, parent_class = nil, property = nil)
244
+ property = camel_case(property.to_s) if property
245
+ if object.is_a? Hash
246
+ # Process a hash.
247
+ specified_class = object[:xsi_type] or object['xsi_type']
248
+ default_class = nil
249
+ # Determine default class for this object, given the property
250
+ # being set.
251
+ if parent_class and property
252
+ parent = REGISTRY.schema_definition_from_class(parent_class)
253
+ element = parent.elements.entries.find do |entry|
254
+ entry.varname.to_s == property.to_s
255
+ end
256
+ default_class = element.mapped_class if element
257
+ end
258
+ validate_object(object, default_class)
259
+ real_class = nil
260
+ if specified_class
261
+ real_class = @module.class_eval(specified_class)
262
+ else
263
+ real_class = default_class
264
+ end
265
+ # Instance real object.
266
+ real_object = real_class.new
267
+ # Set each of its properties.
268
+ object.each do |entry, value|
269
+ entry = entry.to_s
270
+ unless entry == 'xsi_type'
271
+ if @api.config.read('service.use_ruby_names')
272
+ entry = camel_case(entry)
273
+ end
274
+ if value.is_a? Hash
275
+ # Recurse.
276
+ set_object_property(real_object, entry,
277
+ convert_to_object(value, real_class, entry))
278
+ elsif value.is_a? Array
279
+ set_object_property(real_object, entry,
280
+ value.map do |item|
281
+ # Recurse.
282
+ convert_to_object(item, real_class, entry)
283
+ end
284
+ )
285
+ else
286
+ set_object_property(real_object, entry, value)
287
+ end
288
+ end
289
+ end
290
+ return real_object
291
+ elsif object.is_a? Array
292
+ # Process an array
293
+ return object.map do |entry|
294
+ # Recurse.
295
+ convert_to_object(entry, parent_class, property)
296
+ end
297
+ else
298
+ return object
299
+ end
300
+ end
301
+
302
+ # Converts real soap4r objects into dynamic ones (property hashes).
303
+ # This is meant to be called for return objects of remote calls.
304
+ #
305
+ # Args:
306
+ # - object: the object being converted
307
+ #
308
+ def convert_from_object(object)
309
+ if object.class.name =~
310
+ /#{api_config.api_name}::#{version.to_s.upcase}::\\w+::\\w+/
311
+ # Handle soap4r object
312
+ object_class = REGISTRY.schema_definition_from_class(object.class)
313
+ if object_class.elements and !object_class.elements.entries.empty?
314
+ # Process complex object.
315
+ hash = {}
316
+ hash[:xsi_type] = object.class.name.split('::').last
317
+ object_class.elements.entries.each do |entry|
318
+ property = entry.varname.to_s
319
+ if object.respond_to? property and !property.include?('_Type')
320
+ value = object.send(property)
321
+ property_name = nil
322
+ if @api.config.read('service.use_ruby_names')
323
+ property_name = underscore(property).to_sym
324
+ else
325
+ property_name = property.to_sym
326
+ end
327
+ # Recurse.
328
+ hash[property_name] = convert_from_object(value) if value
329
+ end
330
+ end
331
+ return hash
332
+ else
333
+ # Process simple object.
334
+ parent = object.class.superclass
335
+ return parent.new(object)
336
+ end
337
+ elsif object.is_a? Array
338
+ # Handle arrays
339
+ return object.map do |entry|
340
+ # Recurse.
341
+ convert_from_object(entry)
342
+ end
343
+ else
344
+ # Handle native objects
345
+ return object
346
+ end
347
+ end
348
+
349
+
350
+ public
351
+
352
+ EOS
353
+
354
+ # Add service methods
355
+ methods = driver_class::Methods
356
+ module_name = api_config.module_name(version, service)
357
+ methods.each do |method|
358
+ name = method[1]
359
+ doc_link = doc_link(version, service, name)
360
+ method_def = <<-EOS
361
+ # Calls the {#{name}}[#{doc_link}] method of the #{service} service.
362
+ # Check {the online documentation for this method}[#{doc_link}].
363
+ EOS
364
+
365
+ begin
366
+ method_class = eval("#{module_name}::#{fix_case_up(name)}")
367
+ arguments =
368
+ registry.schema_definition_from_class(method_class).elements
369
+ rescue
370
+ method_class = nil
371
+ arguments = nil
372
+ end
373
+
374
+ if arguments and arguments.size > 0
375
+ method_def += <<-EOS
376
+ #
377
+ # Args:
378
+ EOS
379
+ end
380
+
381
+ if arguments
382
+ # Add list of arguments to the RDoc comment
383
+ arguments.each_with_index do |elem, index|
384
+ if type(elem) == ARRAY_CLASSNAME
385
+ method_def += <<-EOS
386
+ # - #{elem.varname}: #{type(elem)} of #{elem.mapped_class}
387
+ EOS
388
+ else
389
+ method_def += <<-EOS
390
+ # - #{elem.varname}: #{type(elem)}
391
+ EOS
392
+ end
393
+ end
394
+ end
395
+
396
+ begin
397
+ response_class =
398
+ eval("#{module_name}::#{fix_case_up(name)}Response")
399
+ returns =
400
+ registry.schema_definition_from_class(response_class).elements
401
+
402
+ if returns.size > 0
403
+ method_def += <<-EOS
404
+ #
405
+ # Returns:
406
+ EOS
407
+ end
408
+
409
+ # Add list of returns to the RDoc comment
410
+ returns.each_with_index do |elem, index|
411
+ if type(elem) == ARRAY_CLASSNAME
412
+ method_def += <<-EOS
413
+ # - #{elem.varname}: #{type(elem)} of #{elem.mapped_class}
414
+ EOS
415
+ else
416
+ method_def += <<-EOS
417
+ # - #{elem.varname}: #{type(elem)}
418
+ EOS
419
+ end
420
+ end
421
+ rescue
422
+ method_def += <<-EOS
423
+ #
424
+ # Returns:
425
+ EOS
426
+ end
427
+
428
+ arg_names = arguments ? arguments.map {|elem| elem.varname} : []
429
+ arg_list = arg_names.join(', ')
430
+
431
+ method_def += <<-EOS
432
+ #
433
+ # Raises:
434
+ # Error::ApiError (or a subclass thereof) if a SOAP fault occurs.
435
+ #
436
+ def #{name}(#{arg_list})
437
+ begin
438
+ arg_array = []
439
+ EOS
440
+
441
+ # Add validation for every argument
442
+ if arguments
443
+ arguments.each_with_index do |elem, index|
444
+ method_def += <<-EOS
445
+ validate_object(#{arg_names[index]}, #{type(elem)})
446
+ arg_array << convert_to_object(#{elem.varname}, #{method_class},
447
+ '#{elem.varname}')
448
+ EOS
449
+ end
450
+ end
451
+
452
+ method_def += <<-EOS
453
+ # Construct request object and make API call
454
+ EOS
455
+
456
+ if arguments
457
+ method_def += <<-EOS
458
+ obj = #{module_name}::#{fix_case_up(name)}.new(*arg_array)
459
+ reply = convert_from_object(@driver.#{name}(obj))
460
+ EOS
461
+ else
462
+ method_def += <<-EOS
463
+ reply = convert_from_object(@driver.#{name}())
464
+ EOS
465
+ end
466
+
467
+ method_def += <<-EOS
468
+ reply = reply[:rval] if reply.include?(:rval)
469
+ return reply
470
+ rescue SOAP::FaultError => fault
471
+ raise #{api_config.api_name}::Errors.create_api_exception(fault,
472
+ self)
473
+ end
474
+ end
475
+
476
+ EOS
477
+ class_def += method_def
478
+
479
+ if name != underscore(name)
480
+ class_def += <<-EOS
481
+ alias #{underscore(name)} #{name}\n
482
+
483
+ EOS
484
+ end
485
+ end
486
+
487
+ # Add extension methods, if any
488
+ extensions = extension_config.extensions[[version, service]]
489
+ unless extensions.nil?
490
+ extensions.each do |ext|
491
+ params = extension_config.methods[ext].join(', ')
492
+ arglist = 'self'
493
+ arglist += ", #{params}" if params != ''
494
+ method_def = <<-EOS
495
+ # <i>Extension method</i> -- Calls the
496
+ # #{api_config.api_name}::Extensions.#{ext} method with +self+ as the
497
+ # first parameter.
498
+ def #{ext}(#{params})
499
+ return #{api_config.api_name}::Extensions.#{ext}(#{arglist})
500
+ end
501
+
502
+ EOS
503
+ class_def += method_def
504
+ end
505
+ end
506
+
507
+ class_def += <<-EOS
508
+ end
509
+ end
510
+ end
511
+ end
512
+ EOS
513
+ return class_def
514
+ end
515
+
516
+ # Helper method to fix a method name from lowerCamelCase to CamelCase.
517
+ #
518
+ # Args:
519
+ # - name: the method name
520
+ #
521
+ # Returns:
522
+ # The fixed name.
523
+ #
524
+ def fix_case_up(name)
525
+ return name[0, 1].upcase + name[1..-1]
526
+ end
527
+
528
+ # Helper method to create a link to a method's entry in the API online
529
+ # docs.
530
+ #
531
+ # Args:
532
+ # - version: the API version (as an integer)
533
+ # - service: the service name (as a string)
534
+ # - method: the method name (as a string)
535
+ #
536
+ # Returns:
537
+ # The URL to the method's entry in the documentation (as a string).
538
+ # +nil+ if none.
539
+ #
540
+ def doc_link(version, service, method)
541
+ return nil
542
+ end
543
+
544
+ # Helper method to return the expected type for a parameter, given the
545
+ # SchemaElementDefinition.
546
+ #
547
+ # Args:
548
+ # - element: SOAP::Mapping::SchemaElementDefinition element for the
549
+ # parameter (taken from the schema definition of the class)
550
+ #
551
+ # Returns:
552
+ # The full name for the expected parameter type (as a String)
553
+ #
554
+ def type(element)
555
+ # Check if it's an array
556
+ if element.as_array?
557
+ return ARRAY_CLASSNAME
558
+ else
559
+ return element.mapped_class
560
+ end
561
+ end
562
+ end
563
+ end
564
+ end
565
+