google-ads-common 0.5.5 → 0.6.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 +5 -0
- data/lib/ads_common/api_config.rb +1 -1
- data/lib/ads_common/build/savon_registry.rb +10 -1
- data/lib/ads_common/errors.rb +34 -10
- data/lib/ads_common/parameters_validator.rb +270 -0
- data/lib/ads_common/savon_headers/base_header_handler.rb +2 -2
- data/lib/ads_common/savon_service.rb +10 -157
- data/test/test_parameters_validator.rb +103 -0
- data/test/test_savon_service.rb +0 -60
- metadata +18 -15
data/ChangeLog
CHANGED
@@ -26,7 +26,7 @@ module AdsCommon
|
|
26
26
|
# Contains helper methods for loading and managing the available services.
|
27
27
|
# This module is meant to be imported into API-specific modules.
|
28
28
|
module ApiConfig
|
29
|
-
ADS_COMMON_VERSION = '0.
|
29
|
+
ADS_COMMON_VERSION = '0.6.0'
|
30
30
|
|
31
31
|
# Get the available API versions.
|
32
32
|
#
|
@@ -63,12 +63,16 @@ module AdsCommon
|
|
63
63
|
def process_types(doc)
|
64
64
|
REXML::XPath.each(doc, '//schema') do |schema|
|
65
65
|
ns_index = process_namespace(schema)
|
66
|
-
get_complex_types(schema)
|
66
|
+
complex_types = get_complex_types(schema)
|
67
|
+
simple_types = get_simple_types(schema)
|
68
|
+
(complex_types + simple_types).each do |ctype|
|
67
69
|
ctype_name = get_element_name(ctype)
|
68
70
|
if ctype_name.match('.+Exception$')
|
69
71
|
@soap_exceptions << extract_exception(ctype)
|
70
72
|
elsif ctype_name.match('.+Error$')
|
71
73
|
# We don't use it at the moment.
|
74
|
+
elsif ctype_name.match('.+\.Reason$')
|
75
|
+
# We don't use it at the moment.
|
72
76
|
else
|
73
77
|
@soap_types << extract_type(ctype, ns_index)
|
74
78
|
end
|
@@ -104,6 +108,11 @@ module AdsCommon
|
|
104
108
|
return REXML::XPath.each(node, 'complexType').to_a
|
105
109
|
end
|
106
110
|
|
111
|
+
# Extracts SimpleTypes from node into an array.
|
112
|
+
def get_simple_types(node)
|
113
|
+
return REXML::XPath.each(node, 'simpleType').to_a
|
114
|
+
end
|
115
|
+
|
107
116
|
# Extracts exception parameters from ComplexTypes element.
|
108
117
|
def extract_exception(exception_element)
|
109
118
|
return {:name => get_element_name(exception_element),
|
data/lib/ads_common/errors.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
#
|
3
|
-
# Authors:: api.
|
3
|
+
# Authors:: api.dklimkin@gmail.com (Danial Klimkin)
|
4
4
|
#
|
5
5
|
# Copyright:: Copyright 2011, Google Inc. All Rights Reserved.
|
6
6
|
#
|
@@ -20,6 +20,8 @@
|
|
20
20
|
# Contains common errors across APIs, as well as base classes to inherit from
|
21
21
|
# in specific APIs.
|
22
22
|
|
23
|
+
require 'pp'
|
24
|
+
|
23
25
|
module AdsCommon
|
24
26
|
module Errors
|
25
27
|
|
@@ -30,31 +32,53 @@ module AdsCommon
|
|
30
32
|
# Raised if an attempt is made to authenticate with missing or wrong
|
31
33
|
# information.
|
32
34
|
class AuthError < Error
|
33
|
-
attr_reader :error
|
34
|
-
attr_reader :info
|
35
|
+
attr_reader :error, :info
|
35
36
|
def initialize(message = self.class.to_s, error = nil, info = nil)
|
36
37
|
super(message)
|
37
|
-
@error = error
|
38
|
-
@info = info
|
38
|
+
@error, @info = error, info
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
# Raised when OAuth access token is required.
|
43
43
|
class OAuthVerificationRequired < AuthError
|
44
|
-
attr_reader :oauth_url
|
45
|
-
attr_reader :request_token
|
44
|
+
attr_reader :oauth_url, :request_token
|
46
45
|
def initialize(oauth_url, request_token)
|
47
46
|
super()
|
48
47
|
@oauth_url, @request_token = oauth_url, request_token
|
49
48
|
end
|
50
49
|
end
|
51
50
|
|
52
|
-
# Raised if
|
51
|
+
# Raised if a required property on an object is missing.
|
53
52
|
class MissingPropertyError < Error
|
54
53
|
attr_reader :property, :object_type
|
55
54
|
def initialize(property, object_type)
|
56
|
-
@property = property
|
57
|
-
|
55
|
+
@property, @object_type = property, object_type
|
56
|
+
end
|
57
|
+
def to_s()
|
58
|
+
return "%s: name: %s, type: %s" % [super, @property, @object_type]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Raised if the type of the object provided does not match expected type.
|
63
|
+
class TypeMismatchError < Error
|
64
|
+
attr_reader :expected, :provided, :field_name
|
65
|
+
def initialize(expected, provided, field_name)
|
66
|
+
@expected, @provided, @field_name = expected, provided, field_name
|
67
|
+
end
|
68
|
+
def to_s()
|
69
|
+
return "%s: expected: '%s', provided: '%s' for field '%s'" %
|
70
|
+
[super, @expected, @provided, @field_name]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Raised if unexpected parameters encountered.
|
75
|
+
class UnexpectedParametersError < Error
|
76
|
+
attr_reader :parameters_list
|
77
|
+
def initialize(parameters_list)
|
78
|
+
@parameters_list = parameters_list
|
79
|
+
end
|
80
|
+
def to_s()
|
81
|
+
return "%s: %s" % [super, PP.singleline_pp(@parameters_list, '')]
|
58
82
|
end
|
59
83
|
end
|
60
84
|
|
@@ -0,0 +1,270 @@
|
|
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
|
+
# This class validates input parameters before passing them to Savon.
|
21
|
+
|
22
|
+
module AdsCommon
|
23
|
+
class ParametersValidator
|
24
|
+
# Savon special keys.
|
25
|
+
IGNORED_HASH_KEYS = [:order!, :attributes!]
|
26
|
+
|
27
|
+
# We collect required namespaces into this hash during validation.
|
28
|
+
attr_reader :extra_namespaces
|
29
|
+
|
30
|
+
# Instance initializer.
|
31
|
+
#
|
32
|
+
# Args:
|
33
|
+
# - service: instance of savon_service to validate for
|
34
|
+
def initialize(registry)
|
35
|
+
@registry = registry
|
36
|
+
@extra_namespaces = {}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Validates input parameters to:
|
40
|
+
# - add parameter names;
|
41
|
+
# - resolve xsi:type where required;
|
42
|
+
# - convert some native types to XML.
|
43
|
+
def validate_args(action_name, args)
|
44
|
+
in_params = @registry.get_method_signature(action_name)[:input]
|
45
|
+
# TODO: compare number of parameters.
|
46
|
+
args_hash = in_params.each_with_index.inject({}) do
|
47
|
+
|result, (in_param, index)|
|
48
|
+
result.merge({in_param[:name] => deep_copy(args[index])})
|
49
|
+
end
|
50
|
+
validate_arguments(args_hash, in_params)
|
51
|
+
return args_hash
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Validates given arguments based on provided fields list.
|
57
|
+
def validate_arguments(args_hash, fields_list, type_ns = nil)
|
58
|
+
check_extra_fields(args_hash, array_from_named_list(fields_list))
|
59
|
+
add_order_key(args_hash, fields_list)
|
60
|
+
fields_list.each do |field|
|
61
|
+
key = field[:name]
|
62
|
+
item = args_hash[key]
|
63
|
+
check_required_argument_present(item, field)
|
64
|
+
if item
|
65
|
+
item_type = get_full_type_signature(field[:type])
|
66
|
+
item_ns = field[:ns] || type_ns
|
67
|
+
key = handle_namespace_override(args_hash, key, item_ns) if item_ns
|
68
|
+
validate_arg(item, args_hash, key, item_type)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
return args_hash
|
72
|
+
end
|
73
|
+
|
74
|
+
# Checks if no extra fields provided outside of known ones.
|
75
|
+
def check_extra_fields(args_hash, known_fields)
|
76
|
+
extra_fields = args_hash.keys - known_fields - IGNORED_HASH_KEYS
|
77
|
+
unless extra_fields.empty?
|
78
|
+
raise AdsCommon::Errors::UnexpectedParametersError.new(extra_fields)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Generates order of XML elements for SOAP request. Adds :order! key to
|
83
|
+
# keep the correct order.
|
84
|
+
def add_order_key(args, fields_list)
|
85
|
+
all_args = fields_list.map {|field| field[:name]}
|
86
|
+
order_array = (all_args & args.keys)
|
87
|
+
args[:order!] = order_array unless order_array.empty?
|
88
|
+
end
|
89
|
+
|
90
|
+
# Checks the provided data structure matches wsdl definition.
|
91
|
+
def check_required_argument_present(arg, field)
|
92
|
+
# At least one item required, none passed.
|
93
|
+
if field[:min_occurs] > 0 and arg.nil?
|
94
|
+
raise AdsCommon::Errors::MissingPropertyError.new(
|
95
|
+
field[:name], field[:type])
|
96
|
+
end
|
97
|
+
# An object passed when an array is expected.
|
98
|
+
if (field[:max_occurs] == :unbounded) and
|
99
|
+
!(arg.nil? or arg.kind_of?(Array))
|
100
|
+
raise AdsCommon::Errors::TypeMismatchError.new(
|
101
|
+
Array, arg.class, field[:name])
|
102
|
+
end
|
103
|
+
# An array passed when an object is expected.
|
104
|
+
if (field[:max_occurs] == 1) and arg.kind_of?(Array)
|
105
|
+
raise AdsCommon::Errors::TypeMismatchError.new(
|
106
|
+
field[:type], Array)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Overrides non-default namespace if requested.
|
111
|
+
def handle_namespace_override(args, key, ns)
|
112
|
+
add_extra_namespace(ns)
|
113
|
+
new_key = prefix_key_with_namespace(key.to_s.lower_camelcase, ns)
|
114
|
+
rename_hash_key(args, key, new_key)
|
115
|
+
replace_array_item(args[:order!], key, new_key)
|
116
|
+
return new_key
|
117
|
+
end
|
118
|
+
|
119
|
+
# Validates single argument.
|
120
|
+
def validate_arg(arg, parent, key, arg_type)
|
121
|
+
result = case arg
|
122
|
+
when Array
|
123
|
+
validate_array_arg(arg, parent, key, arg_type)
|
124
|
+
when Hash
|
125
|
+
validate_hash_arg(arg, parent, key, arg_type)
|
126
|
+
when Time
|
127
|
+
validate_time_arg(arg, parent, key)
|
128
|
+
else
|
129
|
+
arg
|
130
|
+
end
|
131
|
+
return result
|
132
|
+
end
|
133
|
+
|
134
|
+
# Validates Array argument.
|
135
|
+
def validate_array_arg(arg, parent, key, arg_type)
|
136
|
+
result = arg.map do |item|
|
137
|
+
validate_arg(item, parent, key, arg_type)
|
138
|
+
end
|
139
|
+
return result
|
140
|
+
end
|
141
|
+
|
142
|
+
# Validates Hash argument.
|
143
|
+
def validate_hash_arg(arg, parent, key, arg_type)
|
144
|
+
arg_type = handle_xsi_type(arg, parent, key, arg_type)
|
145
|
+
validate_arguments(arg, arg_type[:fields], arg_type[:ns])
|
146
|
+
end
|
147
|
+
|
148
|
+
# Validates Time argument.
|
149
|
+
def validate_time_arg(arg, parent, key)
|
150
|
+
xml_value = time_to_xml_hash(arg)
|
151
|
+
parent[key] = xml_value
|
152
|
+
return xml_value
|
153
|
+
end
|
154
|
+
|
155
|
+
# Handles custom xsi:type.
|
156
|
+
def handle_xsi_type(arg, parent, key, arg_type)
|
157
|
+
xsi_type = arg.delete('xsi:type') || arg.delete(:xsi_type)
|
158
|
+
if xsi_type
|
159
|
+
xsi_field_type = get_full_type_signature(xsi_type)
|
160
|
+
if xsi_field_type.nil?
|
161
|
+
raise AdsCommon::Errors::ApiException.new(
|
162
|
+
"Incorrect xsi:type specified: '%s'" % [xsi_type])
|
163
|
+
else
|
164
|
+
# TODO: make sure xsi_type is derived from arg_type.
|
165
|
+
arg_type = xsi_field_type
|
166
|
+
# xsi:type needs to be from a correct namespace.
|
167
|
+
if xsi_field_type[:ns]
|
168
|
+
xsi_type = prefix_key_with_namespace(xsi_type, xsi_field_type[:ns])
|
169
|
+
end
|
170
|
+
end
|
171
|
+
add_xsi_type(parent, key, xsi_type)
|
172
|
+
end
|
173
|
+
return arg_type
|
174
|
+
end
|
175
|
+
|
176
|
+
# Replaces an item in an array with a different one into the same position.
|
177
|
+
def replace_array_item(data, old_item, new_item)
|
178
|
+
data.map! {|item| (item == old_item) ? new_item : item}
|
179
|
+
end
|
180
|
+
|
181
|
+
# Replaces an item in an array with a different one into the same position.
|
182
|
+
def rename_hash_key(data, old_key, new_key)
|
183
|
+
data[new_key] = data.delete(old_key)
|
184
|
+
return data
|
185
|
+
end
|
186
|
+
|
187
|
+
# Adds ":attributes!" record for Savon to specify xsi:type.
|
188
|
+
def add_xsi_type(parent, key, xsi_type)
|
189
|
+
add_attribute(parent, key, 'xsi:type', xsi_type)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Adds Savon attribute for given node, key, name and value.
|
193
|
+
def add_attribute(node, key, name, value)
|
194
|
+
node[:attributes!] ||= {}
|
195
|
+
node[:attributes!][key] ||= {}
|
196
|
+
if node[:attributes!][key].include?(name)
|
197
|
+
node[:attributes!][key][name] = arrayize(node[:attributes!][key][name])
|
198
|
+
node[:attributes!][key][name] << value
|
199
|
+
else
|
200
|
+
node[:attributes!][key][name] = value
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Prefixes a key with a given namespace index or default namespace.
|
205
|
+
def prefix_key_with_namespace(key, ns_index = nil)
|
206
|
+
namespace = (ns_index.nil?) ? DEFAULT_NAMESPACE : ("ns%d" % ns_index)
|
207
|
+
return prefix_key(key, namespace)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Prefixes with a given namespace.
|
211
|
+
def prefix_key(key, ns)
|
212
|
+
return [ns, key].join(':')
|
213
|
+
end
|
214
|
+
|
215
|
+
# Returns list of 'names' for objects in array.
|
216
|
+
def array_from_named_list(fields_list)
|
217
|
+
return fields_list.map {|field| field[:name]}
|
218
|
+
end
|
219
|
+
|
220
|
+
# Returns copy of object and its sub-objects ("deep" copy).
|
221
|
+
def deep_copy(data)
|
222
|
+
return Marshal.load(Marshal.dump(data))
|
223
|
+
end
|
224
|
+
|
225
|
+
# Returns type signature with all inherited fields.
|
226
|
+
def get_full_type_signature(type_name)
|
227
|
+
result = (type_name.nil?) ? nil : @registry.get_type_signature(type_name)
|
228
|
+
result[:fields] = implode_parent(result) if result and result[:base]
|
229
|
+
return result
|
230
|
+
end
|
231
|
+
|
232
|
+
# Returns all inherited fields of superclasses for given type.
|
233
|
+
def implode_parent(data_type)
|
234
|
+
result = []
|
235
|
+
if data_type[:base]
|
236
|
+
parent_type = @registry.get_type_signature(data_type[:base])
|
237
|
+
result += implode_parent(parent_type)
|
238
|
+
end
|
239
|
+
data_type[:fields].each do |field|
|
240
|
+
# If the parent type includes a field with the same name, overwrite it.
|
241
|
+
result.reject! {|parent_field| parent_field[:name].eql?(field[:name])}
|
242
|
+
# Storing field's namespace.
|
243
|
+
field[:ns] = data_type[:ns] if data_type[:ns]
|
244
|
+
result << field
|
245
|
+
end
|
246
|
+
return result
|
247
|
+
end
|
248
|
+
|
249
|
+
# Adds additional namespace for XML generation.
|
250
|
+
def add_extra_namespace(ns_index)
|
251
|
+
@extra_namespaces.merge!({
|
252
|
+
"xmlns:ns%d" % ns_index => @registry.get_namespace(ns_index)
|
253
|
+
})
|
254
|
+
end
|
255
|
+
|
256
|
+
# Makes sure object is an array.
|
257
|
+
def arrayize(object)
|
258
|
+
return [] if object.nil?
|
259
|
+
return object.is_a?(Array) ? object : [object]
|
260
|
+
end
|
261
|
+
|
262
|
+
# Converts Time to a hash for XML marshalling.
|
263
|
+
def time_to_xml_hash(time)
|
264
|
+
return {
|
265
|
+
:hour => time.hour, :minute => time.min, :second => time.sec,
|
266
|
+
:date => {:year => time.year, :month => time.month, :day => time.day}
|
267
|
+
}
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
@@ -87,8 +87,8 @@ module AdsCommon
|
|
87
87
|
credentials = @credential_handler.credentials(@version)
|
88
88
|
app_name = credentials[:userAgent] || credentials[:useragent]
|
89
89
|
# We don't know the library version here. A breaking change needs to be
|
90
|
-
# introduced. This is scheduled for 0.
|
91
|
-
lib_version = '0.
|
90
|
+
# introduced. This is scheduled for 0.7.0, using Common version for now.
|
91
|
+
lib_version = '0.6.0'
|
92
92
|
soap_user_agent = "Common-Ruby-%s; %s" % [lib_version, app_name]
|
93
93
|
return "Savon/%s (%s)" % [Savon::Version, soap_user_agent]
|
94
94
|
end
|
@@ -22,11 +22,10 @@
|
|
22
22
|
require 'httpi'
|
23
23
|
require 'savon'
|
24
24
|
|
25
|
+
require 'ads_common/parameters_validator'
|
26
|
+
|
25
27
|
module AdsCommon
|
26
28
|
class SavonService
|
27
|
-
# Default namespace name.
|
28
|
-
DEFAULT_NAMESPACE = 'wsdl'
|
29
|
-
|
30
29
|
# HTTP read timeout in seconds.
|
31
30
|
HTTP_READ_TIMEOUT = 15 * 60
|
32
31
|
|
@@ -81,164 +80,31 @@ module AdsCommon
|
|
81
80
|
|
82
81
|
# Executes SOAP action specified as a string with given arguments.
|
83
82
|
def execute_action(action_name, args, &block)
|
84
|
-
|
85
|
-
|
83
|
+
validator = ParametersValidator.new(get_service_registry())
|
84
|
+
args = validator.validate_args(action_name, args)
|
85
|
+
response = execute_soap_request(
|
86
|
+
action_name.to_sym, args, validator.extra_namespaces)
|
86
87
|
handle_errors(response)
|
87
88
|
return extract_result(response, action_name, &block)
|
88
89
|
end
|
89
90
|
|
90
91
|
# Executes the SOAP request with original SOAP name.
|
91
|
-
def execute_soap_request(action, args)
|
92
|
+
def execute_soap_request(action, args, extra_namespaces)
|
92
93
|
original_action_name =
|
93
94
|
get_service_registry.get_method_signature(action)[:original_name]
|
94
95
|
original_action_name = action if original_action_name.nil?
|
95
96
|
response = @client.request(original_action_name) do |soap|
|
96
|
-
set_headers(soap, args)
|
97
|
+
set_headers(soap, args, extra_namespaces)
|
97
98
|
end
|
98
99
|
return response
|
99
100
|
end
|
100
101
|
|
101
|
-
# Validates input parameters to:
|
102
|
-
# - add parameter names;
|
103
|
-
# - resolve xsi:type where required;
|
104
|
-
# - convert some native types to XML.
|
105
|
-
def validate_args(action_name, args)
|
106
|
-
validated_args = {}
|
107
|
-
in_params = get_service_registry.get_method_signature(action_name)[:input]
|
108
|
-
in_params.each_with_index do |in_param, index|
|
109
|
-
key = in_param[:name]
|
110
|
-
value = deep_copy(args[index])
|
111
|
-
validated_args[key] = (value.nil?) ?
|
112
|
-
nil : validate_arg(value, validated_args, key, in_param[:type])
|
113
|
-
# Adding :order! key to keep correct order in SOAP elements.
|
114
|
-
validated_args[:order!] =
|
115
|
-
generate_order_for_args(validated_args, in_params)
|
116
|
-
end
|
117
|
-
return validated_args
|
118
|
-
end
|
119
|
-
|
120
|
-
# Validates method argument. Runs recursively if hash or array encountered.
|
121
|
-
# Also handles some types that need special conversions.
|
122
|
-
def validate_arg(arg, parent = nil, key = nil, field_type_name = nil)
|
123
|
-
field_type = get_full_type_signature(field_type_name)
|
124
|
-
result = case arg
|
125
|
-
when Hash
|
126
|
-
validate_hash_arg(arg, parent, key, field_type)
|
127
|
-
when Array then arg.map do |item|
|
128
|
-
validate_arg(item, parent, key, field_type_name)
|
129
|
-
end
|
130
|
-
when Time then time_to_xml_hash(arg)
|
131
|
-
else arg
|
132
|
-
end
|
133
|
-
return result
|
134
|
-
end
|
135
|
-
|
136
|
-
# Generates order of XML elements for SOAP request. Returns only items
|
137
|
-
# existing in arg.
|
138
|
-
def generate_order_for_args(arg, fields)
|
139
|
-
all_keys = fields.map {|field| field[:name]}
|
140
|
-
return all_keys & arg.keys
|
141
|
-
end
|
142
|
-
|
143
|
-
# Validates hash argument recursively. Keeps tracking of correct place
|
144
|
-
# for xsi:type and adds is when required.
|
145
|
-
def validate_hash_arg(arg, parent = nil, key = nil, field_type = nil)
|
146
|
-
# Non-default namespace should be used, overriding default.
|
147
|
-
if field_type and field_type.include?(:ns)
|
148
|
-
namespace = get_service_registry.get_namespace(field_type[:ns])
|
149
|
-
key = prefix_key(key)
|
150
|
-
add_attribute(parent, key, 'xmlns', namespace)
|
151
|
-
end
|
152
|
-
|
153
|
-
# Handling custom xsi:type.
|
154
|
-
xsi_type = arg.delete('xsi:type') || arg.delete(:xsi_type)
|
155
|
-
if xsi_type
|
156
|
-
xsi_field_type = get_full_type_signature(xsi_type)
|
157
|
-
if xsi_field_type.nil?
|
158
|
-
raise AdsCommon::Errors::ApiException.new(
|
159
|
-
"Incorrect xsi:type specified: '%s'" % [xsi_type])
|
160
|
-
else
|
161
|
-
# TODO: make sure xsi_type is derived from field_type.
|
162
|
-
field_type = xsi_field_type
|
163
|
-
end
|
164
|
-
if parent and key
|
165
|
-
add_xsi_type(parent, key, xsi_type)
|
166
|
-
else
|
167
|
-
raise AdsCommon::Errors::ApiException.new(
|
168
|
-
"Can't find correct position for xsi:type (%s) [%s], [%s]" %
|
169
|
-
[xsi_type, parent, key])
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
# Adding :order! key to keep correct order in SOAP elements.
|
174
|
-
if field_type and field_type.include?(:fields)
|
175
|
-
arg[:order!] =
|
176
|
-
generate_order_for_args(arg, field_type[:fields])
|
177
|
-
end
|
178
|
-
|
179
|
-
# Processing each key-value pair.
|
180
|
-
return arg.inject({}) do |result, (k, v)|
|
181
|
-
if (k == :attributes! or k == :order!)
|
182
|
-
result[k] = v
|
183
|
-
else
|
184
|
-
subfield = (field_type.nil?) ? nil :
|
185
|
-
get_field_by_name(field_type[:fields], k)
|
186
|
-
# Here we will give up if the field is unknown. For full validation
|
187
|
-
# we have to handle nil here.
|
188
|
-
subtype_name, subtype = if subfield and subfield.include?(:type)
|
189
|
-
subtype_name = subfield[:type]
|
190
|
-
subtype = (subtype_name.nil?) ? nil :
|
191
|
-
get_service_registry.get_type_signature(subtype_name)
|
192
|
-
[subtype_name, subtype]
|
193
|
-
end
|
194
|
-
# In case of non-default namespace, the children should be in
|
195
|
-
# overridden namespace but the node has to be in the default.
|
196
|
-
# We also have to fix order! list if we alter the key name.
|
197
|
-
new_key = if (subtype and subtype[:ns])
|
198
|
-
prefixed_key = prefix_key(k)
|
199
|
-
replace_item!(arg[:order!], k, prefixed_key)
|
200
|
-
prefixed_key
|
201
|
-
else
|
202
|
-
k
|
203
|
-
end
|
204
|
-
result[new_key] = validate_arg(v, result, k, subtype_name)
|
205
|
-
end
|
206
|
-
result
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
# Replaces an item in an array with a different one into the same position.
|
211
|
-
def replace_item!(data, old_item, new_item)
|
212
|
-
data.map! {|item| (item == old_item) ? new_item : item}
|
213
|
-
end
|
214
|
-
|
215
|
-
# Adds ":attributes!" record for Savon to specify xsi:type.
|
216
|
-
def add_xsi_type(parent, key, xsi_type)
|
217
|
-
add_attribute(parent, key, 'xsi:type', xsi_type)
|
218
|
-
end
|
219
|
-
|
220
|
-
# Adds Savon attribute for given node, key, name and value.
|
221
|
-
def add_attribute(node, key, name, value)
|
222
|
-
node[:attributes!] ||= {}
|
223
|
-
node[:attributes!][key] ||= {}
|
224
|
-
if node[:attributes!][key].include?(name)
|
225
|
-
node[:attributes!][key][name] = arrayize(node[:attributes!][key][name])
|
226
|
-
node[:attributes!][key][name] << value
|
227
|
-
else
|
228
|
-
node[:attributes!][key][name] = value
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# Prefixes default namespace.
|
233
|
-
def prefix_key(key)
|
234
|
-
return "%s:%s" % [DEFAULT_NAMESPACE, key.to_s.lower_camelcase]
|
235
|
-
end
|
236
|
-
|
237
102
|
# Executes each handler to generate SOAP headers.
|
238
|
-
def set_headers(soap, args)
|
103
|
+
def set_headers(soap, args, extra_namespaces)
|
239
104
|
@headerhandler.each do |handler|
|
240
105
|
handler.prepare_request(@client.http, soap, args)
|
241
106
|
end
|
107
|
+
soap.namespaces.merge!(extra_namespaces) unless extra_namespaces.nil?
|
242
108
|
end
|
243
109
|
|
244
110
|
# Checks for errors in response and raises appropriate exception.
|
@@ -405,14 +271,6 @@ module AdsCommon
|
|
405
271
|
return object.is_a?(Array) ? object : [object]
|
406
272
|
end
|
407
273
|
|
408
|
-
# Converts Time to a hash for XML marshalling.
|
409
|
-
def time_to_xml_hash(time)
|
410
|
-
return {
|
411
|
-
:hour => time.hour, :minute => time.min, :second => time.sec,
|
412
|
-
:date => {:year => time.year, :month => time.month, :day => time.day}
|
413
|
-
}
|
414
|
-
end
|
415
|
-
|
416
274
|
# Returns all inherited fields of superclasses for given type.
|
417
275
|
def implode_parent(data_type)
|
418
276
|
result = []
|
@@ -428,11 +286,6 @@ module AdsCommon
|
|
428
286
|
return result
|
429
287
|
end
|
430
288
|
|
431
|
-
# Returns copy of object and its sub-objects ("deep" copy).
|
432
|
-
def deep_copy(data)
|
433
|
-
return Marshal.load(Marshal.dump(data))
|
434
|
-
end
|
435
|
-
|
436
289
|
# Returns type signature with all inherited fields.
|
437
290
|
def get_full_type_signature(type_name)
|
438
291
|
result = (type_name.nil?) ? nil :
|
@@ -0,0 +1,103 @@
|
|
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
|
+
# Tests validator methods.
|
21
|
+
|
22
|
+
require 'rubygems'
|
23
|
+
require 'test/unit'
|
24
|
+
|
25
|
+
require 'ads_common/parameters_validator'
|
26
|
+
|
27
|
+
module AdsCommon
|
28
|
+
class ParametersValidator
|
29
|
+
public :deep_copy, :add_attribute, :array_from_named_list
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class TestParametersValidator < Test::Unit::TestCase
|
34
|
+
def setup
|
35
|
+
@validator = AdsCommon::ParametersValidator.new(nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_deep_copy_simple
|
39
|
+
result1 = @validator.deep_copy(42)
|
40
|
+
assert_equal(42, result1)
|
41
|
+
|
42
|
+
result2 = @validator.deep_copy('Hello World')
|
43
|
+
assert_equal('Hello World', result2)
|
44
|
+
|
45
|
+
result3 = @validator.deep_copy(nil)
|
46
|
+
assert_nil(result3)
|
47
|
+
|
48
|
+
result4 = @validator.deep_copy([])
|
49
|
+
assert_equal([], result4)
|
50
|
+
assert_not_same([], result4)
|
51
|
+
|
52
|
+
result5 = @validator.deep_copy({})
|
53
|
+
assert_equal({}, result5)
|
54
|
+
assert_not_same({}, result5)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_deep_copy_complex
|
58
|
+
data = {:ab => 'ab', :cd => ['cd', 'de', 'ef']}
|
59
|
+
|
60
|
+
result1 = @validator.deep_copy(data)
|
61
|
+
assert_equal(data, result1)
|
62
|
+
assert_not_same(data, result1)
|
63
|
+
|
64
|
+
result2 = @validator.deep_copy(data)
|
65
|
+
assert_equal(result2, result1)
|
66
|
+
assert_not_same(result2, result1)
|
67
|
+
|
68
|
+
result2[:cd] = nil
|
69
|
+
assert_not_equal(data, result2)
|
70
|
+
assert_equal(data, result1)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_add_attribute
|
74
|
+
node = {}
|
75
|
+
|
76
|
+
key, name, value1, value2, value3 = 'key', 'name', 'Lorem', 'ipsum', 'dolor'
|
77
|
+
|
78
|
+
@validator.add_attribute(node, key, name, value1)
|
79
|
+
assert_kind_of(Hash, node)
|
80
|
+
assert_kind_of(Hash, node[:attributes!])
|
81
|
+
assert_kind_of(Hash, node[:attributes!][key])
|
82
|
+
assert_equal(value1, node[:attributes!][key][name])
|
83
|
+
|
84
|
+
@validator.add_attribute(node, key, name, value2)
|
85
|
+
assert_kind_of(Hash, node)
|
86
|
+
assert_kind_of(Hash, node[:attributes!])
|
87
|
+
assert_kind_of(Hash, node[:attributes!][key])
|
88
|
+
assert_kind_of(Array, node[:attributes!][key][name])
|
89
|
+
assert_equal(value1, node[:attributes!][key][name][0])
|
90
|
+
assert_equal(value2, node[:attributes!][key][name][1])
|
91
|
+
|
92
|
+
@validator.add_attribute(node, key, name, value3)
|
93
|
+
assert_equal(value1, node[:attributes!][key][name][0])
|
94
|
+
assert_equal(value2, node[:attributes!][key][name][1])
|
95
|
+
assert_equal(value3, node[:attributes!][key][name][2])
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_array_from_named_list
|
99
|
+
src = [{:name => 'foo'}, {:name => 'bar', :bar => :baz}, {:name => 'ipsum'}]
|
100
|
+
result = @validator.array_from_named_list(src)
|
101
|
+
assert_equal(['foo', 'bar', 'ipsum'], result)
|
102
|
+
end
|
103
|
+
end
|
data/test/test_savon_service.rb
CHANGED
@@ -219,64 +219,4 @@ class TestSavonService < Test::Unit::TestCase
|
|
219
219
|
assert_instance_of(Float, result4)
|
220
220
|
assert_equal(42.0, result4, 'Float is expected for nil max_occurs')
|
221
221
|
end
|
222
|
-
|
223
|
-
def test_deep_copy_simple
|
224
|
-
result1 = @stub_service.private_deep_copy(42)
|
225
|
-
assert_equal(42, result1)
|
226
|
-
|
227
|
-
result2 = @stub_service.private_deep_copy('Hello World')
|
228
|
-
assert_equal('Hello World', result2)
|
229
|
-
|
230
|
-
result3 = @stub_service.private_deep_copy(nil)
|
231
|
-
assert_nil(result3)
|
232
|
-
|
233
|
-
result4 = @stub_service.private_deep_copy([])
|
234
|
-
assert_equal([], result4)
|
235
|
-
assert_not_same([], result4)
|
236
|
-
|
237
|
-
result5 = @stub_service.private_deep_copy({})
|
238
|
-
assert_equal({}, result5)
|
239
|
-
assert_not_same({}, result5)
|
240
|
-
end
|
241
|
-
|
242
|
-
def test_deep_copy_complex
|
243
|
-
data = {:ab => 'ab', :cd => ['cd', 'de', 'ef']}
|
244
|
-
|
245
|
-
result1 = @stub_service.private_deep_copy(data)
|
246
|
-
assert_equal(data, result1)
|
247
|
-
assert_not_same(data, result1)
|
248
|
-
|
249
|
-
result2 = @stub_service.private_deep_copy(data)
|
250
|
-
assert_equal(result2, result1)
|
251
|
-
assert_not_same(result2, result1)
|
252
|
-
|
253
|
-
result2[:cd] = nil
|
254
|
-
assert_not_equal(data, result2)
|
255
|
-
assert_equal(data, result1)
|
256
|
-
end
|
257
|
-
|
258
|
-
def test_add_attribute
|
259
|
-
node = {}
|
260
|
-
|
261
|
-
key, name, value1, value2, value3 = 'key', 'name', 'Lorem', 'ipsum', 'dolor'
|
262
|
-
|
263
|
-
@stub_service.private_add_attribute(node, key, name, value1)
|
264
|
-
assert_kind_of(Hash, node)
|
265
|
-
assert_kind_of(Hash, node[:attributes!])
|
266
|
-
assert_kind_of(Hash, node[:attributes!][key])
|
267
|
-
assert_equal(value1, node[:attributes!][key][name])
|
268
|
-
|
269
|
-
@stub_service.private_add_attribute(node, key, name, value2)
|
270
|
-
assert_kind_of(Hash, node)
|
271
|
-
assert_kind_of(Hash, node[:attributes!])
|
272
|
-
assert_kind_of(Hash, node[:attributes!][key])
|
273
|
-
assert_kind_of(Array, node[:attributes!][key][name])
|
274
|
-
assert_equal(value1, node[:attributes!][key][name][0])
|
275
|
-
assert_equal(value2, node[:attributes!][key][name][1])
|
276
|
-
|
277
|
-
@stub_service.private_add_attribute(node, key, name, value3)
|
278
|
-
assert_equal(value1, node[:attributes!][key][name][0])
|
279
|
-
assert_equal(value2, node[:attributes!][key][name][1])
|
280
|
-
assert_equal(value3, node[:attributes!][key][name][2])
|
281
|
-
end
|
282
222
|
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 6
|
8
|
+
- 0
|
9
|
+
version: 0.6.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Sergio Gomes
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-12-02 00:00:00 +04:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -89,27 +89,29 @@ extra_rdoc_files:
|
|
89
89
|
- COPYING
|
90
90
|
- ChangeLog
|
91
91
|
files:
|
92
|
-
- lib/ads_common/auth/base_handler.rb
|
93
92
|
- lib/ads_common/auth/client_login_handler.rb
|
93
|
+
- lib/ads_common/auth/base_handler.rb
|
94
94
|
- lib/ads_common/auth/oauth_handler.rb
|
95
|
-
- lib/ads_common/
|
95
|
+
- lib/ads_common/credential_handler.rb
|
96
96
|
- lib/ads_common/build/savon_generator.rb
|
97
|
-
- lib/ads_common/build/savon_registry.rb
|
98
97
|
- lib/ads_common/build/savon_registry_generator.rb
|
98
|
+
- lib/ads_common/build/savon_registry.rb
|
99
99
|
- lib/ads_common/build/savon_service_generator.rb
|
100
|
-
- lib/ads_common/
|
101
|
-
- lib/ads_common/savon_headers/httpi_request_proxy.rb
|
102
|
-
- lib/ads_common/savon_headers/oauth_header_handler.rb
|
103
|
-
- lib/ads_common/savon_headers/simple_header_handler.rb
|
100
|
+
- lib/ads_common/build/savon_abstract_generator.rb
|
104
101
|
- lib/ads_common/api_config.rb
|
105
|
-
- lib/ads_common/api.rb
|
106
102
|
- lib/ads_common/config.rb
|
107
|
-
- lib/ads_common/
|
108
|
-
- lib/ads_common/
|
109
|
-
- lib/ads_common/
|
103
|
+
- lib/ads_common/api.rb
|
104
|
+
- lib/ads_common/savon_headers/base_header_handler.rb
|
105
|
+
- lib/ads_common/savon_headers/simple_header_handler.rb
|
106
|
+
- lib/ads_common/savon_headers/oauth_header_handler.rb
|
107
|
+
- lib/ads_common/savon_headers/httpi_request_proxy.rb
|
110
108
|
- lib/ads_common/savon_service.rb
|
109
|
+
- lib/ads_common/http.rb
|
110
|
+
- lib/ads_common/parameters_validator.rb
|
111
|
+
- lib/ads_common/errors.rb
|
111
112
|
- Rakefile
|
112
113
|
- test/test_config.rb
|
114
|
+
- test/test_parameters_validator.rb
|
113
115
|
- test/test_savon_service.rb
|
114
116
|
- README
|
115
117
|
- COPYING
|
@@ -148,4 +150,5 @@ specification_version: 3
|
|
148
150
|
summary: Common code for Google Ads APIs.
|
149
151
|
test_files:
|
150
152
|
- test/test_config.rb
|
153
|
+
- test/test_parameters_validator.rb
|
151
154
|
- test/test_savon_service.rb
|