google-ads-common 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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