google-ads-common 0.2.0 → 0.2.1

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.
@@ -34,8 +34,6 @@ module AdsCommon
34
34
  #
35
35
  # <%= @generator_stamp %>
36
36
 
37
- require 'savon'
38
-
39
37
  <%= @modules_open_string %>
40
38
 
41
39
  class <%= @service_name %>Registry
@@ -73,7 +71,7 @@ module AdsCommon
73
71
  end
74
72
  <% end %>
75
73
  end
76
- <% end %>
74
+ <% end %>
77
75
  <%= @modules_close_string %>
78
76
 
79
77
  }.gsub(/^ /, '')
@@ -33,80 +33,31 @@ module AdsCommon
33
33
  #
34
34
  # <%= @generator_stamp %>
35
35
 
36
- require 'savon'
36
+ require 'ads_common/savon_service'
37
37
  require '<%= @require_path %>/<%= @service_name.to_s.snakecase %>_registry'
38
38
 
39
39
  <%= @modules_open_string %>
40
40
 
41
- class <%= @service_name %>
42
- attr_accessor :headerhandler, :wiredump_dev, :options
41
+ class <%= @service_name %> < AdsCommon::SavonService
43
42
  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
43
+ namespace = '<%= @namespace %>'
44
+ super(endpoint, namespace)
51
45
  end
52
-
53
46
  <% @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
47
 
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
48
+ def <%= action %>(*args)
49
+ return execute_action('<%= action %>', args)
82
50
  end
51
+ <% end %>
83
52
 
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
53
+ private
90
54
 
91
- if response.http_error?
92
- raise AdsCommon::Errors::HttpError,
93
- "HTTP Error occurred: %s" % response.http_error
94
- end
55
+ def get_service_registry()
56
+ return <%= @service_name %>Registry
95
57
  end
96
58
 
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
59
+ def get_module()
60
+ return <%= @module_name %>
110
61
  end
111
62
  end
112
63
  <%= @modules_close_string %>
@@ -268,7 +268,7 @@ module AdsCommon
268
268
  object.each do |entry, value|
269
269
  entry = entry.to_s
270
270
  unless entry == 'xsi_type'
271
- if @api.config.read('service.use_ruby_names')
271
+ if @api.config.read('service.use_ruby_names', true)
272
272
  entry = camel_case(entry)
273
273
  end
274
274
  if value.is_a? Hash
@@ -319,7 +319,7 @@ module AdsCommon
319
319
  if object.respond_to? property and !property.include?('_Type')
320
320
  value = object.send(property)
321
321
  property_name = nil
322
- if @api.config.read('service.use_ruby_names')
322
+ if @api.config.read('service.use_ruby_names', true)
323
323
  property_name = underscore(property).to_sym
324
324
  else
325
325
  property_name = property.to_sym
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/ruby
2
2
  #
3
- # Authors:: api.sgomes@gmail.com (Sérgio Gomes)
3
+ # Authors:: api.dklimkin@gmail.com (Danial Klimkin)
4
4
  #
5
5
  # Copyright:: Copyright 2010, Google Inc. All Rights Reserved.
6
6
  #
@@ -29,26 +29,21 @@ module AdsCommon
29
29
  # Initialized the Config object with either the contents of a provided file
30
30
  # or a provided hash.
31
31
  def initialize(param = nil)
32
- @config = {}
33
- if param and param.is_a? String
34
- load(param)
35
- elsif param and param.is_a? Hash
36
- set_all(param)
32
+ @config = Hash.new
33
+ case param
34
+ when String: load(param)
35
+ when Hash: set_all(param)
37
36
  end
38
37
  end
39
38
 
40
39
  # Reads a property or category from the loaded configuration.
41
40
  # They can be indexed using a dot-based notation (e.g. "category.property"
42
41
  # to access "property" under "category").
43
- def read(property_path = nil)
44
- current_level = @config
45
- if property_path
46
- property_path.split('.').each do |item|
47
- return nil if !current_level or !(current_level.is_a?(Hash))
48
- current_level = current_level[item.to_sym]
49
- end
50
- end
51
- return current_level
42
+ #
43
+ # Returns the specified default if no value found.
44
+ def read(property_path, default_value = nil)
45
+ result = find_value(@config, property_path)
46
+ return (result.nil?) ? default_value : result
52
47
  end
53
48
 
54
49
  # Writes a new value to a property or category in memory (creating it if
@@ -56,18 +51,13 @@ module AdsCommon
56
51
  # They can be indexed using a dot-based notation (e.g. "category.property"
57
52
  # to access "property" under "category").
58
53
  def set(property_path, value)
59
- @config = {} if !@config
60
- current_level = @config
61
54
  if property_path
62
- segments = property_path.split('.')
63
- segments[0,-2].each do |item|
64
- return nil if !current_level or !(current_level.is_a?(Hash))
65
- current_level = current_level[item.to_sym]
66
- end
67
- if current_level and current_level.is_a?(Hash)
68
- current_level[segments.last] = value
69
- return value
55
+ last_node = @config
56
+ last_name = property_path.split('.').inject(nil) do |last_name, section|
57
+ last_node = last_node[last_name.to_sym] ||= {} unless last_name.nil?
58
+ section
70
59
  end
60
+ last_node[last_name.to_sym] = value
71
61
  end
72
62
  return nil
73
63
  end
@@ -77,46 +67,41 @@ module AdsCommon
77
67
  @config = process_hash_keys(properties)
78
68
  end
79
69
 
70
+ private
71
+
80
72
  # Auxiliary method to recurse through a hash and convert all the keys to
81
73
  # symbols.
82
74
  def process_hash_keys(hash)
83
- new_hash = {}
84
- hash.each do |key, value|
85
- if value.is_a? Hash
86
- new_hash[key.to_sym] = process_hash_keys(value)
87
- else
88
- new_hash[key.to_sym] = value
89
- end
75
+ return hash.inject({}) do |result, pair|
76
+ key, value = pair
77
+ result[key.to_sym] = value.is_a?(Hash) ?
78
+ process_hash_keys(value) : value
79
+ result
90
80
  end
91
- return new_hash
92
81
  end
93
82
 
94
- # Reads a configuration file and returns a Ruby structure with the complete
95
- # set of keys and values.
96
- #
83
+ # Finds a value for string of format 'level1.level2.name' in a given hash.
84
+ def find_value(data, path)
85
+ return (path.nil? or data.nil?) ? nil :
86
+ path.split('.').inject(data) do |node, section|
87
+ break if node.nil?
88
+ key = section.to_sym
89
+ (node.is_a?(Hash) and node.include?(key)) ? node[key] : nil
90
+ end
91
+ end
92
+
93
+ # Reads a configuration file into instance variable as a Ruby structure with
94
+ # the complete set of keys and values.
97
95
  #
98
96
  # Args:
99
97
  # - filename: config file to be read (*String*)
100
98
  #
101
- # Returns:
102
- # - Ruby objects for the stored YAML
103
- #
104
99
  # Raises:
105
- # - <b>Errno::EACCES</b> if the file does not exist.
100
+ # - <b>Errno::ENOENT</b> if the file does not exist.
106
101
  #
107
102
  def load(filename)
108
- unless File.exist?(filename)
109
- raise(Errno::EACCES, "File #{filename} does not exist")
110
- end
111
- file = nil
112
- begin
113
- file = File.open(filename)
114
- @config = YAML::load(file)
115
- ensure
116
- file.close if file
117
- end
118
- return @config
103
+ @config = YAML::load_file(filename)
104
+ return nil
119
105
  end
120
106
  end
121
-
122
107
  end
@@ -45,7 +45,8 @@ module AdsCommon
45
45
  end
46
46
 
47
47
  # Enriches soap object with API-specific headers like namespaces, login
48
- # credentials etc.
48
+ # credentials etc. Sets the default namespace for the body to the one
49
+ # specified in initializer.
49
50
  #
50
51
  # Args:
51
52
  # - soap: a Savon soap object to fill fields in.
@@ -57,9 +58,8 @@ module AdsCommon
57
58
  soap.header[prepend_namespace(@element_name)] =
58
59
  generate_request_header()
59
60
  soap.namespace = @namespace
60
- if (!args.nil?)
61
- soap.body = prepare_args(args)
62
- end
61
+ soap.body = args if args
62
+ soap.input[1] = {:xmlns => @namespace}
63
63
  end
64
64
 
65
65
  private
@@ -82,66 +82,15 @@ module AdsCommon
82
82
  return request_header
83
83
  end
84
84
 
85
- # Modifies request parameters to include namespace reference. For Hash
86
- # and Array data types dives deeper into structure.
87
- #
88
- # Args:
89
- # - args: subtree of request parameters.
90
- #
91
- # Returns:
92
- # - subtree with modified parameters including namespaces.
93
- def prepare_args(args)
94
- res = case args
95
- when Hash
96
- prepare_hash_args(args)
97
- when Array
98
- prepare_array_args(args)
99
- else
100
- args
101
- end
102
- return res
103
- end
104
-
105
- # Crawls hash for all the request parameters and adds namespace
106
- # reference for the keys.
107
- #
108
- # Args:
109
- # - args: Hash element of subtree of request parameters.
110
- #
111
- # Returns:
112
- # - Modified Hash with all keys updated and all values crawled.
113
- def prepare_hash_args(args)
114
- res = {}
115
- args.each do |key, value|
116
- res[prepend_namespace(key)] = prepare_args(value)
117
- end
118
- return res
119
- end
120
-
121
- # Crawls array and process each of its elements to include namespace.
122
- #
123
- # Args:
124
- # - args: Array element of subtree of request parameters.
125
- #
126
- # Returns:
127
- # - Modified Array with every element crawled.
128
- def prepare_array_args(args)
129
- res = []
130
- args.each do |item|
131
- res << prepare_args(item)
132
- end
133
- return res
134
- end
135
-
136
- # Adds namespace to the request parameter name.
85
+ # Adds namespace to the given string.
137
86
  #
138
87
  # Args:
139
- # - str: String to prepend with a namespace
88
+ # - str: String to prepend with a namespace.
140
89
  #
141
90
  # Returns:
142
91
  # - String with a namespace.
143
92
  def prepend_namespace(str)
144
- return "%s:%s" % [DEFAULT_NAMESPACE, str.to_s]
93
+ return "%s:%s" % [DEFAULT_NAMESPACE, str]
145
94
  end
146
95
  end
147
96
  end
@@ -0,0 +1,266 @@
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
+ # Base class for all generated API services based on Savon backend.
21
+
22
+ gem 'savon', '~>0.9.1'
23
+ require 'savon'
24
+
25
+ module AdsCommon
26
+ class SavonService
27
+ attr_accessor :headerhandler, :wiredump_dev, :options
28
+
29
+ # Creates a new service.
30
+ def initialize(endpoint, namespace)
31
+ if self.class() == AdsCommon::SavonService
32
+ raise NoMethodError, "Tried to instantiate an abstract class"
33
+ end
34
+ @headerhandler = []
35
+ @wiredump_dev = nil
36
+ @options = {}
37
+ @client = Savon::Client.new do |wsdl|
38
+ wsdl.namespace = namespace
39
+ wsdl.endpoint = endpoint
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ # Returns ServiceRegistry for the current service. Has to be overriden.
46
+ def get_service_registry()
47
+ raise NoMethodError, "This methods needs to be overriden"
48
+ end
49
+
50
+ # Returns Module for the current service. Has to be overriden.
51
+ def get_module()
52
+ raise NoMethodError, "This methods needs to be overriden"
53
+ end
54
+
55
+ # Executes SOAP action specified as a string with given arguments.
56
+ def execute_action(action_name, args)
57
+ args = validate_args(action_name, args)
58
+ response = @client.request(action_name.to_sym) do |soap|
59
+ set_headers(soap, args)
60
+ end
61
+ handle_errors(response)
62
+ return extract_result(response, action_name)
63
+ end
64
+
65
+ # Validates input parameters to:
66
+ # - add parameter names;
67
+ # - resolve xsi:type where required;
68
+ # - convert some native types to xml.
69
+ def validate_args(action_name, *args)
70
+ validated_args = Hash.new
71
+ in_params = get_service_registry.get_method_signature(action_name)[:input]
72
+ in_params.each_with_index do |in_param, index|
73
+ key = in_param[:name]
74
+ value = args[index]
75
+ validated_args[key] = (value.nil?) ? nil :
76
+ validate_arg(value, validated_args, key)
77
+ end
78
+ return validated_args
79
+ end
80
+
81
+ # Validates method argument. Runs recursively if hash or array encountered.
82
+ # Also handles some types that need special conversions.
83
+ def validate_arg(arg, parent = nil, key = nil)
84
+ result = case arg
85
+ when Hash: validate_hash_arg(arg, parent, key)
86
+ when Array: arg.map {|item| validate_arg(item, parent, key)}
87
+ when Time: time_to_xml_hash(arg)
88
+ else arg
89
+ end
90
+ return result
91
+ end
92
+
93
+ # Validates hash argument recursively. Keeps tracking of correct place
94
+ # for xsi:type and adds is when required.
95
+ def validate_hash_arg(arg, parent = nil, key = nil)
96
+ xsi_type = arg.delete('xsi:type') || arg.delete(:xsi_type)
97
+ if xsi_type
98
+ if parent and key
99
+ add_xsi_type(parent, key, xsi_type)
100
+ else
101
+ raise AdsCommon::Errors::ApiException.new(
102
+ "Can't find correct position for xsi:type (%s) [%s], [%s]" %
103
+ [xsi_type, parent, key])
104
+ end
105
+ end
106
+ return arg.inject({}) do |result, (key, value)|
107
+ result[key] = (key == :attributes!) ? value :
108
+ validate_arg(value, result, key)
109
+ result
110
+ end
111
+ end
112
+
113
+ # Adds ":attributes!" record for Savon to specify xsi:type.
114
+ def add_xsi_type(parent, key, xsi_type)
115
+ parent[:attributes!] ||= {}
116
+ parent[:attributes!][key] ||= {}
117
+ parent[:attributes!][key]["xsi:type"] ||= []
118
+ parent[:attributes!][key]["xsi:type"] << xsi_type
119
+ end
120
+
121
+ # Executes each handler to generate SOAP headers.
122
+ def set_headers(soap, args)
123
+ @headerhandler.each {|handler| handler.prepare_soap(soap, args)}
124
+ end
125
+
126
+ # Checks for errors in response and raises appropriate exception.
127
+ def handle_errors(response)
128
+ if response.soap_fault?
129
+ exception = exception_for_soap_fault(response)
130
+ raise exception
131
+ end
132
+ if response.http_error?
133
+ raise AdsCommon::Errors::HttpError,
134
+ "HTTP Error occurred: %s" % response.http_error
135
+ end
136
+ end
137
+
138
+ # Finds an exception object for a given response.
139
+ def exception_for_soap_fault(response)
140
+ begin
141
+ fault = response[:fault]
142
+ if fault[:detail] and fault[:detail][:api_exception_fault]
143
+ exception_fault = fault[:detail][:api_exception_fault]
144
+ exception_name = exception_fault[:application_exception_type]
145
+ exception_class = get_module().const_get(exception_name)
146
+ return exception_class.new(exception_fault)
147
+ elsif fault[:faultstring]
148
+ fault_message = fault[:faultstring]
149
+ return AdsCommon::Errors::ApiException.new(
150
+ "Unknown exception with error: %s" % fault_message)
151
+ else
152
+ raise ArgumentError.new(fault.to_s)
153
+ end
154
+ rescue Exception => e
155
+ return AdsCommon::Errors::ApiException.new(
156
+ "Failed to resolve exception (%s), SOAP fault: %s" %
157
+ [e.message, response.soap_fault])
158
+ end
159
+ end
160
+
161
+ # Extracts the finest results possible for the given result. Returns the
162
+ # response itself in worst case (contents unknown).
163
+ def extract_result(response, action_name)
164
+ method = get_service_registry.get_method_signature(action_name)
165
+ action = method[:output][:name].to_sym
166
+ result = response.to_hash
167
+ result = result[action] if result.include?(action)
168
+ return normalize_output(result, method)
169
+ end
170
+
171
+ # Normalizes output starting with root node "rval".
172
+ def normalize_output(output_data, method_definition)
173
+ fields_list = method_definition[:output][:fields]
174
+ result = normalize_output_field(output_data, fields_list, :rval)
175
+ return result[:rval]
176
+ end
177
+
178
+ # Normalizes one field of a given data recursively.
179
+ # Args:
180
+ # - output_data: XML data to normalize
181
+ # - fields_list: expected list of fields from signature
182
+ # - field_name: specifies field name to normalize
183
+ def normalize_output_field(output_data, fields_list, field_name)
184
+ return nil if output_data.nil?
185
+ field_definition = get_field_by_name(fields_list, field_name)
186
+ field_sym = field_name.to_sym
187
+ output_data[field_sym] = normalize_type(output_data[field_sym],
188
+ field_definition)
189
+
190
+ sub_type = get_service_registry.get_type_signature(
191
+ field_definition[:type])
192
+ if sub_type
193
+ sub_type[:fields] += implode_parent(sub_type)
194
+ if sub_type[:fields]
195
+ # go recursive
196
+ sub_type[:fields].each do |sub_type_field|
197
+ if output_data[field_sym].is_a?(Array)
198
+ items_list = output_data[field_sym]
199
+ output_data[field_sym] = Array.new
200
+ items_list.each do |item|
201
+ output_data[field_sym] <<
202
+ normalize_output_field(item, sub_type_field,
203
+ sub_type_field[:name])
204
+ end
205
+ else
206
+ output_data[field_sym] =
207
+ normalize_output_field(output_data[field_sym], sub_type_field,
208
+ sub_type_field[:name])
209
+ end
210
+ end
211
+ end
212
+ end
213
+ return output_data
214
+ end
215
+
216
+ # Converts XML input string into a native format.
217
+ def normalize_type(data, field)
218
+ type_name = field[:type]
219
+ result = case type_name
220
+ when 'long', 'int': Integer(data)
221
+ when 'double': Float(data)
222
+ when 'boolean': data.kind_of?(String) ? data.casecmp('true') == 0 : data
223
+ else data
224
+ end
225
+ # If field signature allows an array, forcing array structure even for one
226
+ # item.
227
+ if !field[:min_occurs].nil? and
228
+ (field[:max_occurs].nil? or field[:max_occurs] > 1)
229
+ result = arrayize(result)
230
+ end
231
+ return result
232
+ end
233
+
234
+ # Finds a field in a list by its name.
235
+ def get_field_by_name(fields_list, name)
236
+ fields_array = arrayize(fields_list)
237
+ index = fields_array.find_index {|field| field[:name].eql?(name)}
238
+ return (index.nil?) ? nil : fields_array.at(index)
239
+ end
240
+
241
+ # Makes sure object is an array.
242
+ def arrayize(object)
243
+ return Array.new if object.nil?
244
+ return object.is_a?(Array) ? object : [object]
245
+ end
246
+
247
+ # Converts Time to a hash for XML marshalling.
248
+ def time_to_xml_hash(time)
249
+ return {
250
+ :hour => time.hour, :minute => time.min, :second => time.sec,
251
+ :date => {:year => time.year, :month => time.month, :day => time.day}
252
+ }
253
+ end
254
+
255
+ # Returns all inherited fields of superclasses for given type.
256
+ def implode_parent(data_type)
257
+ result = Array.new
258
+ if data_type and data_type[:base]
259
+ parent_type = get_service_registry.get_type_signature(data_type[:base])
260
+ result += parent_type[:fields] if parent_type[:fields]
261
+ result += implode_parent(parent_type) if parent_type[:base]
262
+ end
263
+ return result
264
+ end
265
+ end
266
+ end