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.
- data/ChangeLog +4 -0
- data/README +1 -1
- data/lib/ads_common/api.rb +66 -86
- data/lib/ads_common/api_config.rb +37 -64
- data/lib/ads_common/auth/base_handler.rb +9 -19
- data/lib/ads_common/auth/client_login_handler.rb +43 -44
- data/lib/ads_common/auth/oauth_handler.rb +63 -77
- data/lib/ads_common/build/savon_generator.rb +1 -5
- data/lib/ads_common/build/savon_service_generator.rb +3 -16
- data/lib/ads_common/credential_handler.rb +31 -9
- data/lib/ads_common/http.rb +20 -21
- data/lib/ads_common/parameters_validator.rb +2 -1
- data/lib/ads_common/results_extractor.rb +183 -0
- data/lib/ads_common/savon_headers/base_header_handler.rb +26 -21
- data/lib/ads_common/savon_headers/oauth_header_handler.rb +4 -29
- data/lib/ads_common/savon_service.rb +28 -174
- data/lib/ads_common/version.rb +1 -1
- data/test/coverage.rb +35 -0
- data/test/suite_unittests.rb +30 -0
- data/test/test_client_login_handler.rb +41 -8
- data/test/test_config.rb +3 -4
- data/test/test_credential_handler.rb +55 -0
- data/test/test_parameters_validator.rb +24 -1
- data/test/test_results_extractor.rb +165 -0
- data/test/test_savon_service.rb +14 -167
- metadata +38 -25
- data/lib/ads_common/savon_headers/simple_header_handler.rb +0 -63
@@ -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(
|
45
|
+
def initialize(config, endpoint)
|
48
46
|
namespace = '<%= @namespace %>'
|
49
|
-
super(
|
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
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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 =
|
93
|
+
def load_from_config(config)
|
94
|
+
@credentials = config.read('authentication')
|
73
95
|
end
|
74
96
|
end
|
75
97
|
end
|
data/lib/ads_common/http.rb
CHANGED
@@ -25,14 +25,15 @@ require 'httpi'
|
|
25
25
|
require 'ads_common/errors'
|
26
26
|
|
27
27
|
module AdsCommon
|
28
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
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 =
|
86
|
+
httpi.read_timeout =
|
88
87
|
config.read('connection.read_timeout', HTTP_READ_TIMEOUT)
|
89
|
-
httpi.open_timeout =
|
88
|
+
httpi.open_timeout =
|
90
89
|
config.read('connection.open_timeout', HTTP_OPEN_TIMEOUT)
|
91
|
-
strict_ssl =
|
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
|
@@ -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,
|
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
|
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'] =
|
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
|
87
|
-
def
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
return
|
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
|