savon-SU 2.11.1b

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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.travis.yml +15 -0
  4. data/.yardopts +6 -0
  5. data/CHANGELOG.md +1098 -0
  6. data/CONTRIBUTING.md +46 -0
  7. data/Gemfile +18 -0
  8. data/LICENSE +20 -0
  9. data/README.md +69 -0
  10. data/Rakefile +14 -0
  11. data/donate.png +0 -0
  12. data/lib/savon.rb +27 -0
  13. data/lib/savon/block_interface.rb +26 -0
  14. data/lib/savon/builder.rb +205 -0
  15. data/lib/savon/client.rb +102 -0
  16. data/lib/savon/core_ext/string.rb +29 -0
  17. data/lib/savon/header.rb +93 -0
  18. data/lib/savon/http_error.rb +27 -0
  19. data/lib/savon/log_message.rb +52 -0
  20. data/lib/savon/message.rb +37 -0
  21. data/lib/savon/mock.rb +5 -0
  22. data/lib/savon/mock/expectation.rb +80 -0
  23. data/lib/savon/mock/spec_helper.rb +62 -0
  24. data/lib/savon/model.rb +84 -0
  25. data/lib/savon/operation.rb +144 -0
  26. data/lib/savon/options.rb +410 -0
  27. data/lib/savon/qualified_message.rb +50 -0
  28. data/lib/savon/request.rb +97 -0
  29. data/lib/savon/request_logger.rb +48 -0
  30. data/lib/savon/response.rb +113 -0
  31. data/lib/savon/soap_fault.rb +50 -0
  32. data/lib/savon/version.rb +3 -0
  33. data/savon-SU.gemspec +46 -0
  34. data/spec/fixtures/gzip/message.gz +0 -0
  35. data/spec/fixtures/response/another_soap_fault.xml +14 -0
  36. data/spec/fixtures/response/authentication.xml +14 -0
  37. data/spec/fixtures/response/f5.xml +39 -0
  38. data/spec/fixtures/response/header.xml +13 -0
  39. data/spec/fixtures/response/list.xml +18 -0
  40. data/spec/fixtures/response/multi_ref.xml +39 -0
  41. data/spec/fixtures/response/soap_fault.xml +8 -0
  42. data/spec/fixtures/response/soap_fault12.xml +18 -0
  43. data/spec/fixtures/response/soap_fault_funky.xml +8 -0
  44. data/spec/fixtures/response/taxcloud.xml +1 -0
  45. data/spec/fixtures/ssl/client_cert.pem +16 -0
  46. data/spec/fixtures/ssl/client_encrypted_key.pem +30 -0
  47. data/spec/fixtures/ssl/client_encrypted_key_cert.pem +24 -0
  48. data/spec/fixtures/ssl/client_key.pem +15 -0
  49. data/spec/fixtures/wsdl/authentication.xml +63 -0
  50. data/spec/fixtures/wsdl/betfair.xml +2981 -0
  51. data/spec/fixtures/wsdl/edialog.xml +15416 -0
  52. data/spec/fixtures/wsdl/interhome.xml +2137 -0
  53. data/spec/fixtures/wsdl/lower_camel.xml +52 -0
  54. data/spec/fixtures/wsdl/multiple_namespaces.xml +92 -0
  55. data/spec/fixtures/wsdl/multiple_types.xml +60 -0
  56. data/spec/fixtures/wsdl/no_message_tag.xml +1267 -0
  57. data/spec/fixtures/wsdl/taxcloud.xml +934 -0
  58. data/spec/fixtures/wsdl/team_software.xml +1 -0
  59. data/spec/fixtures/wsdl/vies.xml +176 -0
  60. data/spec/fixtures/wsdl/wasmuth.xml +153 -0
  61. data/spec/integration/centra_spec.rb +66 -0
  62. data/spec/integration/email_example_spec.rb +32 -0
  63. data/spec/integration/random_quote_spec.rb +23 -0
  64. data/spec/integration/ratp_example_spec.rb +28 -0
  65. data/spec/integration/stockquote_example_spec.rb +28 -0
  66. data/spec/integration/support/application.rb +82 -0
  67. data/spec/integration/support/server.rb +84 -0
  68. data/spec/integration/temperature_example_spec.rb +46 -0
  69. data/spec/integration/zipcode_example_spec.rb +42 -0
  70. data/spec/savon/builder_spec.rb +137 -0
  71. data/spec/savon/client_spec.rb +271 -0
  72. data/spec/savon/core_ext/string_spec.rb +37 -0
  73. data/spec/savon/features/message_tag_spec.rb +61 -0
  74. data/spec/savon/http_error_spec.rb +49 -0
  75. data/spec/savon/log_message_spec.rb +44 -0
  76. data/spec/savon/message_spec.rb +70 -0
  77. data/spec/savon/mock_spec.rb +174 -0
  78. data/spec/savon/model_spec.rb +182 -0
  79. data/spec/savon/observers_spec.rb +92 -0
  80. data/spec/savon/operation_spec.rb +230 -0
  81. data/spec/savon/options_spec.rb +1064 -0
  82. data/spec/savon/qualified_message_spec.rb +20 -0
  83. data/spec/savon/request_logger_spec.rb +37 -0
  84. data/spec/savon/request_spec.rb +496 -0
  85. data/spec/savon/response_spec.rb +270 -0
  86. data/spec/savon/soap_fault_spec.rb +131 -0
  87. data/spec/spec_helper.rb +30 -0
  88. data/spec/support/adapters.rb +48 -0
  89. data/spec/support/endpoint.rb +25 -0
  90. data/spec/support/fixture.rb +39 -0
  91. data/spec/support/integration.rb +9 -0
  92. data/spec/support/stdout.rb +25 -0
  93. metadata +317 -0
@@ -0,0 +1,144 @@
1
+ require "savon/options"
2
+ require "savon/block_interface"
3
+ require "savon/request"
4
+ require "savon/builder"
5
+ require "savon/response"
6
+ require "savon/request_logger"
7
+ require "savon/http_error"
8
+
9
+ module Savon
10
+ class Operation
11
+
12
+ def self.create(operation_name, wsdl, globals)
13
+ if wsdl.document?
14
+ ensure_name_is_symbol! operation_name
15
+ ensure_exists! operation_name, wsdl
16
+ end
17
+
18
+ new(operation_name, wsdl, globals)
19
+ end
20
+
21
+ def self.ensure_exists!(operation_name, wsdl)
22
+ unless wsdl.soap_actions.include? operation_name
23
+ raise UnknownOperationError, "Unable to find SOAP operation: #{operation_name.inspect}\n" \
24
+ "Operations provided by your service: #{wsdl.soap_actions.inspect}"
25
+ end
26
+ rescue Wasabi::Resolver::HTTPError => e
27
+ raise HTTPError.new(e.response)
28
+ end
29
+
30
+ def self.ensure_name_is_symbol!(operation_name)
31
+ unless operation_name.kind_of? Symbol
32
+ raise ArgumentError, "Expected the first parameter (the name of the operation to call) to be a symbol\n" \
33
+ "Actual: #{operation_name.inspect} (#{operation_name.class})"
34
+ end
35
+ end
36
+
37
+ def initialize(name, wsdl, globals)
38
+ @name = name
39
+ @wsdl = wsdl
40
+ @globals = globals
41
+
42
+ @logger = RequestLogger.new(globals)
43
+ end
44
+
45
+ def build(locals = {}, &block)
46
+ set_locals(locals, block)
47
+ Builder.new(@name, @wsdl, @globals, @locals)
48
+ end
49
+
50
+ def call(locals = {}, &block)
51
+ builder = build(locals, &block)
52
+
53
+ response = Savon.notify_observers(@name, builder, @globals, @locals)
54
+ response ||= call_with_logging build_request(builder)
55
+
56
+ raise_expected_httpi_response! unless response.kind_of?(HTTPI::Response)
57
+
58
+ create_response(response)
59
+ end
60
+
61
+ def request(locals = {}, &block)
62
+ builder = build(locals, &block)
63
+ build_request(builder)
64
+ end
65
+
66
+ private
67
+
68
+ def create_response(response)
69
+ if multipart_supported?
70
+ Multipart::Response.new(response, @globals, @locals)
71
+ else
72
+ Response.new(response, @globals, @locals)
73
+ end
74
+ end
75
+
76
+ def multipart_supported?
77
+ return false unless @globals[:multipart] || @locals[:multipart]
78
+
79
+ if Savon.const_defined? :Multipart
80
+ true
81
+ else
82
+ raise 'Unable to find Savon::Multipart. Make sure the savon-multipart gem is installed and loaded.'
83
+ end
84
+ end
85
+
86
+ def set_locals(locals, block)
87
+ locals = LocalOptions.new(locals)
88
+ BlockInterface.new(locals).evaluate(block) if block
89
+
90
+ @locals = locals
91
+ end
92
+
93
+ def call_with_logging(request)
94
+ @logger.log(request) { HTTPI.post(request, @globals[:adapter]) }
95
+ end
96
+
97
+ def build_request(builder)
98
+ @locals[:soap_action] ||= soap_action
99
+ @globals[:endpoint] ||= endpoint
100
+
101
+ request = SOAPRequest.new(@globals).build(
102
+ :soap_action => soap_action,
103
+ :cookies => @locals[:cookies]
104
+ )
105
+
106
+ request.url = endpoint
107
+ request.body = builder.to_s
108
+
109
+ # TODO: could HTTPI do this automatically in case the header
110
+ # was not specified manually? [dh, 2013-01-04]
111
+ request.headers["Content-Length"] = request.body.bytesize.to_s
112
+
113
+ request
114
+ end
115
+
116
+ def soap_action
117
+ # soap_action explicitly set to something falsy
118
+ return if @locals.include?(:soap_action) && !@locals[:soap_action]
119
+
120
+ # get the soap_action from local options
121
+ soap_action = @locals[:soap_action]
122
+ # with no local option, but a wsdl, ask it for the soap_action
123
+ soap_action ||= @wsdl.soap_action(@name.to_sym) if @wsdl.document?
124
+ # if there is no soap_action up to this point, fallback to a simple default
125
+ soap_action ||= Gyoku.xml_tag(@name, :key_converter => @globals[:convert_request_keys_to])
126
+ end
127
+
128
+ def endpoint
129
+ @globals[:endpoint] || @wsdl.endpoint.tap do |url|
130
+ if @globals[:host]
131
+ host_url = URI.parse(@globals[:host])
132
+ url.host = host_url.host
133
+ url.port = host_url.port
134
+ end
135
+ end
136
+ end
137
+
138
+ def raise_expected_httpi_response!
139
+ raise Error, "Observers need to return an HTTPI::Response to mock " \
140
+ "the request or nil to execute the request."
141
+ end
142
+
143
+ end
144
+ end
@@ -0,0 +1,410 @@
1
+ require "logger"
2
+ require "httpi"
3
+
4
+ module Savon
5
+ class Options
6
+
7
+ def initialize(options = {})
8
+ @options = {}
9
+ assign options
10
+ end
11
+
12
+ attr_reader :option_type
13
+
14
+ def [](option)
15
+ @options[option]
16
+ end
17
+
18
+ def []=(option, value)
19
+ value = [value].flatten
20
+ self.send(option, *value)
21
+ end
22
+
23
+ def include?(option)
24
+ @options.key? option
25
+ end
26
+
27
+ private
28
+
29
+ def assign(options)
30
+ options.each do |option, value|
31
+ self.send(option, value)
32
+ end
33
+ end
34
+
35
+ def method_missing(option, _)
36
+ raise UnknownOptionError, "Unknown #{option_type} option: #{option.inspect}"
37
+ end
38
+ end
39
+
40
+ module SharedOptions
41
+ # WSSE auth credentials for Akami.
42
+ # Local will override the global wsse_auth value, e.g.
43
+ # global == [user, pass] && local == [user2, pass2] => [user2, pass2]
44
+ # global == [user, pass] && local == false => false
45
+ # global == [user, pass] && local == nil => [user, pass]
46
+ def wsse_auth(*credentials)
47
+ credentials.flatten!
48
+ if credentials.size == 1
49
+ @options[:wsse_auth] = credentials.first
50
+ else
51
+ @options[:wsse_auth] = credentials
52
+ end
53
+ end
54
+
55
+ # Instruct Akami to enable wsu:Timestamp headers.
56
+ # Local will override the global wsse_timestamp value, e.g.
57
+ # global == true && local == true => true
58
+ # global == true && local == false => false
59
+ # global == true && local == nil => true
60
+ def wsse_timestamp(timestamp = true)
61
+ @options[:wsse_timestamp] = timestamp
62
+ end
63
+
64
+ def wsse_signature(signature)
65
+ @options[:wsse_signature] = signature
66
+ end
67
+ end
68
+
69
+ class GlobalOptions < Options
70
+ include SharedOptions
71
+
72
+ def initialize(options = {})
73
+ @option_type = :global
74
+
75
+ defaults = {
76
+ :encoding => "UTF-8",
77
+ :soap_version => 1,
78
+ :namespaces => {},
79
+ #:logger => Logger.new($stdout),
80
+ :log => false,
81
+ :filters => [],
82
+ :pretty_print_xml => false,
83
+ :raise_errors => true,
84
+ :strip_namespaces => true,
85
+ :convert_response_tags_to => lambda { |tag| tag.snakecase.to_sym},
86
+ :convert_attributes_to => lambda { |k,v| [k,v] },
87
+ :multipart => false,
88
+ :adapter => nil,
89
+ :use_wsa_headers => false,
90
+ :no_message_tag => false,
91
+ :follow_redirects => false,
92
+ :unwrap => false,
93
+ :host => nil
94
+ }
95
+
96
+ options = defaults.merge(options)
97
+
98
+ # this option is a shortcut on the logger which needs to be set
99
+ # before it can be modified to set the option.
100
+ delayed_level = options.delete(:log_level)
101
+
102
+ super(options)
103
+
104
+ log_level(delayed_level) unless delayed_level.nil?
105
+ end
106
+
107
+ # Location of the local or remote WSDL document.
108
+ def wsdl(wsdl_address)
109
+ @options[:wsdl] = wsdl_address
110
+ end
111
+
112
+ # set different host for actions in WSDL
113
+ def host(host)
114
+ @options[:host] = host
115
+ end
116
+
117
+ # SOAP endpoint.
118
+ def endpoint(endpoint)
119
+ @options[:endpoint] = endpoint
120
+ end
121
+
122
+ # Target namespace.
123
+ def namespace(namespace)
124
+ @options[:namespace] = namespace
125
+ end
126
+
127
+ # The namespace identifer.
128
+ def namespace_identifier(identifier)
129
+ @options[:namespace_identifier] = identifier
130
+ end
131
+
132
+ # Namespaces for the SOAP envelope.
133
+ def namespaces(namespaces)
134
+ @options[:namespaces] = namespaces
135
+ end
136
+
137
+ # Proxy server to use for all requests.
138
+ def proxy(proxy)
139
+ @options[:proxy] = proxy
140
+ end
141
+
142
+ # A Hash of HTTP headers.
143
+ def headers(headers)
144
+ @options[:headers] = headers
145
+ end
146
+
147
+ # Open timeout in seconds.
148
+ def open_timeout(open_timeout)
149
+ @options[:open_timeout] = open_timeout
150
+ end
151
+
152
+ # Read timeout in seconds.
153
+ def read_timeout(read_timeout)
154
+ @options[:read_timeout] = read_timeout
155
+ end
156
+
157
+ # The encoding to use. Defaults to "UTF-8".
158
+ def encoding(encoding)
159
+ @options[:encoding] = encoding
160
+ end
161
+
162
+ # The global SOAP header. Expected to be a Hash or responding to #to_s.
163
+ def soap_header(header)
164
+ @options[:soap_header] = header
165
+ end
166
+
167
+ # Sets whether elements should be :qualified or :unqualified.
168
+ # If you need to use this option, please open an issue and make
169
+ # sure to add your WSDL document for debugging.
170
+ def element_form_default(element_form_default)
171
+ @options[:element_form_default] = element_form_default
172
+ end
173
+
174
+ # Can be used to change the SOAP envelope namespace identifier.
175
+ # If you need to use this option, please open an issue and make
176
+ # sure to add your WSDL document for debugging.
177
+ def env_namespace(env_namespace)
178
+ @options[:env_namespace] = env_namespace
179
+ end
180
+
181
+ # Changes the SOAP version to 1 or 2.
182
+ def soap_version(soap_version)
183
+ @options[:soap_version] = soap_version
184
+ end
185
+
186
+ # Whether or not to raise SOAP fault and HTTP errors.
187
+ def raise_errors(raise_errors)
188
+ @options[:raise_errors] = raise_errors
189
+ end
190
+
191
+ # Whether or not to log.
192
+ def log(log)
193
+ HTTPI.log = log
194
+ @options[:log] = log
195
+ end
196
+
197
+ # The logger to use. Defaults to a Savon::Logger instance.
198
+ def logger(logger)
199
+ HTTPI.logger = logger
200
+ @options[:logger] = logger
201
+ end
202
+
203
+ # Changes the Logger's log level.
204
+ def log_level(level)
205
+ levels = { :debug => 0, :info => 1, :warn => 2, :error => 3, :fatal => 4 }
206
+
207
+ unless levels.include? level
208
+ raise ArgumentError, "Invalid log level: #{level.inspect}\n" \
209
+ "Expected one of: #{levels.keys.inspect}"
210
+ end
211
+
212
+ @options[:logger].level = levels[level]
213
+ end
214
+
215
+ # A list of XML tags to filter from logged SOAP messages.
216
+ def filters(*filters)
217
+ @options[:filters] = filters.flatten
218
+ end
219
+
220
+ # Whether to pretty print request and response XML log messages.
221
+ def pretty_print_xml(pretty_print_xml)
222
+ @options[:pretty_print_xml] = pretty_print_xml
223
+ end
224
+
225
+ # Specifies the SSL version to use.
226
+ def ssl_version(version)
227
+ @options[:ssl_version] = version
228
+ end
229
+
230
+ # Whether and how to to verify the connection.
231
+ def ssl_verify_mode(verify_mode)
232
+ @options[:ssl_verify_mode] = verify_mode
233
+ end
234
+
235
+ # Sets the cert key file to use.
236
+ def ssl_cert_key_file(file)
237
+ @options[:ssl_cert_key_file] = file
238
+ end
239
+
240
+ # Sets the cert key to use.
241
+ def ssl_cert_key(key)
242
+ @options[:ssl_cert_key] = key
243
+ end
244
+
245
+ # Sets the cert key password to use.
246
+ def ssl_cert_key_password(password)
247
+ @options[:ssl_cert_key_password] = password
248
+ end
249
+
250
+ # Sets the cert file to use.
251
+ def ssl_cert_file(file)
252
+ @options[:ssl_cert_file] = file
253
+ end
254
+
255
+ # Sets the cert to use.
256
+ def ssl_cert(cert)
257
+ @options[:ssl_cert] = cert
258
+ end
259
+
260
+ # Sets the ca cert file to use.
261
+ def ssl_ca_cert_file(file)
262
+ @options[:ssl_ca_cert_file] = file
263
+ end
264
+
265
+ # Sets the ca cert to use.
266
+ def ssl_ca_cert(cert)
267
+ @options[:ssl_ca_cert] = cert
268
+ end
269
+
270
+
271
+ # HTTP basic auth credentials.
272
+ def basic_auth(*credentials)
273
+ @options[:basic_auth] = credentials.flatten
274
+ end
275
+
276
+ # HTTP digest auth credentials.
277
+ def digest_auth(*credentials)
278
+ @options[:digest_auth] = credentials.flatten
279
+ end
280
+
281
+ # NTLM auth credentials.
282
+ def ntlm(*credentials)
283
+ @options[:ntlm] = credentials.flatten
284
+ end
285
+
286
+ # Instruct Nori whether to strip namespaces from XML nodes.
287
+ def strip_namespaces(strip_namespaces)
288
+ @options[:strip_namespaces] = strip_namespaces
289
+ end
290
+
291
+ # Tell Gyoku how to convert Hash key Symbols to XML tags.
292
+ # Accepts one of :lower_camelcase, :camelcase, :upcase, or :none.
293
+ def convert_request_keys_to(converter)
294
+ @options[:convert_request_keys_to] = converter
295
+ end
296
+
297
+ # Tell Gyoku to unwrap Array of Hashes
298
+ # Accepts a boolean, default to false
299
+ def unwrap(unwrap)
300
+ @options[:unwrap] = unwrap
301
+ end
302
+
303
+ # Tell Nori how to convert XML tags from the SOAP response into Hash keys.
304
+ # Accepts a lambda or a block which receives an XML tag and returns a Hash key.
305
+ # Defaults to convert tags to snakecase Symbols.
306
+ def convert_response_tags_to(converter = nil, &block)
307
+ @options[:convert_response_tags_to] = block || converter
308
+ end
309
+
310
+ # Tell Nori how to convert XML attributes on tags from the SOAP response into Hash keys.
311
+ # Accepts a lambda or a block which receives an XML tag and returns a Hash key.
312
+ # Defaults to doing nothing
313
+ def convert_attributes_to(converter = nil, &block)
314
+ @options[:convert_attributes_to] = block || converter
315
+ end
316
+
317
+ # Instruct Savon to create a multipart response if available.
318
+ def multipart(multipart)
319
+ @options[:multipart] = multipart
320
+ end
321
+
322
+ # Instruct Savon what HTTPI adapter it should use instead of default
323
+ def adapter(adapter)
324
+ @options[:adapter] = adapter
325
+ end
326
+
327
+ # Enable inclusion of WS-Addressing headers.
328
+ def use_wsa_headers(use)
329
+ @options[:use_wsa_headers] = use
330
+ end
331
+
332
+ def no_message_tag(bool)
333
+ @options[:no_message_tag] = bool
334
+ end
335
+
336
+ # Instruct requests to follow HTTP redirects.
337
+ def follow_redirects(follow_redirects)
338
+ @options[:follow_redirects] = follow_redirects
339
+ end
340
+ end
341
+
342
+ class LocalOptions < Options
343
+ include SharedOptions
344
+
345
+ def initialize(options = {})
346
+ @option_type = :local
347
+
348
+ defaults = {
349
+ :advanced_typecasting => true,
350
+ :response_parser => :nokogiri,
351
+ :multipart => false
352
+ }
353
+
354
+ super defaults.merge(options)
355
+ end
356
+
357
+ # The local SOAP header. Expected to be a Hash or respond to #to_s.
358
+ # Will be merged with the global SOAP header if both are Hashes.
359
+ # Otherwise the local option will be prefered.
360
+ def soap_header(header)
361
+ @options[:soap_header] = header
362
+ end
363
+
364
+ # The SOAP message to send. Expected to be a Hash or a String.
365
+ def message(message)
366
+ @options[:message] = message
367
+ end
368
+
369
+ # SOAP message tag (formerly known as SOAP input tag). If it's not set, Savon retrieves the name from
370
+ # the WSDL document (if available). Otherwise, Gyoku converts the operation name into an XML element.
371
+ def message_tag(message_tag)
372
+ @options[:message_tag] = message_tag
373
+ end
374
+
375
+ # Attributes for the SOAP message tag.
376
+ def attributes(attributes)
377
+ @options[:attributes] = attributes
378
+ end
379
+
380
+ # Value of the SOAPAction HTTP header.
381
+ def soap_action(soap_action)
382
+ @options[:soap_action] = soap_action
383
+ end
384
+
385
+ # Cookies to be used for the next request.
386
+ def cookies(cookies)
387
+ @options[:cookies] = cookies
388
+ end
389
+
390
+ # The SOAP request XML to send. Expected to be a String.
391
+ def xml(xml)
392
+ @options[:xml] = xml
393
+ end
394
+
395
+ # Instruct Nori to use advanced typecasting.
396
+ def advanced_typecasting(advanced)
397
+ @options[:advanced_typecasting] = advanced
398
+ end
399
+
400
+ # Instruct Nori to use :rexml or :nokogiri to parse the response.
401
+ def response_parser(parser)
402
+ @options[:response_parser] = parser
403
+ end
404
+
405
+ # Instruct Savon to create a multipart response if available.
406
+ def multipart(multipart)
407
+ @options[:multipart] = multipart
408
+ end
409
+ end
410
+ end