google-ads-common 0.6.4 → 0.7.0

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