google-ads-common 0.6.4 → 0.7.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.
@@ -25,6 +25,7 @@ require 'ads_common/build/savon_abstract_generator'
25
25
  module AdsCommon
26
26
  module Build
27
27
  class SavonServiceGenerator < SavonAbstractGenerator
28
+
28
29
  SERVICE_TEMPLATE = %q{<% %>
29
30
  # Encoding: utf-8
30
31
  #
@@ -37,16 +38,13 @@ module AdsCommon
37
38
 
38
39
  require 'ads_common/savon_service'
39
40
  require '<%= @require_path %>/<%= @service_name.to_s.snakecase %>_registry'
40
- <% unless @extensions.empty? %>
41
- require '<%= @api_name.snakecase %>/extensions'
42
- <% end %>
43
41
 
44
42
  <%= @modules_open_string %>
45
43
 
46
44
  class <%= @service_name %> < AdsCommon::SavonService
47
- def initialize(api, endpoint)
45
+ def initialize(config, endpoint)
48
46
  namespace = '<%= @namespace %>'
49
- super(api, endpoint, namespace, :<%= @version %>)
47
+ super(config, endpoint, namespace, :<%= @version %>)
50
48
  end
51
49
  <% @actions.each do |action| %>
52
50
 
@@ -54,12 +52,6 @@ module AdsCommon
54
52
  return execute_action('<%= action %>', args, &block)
55
53
  end
56
54
  <% end %>
57
- <% @extensions.each do |extention| %>
58
-
59
- def <%= extention %>(*args)
60
- return <%= @api_name %>::Extensions.<%= extention %>(self, args)
61
- end
62
- <% end %>
63
55
 
64
56
  private
65
57
 
@@ -78,17 +70,12 @@ module AdsCommon
78
70
  def initialize(args)
79
71
  super(args)
80
72
  @actions = []
81
- @extensions = []
82
73
  end
83
74
 
84
75
  def add_actions(actions)
85
76
  @actions += actions
86
77
  end
87
78
 
88
- def add_extensions(extensions)
89
- @extensions += extensions
90
- end
91
-
92
79
  def get_code_template()
93
80
  SERVICE_TEMPLATE
94
81
  end
@@ -21,15 +21,23 @@
21
21
 
22
22
  module AdsCommon
23
23
  class CredentialHandler
24
- attr_reader :credentials
25
24
 
25
+ # Initializes CredentialHandler.
26
26
  def initialize(config)
27
27
  @config = config
28
- load_from_config
28
+ load_from_config(config)
29
+ end
30
+
31
+ # Returns credentials set for the next call.
32
+ def credentials(credentials_override = nil)
33
+ credentials = @credentials.dup()
34
+ credentials.merge!(credentials_override) unless credentials_override.nil?
35
+ return credentials
29
36
  end
30
37
 
31
38
  # Set the credentials hash to a new one. Calculate difference, and call the
32
39
  # AuthHandler callback appropriately.
40
+ # TODO(dklimkin): re-write this with inject.
33
41
  def credentials=(new_credentials)
34
42
  # Find new and changed properties.
35
43
  diff = new_credentials.select do |key, value|
@@ -55,21 +63,35 @@ module AdsCommon
55
63
  # appropriately.
56
64
  def set_credential(credential, value)
57
65
  @credentials[credential] = value
66
+ # TODO(dklimkin): @auth_handler is never defined.
58
67
  @auth_handler.property_changed(credential, value) if @auth_handler
59
68
  end
60
69
 
61
- # Returns current configuration.
62
- # TODO: we need better way to access config widely,
63
- # remove after refactoring.
64
- def get_config()
65
- return @config
70
+ # Generates string for UserAgent to put into HTTP headers.
71
+ def generate_http_user_agent()
72
+ agent_data = []
73
+ agent_data << HTTPI::Adapter.use.to_s
74
+ ruby_engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
75
+ agent_data << [ruby_engine, RUBY_VERSION].join('/')
76
+ agent_data << (credentials[:user_agent] || $0)
77
+ user_agent = "HTTPI/%s (%s)" % [HTTPI::VERSION, agent_data.join(', ')]
78
+ return user_agent
79
+ end
80
+
81
+ # Generates string for UserAgent to put into SOAP headers.
82
+ def generate_soap_user_agent(extra_ids = [])
83
+ agent_data = extra_ids
84
+ agent_data << "Common-Ruby-%s" % AdsCommon::ApiConfig::CLIENT_LIB_VERSION
85
+ agent_data << (@credentials[:user_agent] || $0)
86
+ user_agent = "Savon/%s (%s)" % [Savon::Version, agent_data.join(', ')]
87
+ return user_agent
66
88
  end
67
89
 
68
90
  private
69
91
 
70
92
  # Loads the credentials from the config data.
71
- def load_from_config
72
- @credentials = @config.read('authentication')
93
+ def load_from_config(config)
94
+ @credentials = config.read('authentication')
73
95
  end
74
96
  end
75
97
  end
@@ -25,14 +25,15 @@ require 'httpi'
25
25
  require 'ads_common/errors'
26
26
 
27
27
  module AdsCommon
28
- module Http
28
+ class Http
29
+
29
30
  # HTTP read and open timeouts in seconds.
30
31
  HTTP_READ_TIMEOUT = 15 * 60
31
32
  HTTP_OPEN_TIMEOUT = 5 * 60
32
33
 
33
34
  # Performs a get on a URL, using all of the connection options in the
34
35
  # client library, returning a HTTPI::Response.
35
- def self.get_response(url, config = nil, headers = nil)
36
+ def self.get_response(url, config, headers = nil)
36
37
  request = prepare_request(url, config, headers)
37
38
  response = HTTPI.get(request)
38
39
  return response
@@ -40,20 +41,20 @@ module AdsCommon
40
41
 
41
42
  # Performs a get on a URL, using all of the connection options in the
42
43
  # client library, returning the response body as a string.
43
- def self.get(url, config = nil, headers = nil)
44
+ def self.get(url, config, headers = nil)
44
45
  return get_response(url, config, headers).body
45
46
  end
46
47
 
47
48
  # Performs a post on a URL, using all of the connection options in the
48
49
  # client library, returning a HTTPI::Response.
49
- def self.post_response(url, data, config = nil, headers = nil)
50
+ def self.post_response(url, data, config, headers = nil)
50
51
  request = prepare_request(url, config, headers, data)
51
52
  return HTTPI.post(request)
52
53
  end
53
54
 
54
55
  # Performs a post on a URL, using all of the connection options in the
55
56
  # client library, returning the response body as a string.
56
- def self.post(url, data, config = nil, headers = nil)
57
+ def self.post(url, data, config, headers = nil)
57
58
  return post_response(url, data, config, headers).body
58
59
  end
59
60
 
@@ -61,7 +62,7 @@ module AdsCommon
61
62
 
62
63
  # Returns a suitably configured request object for a given URL and options.
63
64
  # Defaulting to stricter :peer validation.
64
- def self.prepare_request(url, config = nil, headers = nil, data = nil)
65
+ def self.prepare_request(url, config, headers = nil, data = nil)
65
66
  request = HTTPI::Request.new(url)
66
67
  request.headers = headers if headers
67
68
  request.body = data if data
@@ -71,24 +72,22 @@ module AdsCommon
71
72
 
72
73
  # Configures HTTPI request according to the config provided.
73
74
  def self.configure_httpi(config, httpi)
74
- if config
75
- adapter = config.read('connection.adapter')
76
- HTTPI.adapter = adapter if adapter
77
- proxy = config.read('connection.proxy')
78
- httpi.proxy = proxy if proxy
79
- enable_gzip = config.read('connection.enable_gzip', false)
80
- httpi.gzip if enable_gzip
81
- logger = config.read('library.logger')
82
- if logger
83
- HTTPI.logger = logger
84
- HTTPI.log_level = :debug
85
- end
75
+ adapter = config.read('connection.adapter')
76
+ HTTPI.adapter = adapter if adapter
77
+ proxy = config.read('connection.proxy')
78
+ httpi.proxy = proxy if proxy
79
+ enable_gzip = config.read('connection.enable_gzip', false)
80
+ httpi.gzip if enable_gzip
81
+ logger = config.read('library.logger')
82
+ if logger
83
+ HTTPI.logger = logger
84
+ HTTPI.log_level = :debug
86
85
  end
87
- httpi.read_timeout = (config.nil?) ? HTTP_READ_TIMEOUT :
86
+ httpi.read_timeout =
88
87
  config.read('connection.read_timeout', HTTP_READ_TIMEOUT)
89
- httpi.open_timeout = (config.nil?) ? HTTP_OPEN_TIMEOUT :
88
+ httpi.open_timeout =
90
89
  config.read('connection.open_timeout', HTTP_OPEN_TIMEOUT)
91
- strict_ssl = config.nil? or
90
+ strict_ssl =
92
91
  config.read('connection.strict_ssl_verification', true)
93
92
  httpi.auth.ssl.verify_mode = strict_ssl ? :peer : :none
94
93
  end
@@ -30,7 +30,8 @@ module AdsCommon
30
30
  # Instance initializer.
31
31
  #
32
32
  # Args:
33
- # - service: instance of savon_service to validate for
33
+ # - registry: a registry that defines service
34
+ #
34
35
  def initialize(registry)
35
36
  @registry = registry
36
37
  @extra_namespaces = {}
@@ -0,0 +1,183 @@
1
+ # Encoding: utf-8
2
+ #
3
+ # Author:: api.dklimkin@gmail.com (Danial Klimkin)
4
+ #
5
+ # Copyright:: Copyright 2012, 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
+ # This class extracts data received from Savon and enriches it.
21
+
22
+ module AdsCommon
23
+ class ResultsExtractor
24
+
25
+ # Instance initializer.
26
+ #
27
+ # Args:
28
+ # - registry: a registry that defines service
29
+ #
30
+ def initialize(registry)
31
+ @registry = registry
32
+ end
33
+
34
+ # Extracts the finest results possible for the given result. Returns the
35
+ # response itself in worst case (contents unknown).
36
+ def extract_result(response, action_name)
37
+ method = @registry.get_method_signature(action_name)
38
+ action = method[:output][:name].to_sym
39
+ result = response.to_hash
40
+ result = result[action] if result.include?(action)
41
+ result = normalize_output(result, method)
42
+ return result[:rval] || result
43
+ return result
44
+ end
45
+
46
+ private
47
+
48
+ # Extracts misc data from response header.
49
+ def extract_header_data(response)
50
+ header_type = get_full_type_signature(:SoapResponseHeader)
51
+ headers = response.header[:response_header].dup
52
+ process_attributes(headers, false)
53
+ result = headers.inject({}) do |result, (key, v)|
54
+ normalize_output_field(headers, header_type[:fields], key)
55
+ result[key] = headers[key]
56
+ result
57
+ end
58
+ return result
59
+ end
60
+
61
+ # Normalizes output starting with root output node.
62
+ def normalize_output(output_data, method_definition)
63
+ fields = method_definition[:output][:fields]
64
+ result = normalize_fields(output_data, fields)
65
+ end
66
+
67
+ # Normalizes all fields for the given data based on the fields list
68
+ # provided.
69
+ def normalize_fields(data, fields)
70
+ fields.each do |field|
71
+ field_name = field[:name]
72
+ if data.include?(field_name)
73
+ field_data = data[field_name]
74
+ field_data = normalize_output_field(field_data, field)
75
+ field_data = check_array_collapse(field_data, field)
76
+ data[field_name] = field_data unless field_data.nil?
77
+ end
78
+ end
79
+ return data
80
+ end
81
+
82
+ # Normalizes one field of a given data recursively.
83
+ #
84
+ # Args:
85
+ # - field_data: XML data to normalize
86
+ # - field_def: field type definition for the data
87
+ #
88
+ def normalize_output_field(field_data, field_def)
89
+ return case field_data
90
+ when Array
91
+ normalize_array_field(field_data, field_def)
92
+ when Hash
93
+ normalize_hash_field(field_data, field_def)
94
+ else
95
+ normalize_item(field_data, field_def)
96
+ end
97
+ end
98
+
99
+ # Normalizes every item of an Array.
100
+ def normalize_array_field(data, field_def)
101
+ return data.map {|item| normalize_output_field(item, field_def)}
102
+ end
103
+
104
+ # Normalizes every item of a Hash.
105
+ def normalize_hash_field(field, field_def)
106
+ process_attributes(field, true)
107
+ field_type = determine_type(field, field_def[:type])
108
+ type_signature = get_full_type_signature(field_type)
109
+ # If we don't know the type, pass as-is.
110
+ return (type_signature.nil?) ?
111
+ field : normalize_fields(field, type_signature[:fields])
112
+ end
113
+
114
+ # Returns field type based on the field structure. Allows to override the
115
+ # type with custom xsi:type.
116
+ def determine_type(field_data, field_type)
117
+ if field_data.kind_of?(Hash) and field_data.include?(:xsi_type)
118
+ field_type = field_data[:xsi_type]
119
+ end
120
+ return field_type
121
+ end
122
+
123
+ # Converts one leaf item to a built-in type.
124
+ def normalize_item(item, field_def)
125
+ return case field_def[:type]
126
+ when 'long', 'int' then Integer(item)
127
+ when 'double', 'float' then Float(item)
128
+ when 'boolean' then item.kind_of?(String) ?
129
+ item.casecmp('true') == 0 : item
130
+ else item
131
+ end
132
+ end
133
+
134
+ # Checks if the field signature allows an array and forces array structure
135
+ # even for a signle item.
136
+ def check_array_collapse(data, field_def)
137
+ result = data
138
+ if !field_def[:min_occurs].nil? and
139
+ (field_def[:max_occurs] == :unbounded ||
140
+ (!field_def[:max_occurs].nil? and field_def[:max_occurs] > 1))
141
+ result = arrayize(result)
142
+ end
143
+ return result
144
+ end
145
+
146
+ # Makes sure object is an array.
147
+ def arrayize(object)
148
+ return [] if object.nil?
149
+ return object.is_a?(Array) ? object : [object]
150
+ end
151
+
152
+ # Returns all inherited fields of superclasses for given type.
153
+ def implode_parent(data_type)
154
+ result = []
155
+ if data_type[:base]
156
+ parent_type = @registry.get_type_signature(data_type[:base])
157
+ result += implode_parent(parent_type)
158
+ end
159
+ data_type[:fields].each do |field|
160
+ # If the parent type includes a field with the same name, overwrite it.
161
+ result.reject! {|parent_field| parent_field[:name].eql?(field[:name])}
162
+ result << field
163
+ end
164
+ return result
165
+ end
166
+
167
+ # Returns type signature with all inherited fields.
168
+ def get_full_type_signature(type_name)
169
+ result = (type_name.nil?) ? nil : @registry.get_type_signature(type_name)
170
+ result[:fields] = implode_parent(result) if result and result[:base]
171
+ return result
172
+ end
173
+
174
+ # Handles attributes received from Savon.
175
+ def process_attributes(data, keep_xsi_type = false)
176
+ if keep_xsi_type
177
+ xsi_type = data.delete(:"@xsi:type")
178
+ data[:xsi_type] = xsi_type if xsi_type
179
+ end
180
+ data.reject! {|key, value| key.to_s.start_with?('@')}
181
+ end
182
+ end
183
+ end
@@ -24,27 +24,24 @@ require 'savon'
24
24
  module AdsCommon
25
25
  module SavonHeaders
26
26
  class BaseHeaderHandler
27
+
27
28
  # Default namespace alias.
28
29
  DEFAULT_NAMESPACE = 'wsdl'
30
+ DEFAULT_ELEMENT_NAME = 'RequestHeader'
29
31
 
30
32
  # Initializes a header handler.
31
33
  #
32
34
  # Args:
33
35
  # - credential_handler: a header with credential data
34
36
  # - auth_handler: a header with auth data
35
- # - element_name: an API-specific name of header element
36
37
  # - namespace: default namespace to use
37
38
  # - version: services version
38
39
  #
39
- def initialize(credential_handler, auth_handler, element_name,
40
- namespace, version)
40
+ def initialize(credential_handler, auth_handler, namespace, version)
41
41
  @credential_handler = credential_handler
42
42
  @auth_handler = auth_handler
43
- @element_name = element_name
44
43
  @namespace = namespace
45
44
  @version = version
46
- @config = credential_handler.get_config
47
- Savon.configure {|config| config.raise_errors = false}
48
45
  end
49
46
 
50
47
  # Enriches soap object with API-specific headers like namespaces, login
@@ -55,22 +52,27 @@ module AdsCommon
55
52
  # Args:
56
53
  # - request: a HTTPI Request for extra configuration
57
54
  # - soap: a Savon soap object to fill fields in
58
- # - args: request parameters to adjust for namespaces
59
55
  #
60
56
  # Returns:
61
57
  # - Modified request and soap structures
62
58
  #
63
- def prepare_request(request, soap, args)
59
+ def prepare_request(request, soap)
64
60
  soap.namespace = @namespace
65
- soap.body = args if args
66
61
  # Sets the default namespace for the body.
67
62
  soap.input[2] = {:xmlns => @namespace}
68
63
  # Sets User-Agent in the HTTP header.
69
- request.headers['User-Agent'] = generate_user_agent_string()
64
+ request.headers['User-Agent'] =
65
+ @credential_handler.generate_http_user_agent()
66
+ generate_headers(request, soap)
70
67
  end
71
68
 
72
69
  private
73
70
 
71
+ # Returns element name for SOAP header.
72
+ def get_header_element_name()
73
+ return DEFAULT_ELEMENT_NAME
74
+ end
75
+
74
76
  # Adds namespace to the given string.
75
77
  #
76
78
  # Args:
@@ -83,17 +85,20 @@ module AdsCommon
83
85
  return "%s:%s" % [DEFAULT_NAMESPACE, str]
84
86
  end
85
87
 
86
- # Generates User-Agent text for HTTP request.
87
- def generate_user_agent_string()
88
- credentials = @credential_handler.credentials(@version)
89
- app_name = credentials[:userAgent] || credentials[:useragent]
90
- # We don't know the library version here. A breaking change needs to be
91
- # introduced. This is scheduled for 0.7.0, using Common version for now.
92
- lib_version = '0.6.4'
93
- soap_user_agent = "Common-Ruby-%s; %s" % [lib_version, app_name]
94
- user_agent = "Savon/%s (%s)" % [Savon::Version, soap_user_agent]
95
- user_agent += ' (gzip)' if @config.read('connection.enable_gzip', false)
96
- return user_agent
88
+ # Generates SOAP headers with the default request header element.
89
+ def generate_headers(request, soap)
90
+ element_name = get_header_element_name()
91
+ soap.header[prepend_namespace(element_name)] = generate_request_header()
92
+ end
93
+
94
+ # Generates SOAP default request header with all requested headers.
95
+ def generate_request_header()
96
+ credentials = @credential_handler.credentials
97
+ extra_headers = credentials[:extra_headers]
98
+ return extra_headers.inject({}) do |result, (header, value)|
99
+ result[prepend_namespace(header)] = value
100
+ result
101
+ end
97
102
  end
98
103
  end
99
104
  end