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.
- 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
|