google-ads-common 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|