they-savon 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.gitignore +10 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +13 -0
  4. data/CHANGELOG.md +631 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE +20 -0
  7. data/README.md +42 -0
  8. data/Rakefile +7 -0
  9. data/lib/savon/client.rb +168 -0
  10. data/lib/savon/core_ext/object.rb +14 -0
  11. data/lib/savon/core_ext/string.rb +23 -0
  12. data/lib/savon/error.rb +6 -0
  13. data/lib/savon/global.rb +129 -0
  14. data/lib/savon/http/error.rb +42 -0
  15. data/lib/savon/soap/fault.rb +59 -0
  16. data/lib/savon/soap/request.rb +71 -0
  17. data/lib/savon/soap/response.rb +109 -0
  18. data/lib/savon/soap/xml.rb +227 -0
  19. data/lib/savon/soap.rb +21 -0
  20. data/lib/savon/version.rb +5 -0
  21. data/lib/savon/wasabi/document.rb +41 -0
  22. data/lib/savon.rb +14 -0
  23. data/savon.gemspec +32 -0
  24. data/spec/fixtures/gzip/message.gz +0 -0
  25. data/spec/fixtures/response/another_soap_fault.xml +14 -0
  26. data/spec/fixtures/response/authentication.xml +14 -0
  27. data/spec/fixtures/response/header.xml +13 -0
  28. data/spec/fixtures/response/list.xml +18 -0
  29. data/spec/fixtures/response/multi_ref.xml +39 -0
  30. data/spec/fixtures/response/soap_fault.xml +8 -0
  31. data/spec/fixtures/response/soap_fault12.xml +18 -0
  32. data/spec/fixtures/response/taxcloud.xml +1 -0
  33. data/spec/fixtures/wsdl/authentication.xml +63 -0
  34. data/spec/fixtures/wsdl/lower_camel.xml +52 -0
  35. data/spec/fixtures/wsdl/multiple_namespaces.xml +61 -0
  36. data/spec/fixtures/wsdl/multiple_types.xml +60 -0
  37. data/spec/fixtures/wsdl/taxcloud.xml +934 -0
  38. data/spec/savon/client_spec.rb +461 -0
  39. data/spec/savon/core_ext/object_spec.rb +19 -0
  40. data/spec/savon/core_ext/string_spec.rb +37 -0
  41. data/spec/savon/http/error_spec.rb +52 -0
  42. data/spec/savon/savon_spec.rb +146 -0
  43. data/spec/savon/soap/fault_spec.rb +89 -0
  44. data/spec/savon/soap/request_spec.rb +57 -0
  45. data/spec/savon/soap/response_spec.rb +224 -0
  46. data/spec/savon/soap/xml_spec.rb +309 -0
  47. data/spec/savon/soap_spec.rb +16 -0
  48. data/spec/savon/wasabi/document_spec.rb +45 -0
  49. data/spec/spec_helper.rb +15 -0
  50. data/spec/support/endpoint.rb +25 -0
  51. data/spec/support/fixture.rb +35 -0
  52. metadata +216 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Daniel Harrington
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ Savon [![Build Status](https://secure.travis-ci.org/rubiii/savon.png)](http://travis-ci.org/rubiii/savon)
2
+ =====
3
+
4
+ Heavy metal Ruby SOAP client
5
+
6
+ [Documentation](http://savonrb.com) | [RDoc](http://rubydoc.info/gems/savon) |
7
+ [Mailing list](https://groups.google.com/forum/#!forum/savonrb) | [Twitter](http://twitter.com/savonrb)
8
+
9
+ Installation
10
+ ------------
11
+
12
+ Savon is available through [Rubygems](http://rubygems.org/gems/savon) and can be installed via:
13
+
14
+ ```
15
+ $ gem install savon
16
+ ```
17
+
18
+ Introduction
19
+ ------------
20
+
21
+ ``` ruby
22
+ require "savon"
23
+
24
+ # create a client for your SOAP service
25
+ client = Savon::Client.new("http://service.example.com?wsdl")
26
+
27
+ client.wsdl.soap_actions
28
+ # => [:create_user, :get_user, :get_all_users]
29
+
30
+ # execute a SOAP request to call the "getUser" action
31
+ response = client.request(:get_user) do
32
+ soap.body = { :id => 1 }
33
+ end
34
+
35
+ response.body
36
+ # => { :get_user_response => { :first_name => "The", :last_name => "Hoff" } }
37
+ ```
38
+
39
+ Documentation
40
+ -------------
41
+
42
+ Continue reading at [savonrb.com](http://savonrb.com)
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
@@ -0,0 +1,168 @@
1
+ require "httpi/request"
2
+ require "akami"
3
+
4
+ require "savon/wasabi/document"
5
+ require "savon/soap/xml"
6
+ require "savon/soap/request"
7
+ require "savon/soap/response"
8
+
9
+ module Savon
10
+
11
+ # = Savon::Client
12
+ #
13
+ # Savon::Client is the main object for connecting to a SOAP service.
14
+ class Client
15
+
16
+ # Initializes the Savon::Client for a SOAP service. Accepts a +block+ which is evaluated in the
17
+ # context of this object to let you access the +wsdl+, +http+, and +wsse+ methods.
18
+ #
19
+ # == Examples
20
+ #
21
+ # # Using a remote WSDL
22
+ # client = Savon::Client.new("http://example.com/UserService?wsdl")
23
+ #
24
+ # # Using a local WSDL
25
+ # client = Savon::Client.new File.expand_path("../wsdl/service.xml", __FILE__)
26
+ #
27
+ # # Directly accessing a SOAP endpoint
28
+ # client = Savon::Client.new do
29
+ # wsdl.endpoint = "http://example.com/UserService"
30
+ # wsdl.namespace = "http://users.example.com"
31
+ # end
32
+ def initialize(wsdl_document = nil, &block)
33
+ wsdl.document = wsdl_document if wsdl_document
34
+ process 1, &block if block
35
+ wsdl.request = http
36
+ end
37
+
38
+ # Returns the <tt>Savon::Wasabi::Document</tt>.
39
+ def wsdl
40
+ @wsdl ||= Wasabi::Document.new
41
+ end
42
+
43
+ # Returns the <tt>HTTPI::Request</tt>.
44
+ def http
45
+ @http ||= HTTPI::Request.new
46
+ end
47
+
48
+ # Returns the <tt>Akami::WSSE</tt> object.
49
+ def wsse
50
+ @wsse ||= Akami.wsse
51
+ end
52
+
53
+ # Returns the <tt>Savon::SOAP::XML</tt> object. Please notice, that this object is only available
54
+ # in a block given to <tt>Savon::Client#request</tt>. A new instance of this object is created
55
+ # per SOAP request.
56
+ attr_reader :soap
57
+
58
+ # Executes a SOAP request for a given SOAP action. Accepts a +block+ which is evaluated in the
59
+ # context of this object to let you access the +soap+, +wsdl+, +http+ and +wsse+ methods.
60
+ #
61
+ # == Examples
62
+ #
63
+ # # Calls a "getUser" SOAP action with the payload of "<userId>123</userId>"
64
+ # client.request(:get_user) { soap.body = { :user_id => 123 } }
65
+ #
66
+ # # Prefixes the SOAP input tag with a given namespace: "<wsdl:GetUser>...</wsdl:GetUser>"
67
+ # client.request(:wsdl, "GetUser") { soap.body = { :user_id => 123 } }
68
+ #
69
+ # # SOAP input tag with attributes: <getUser xmlns:wsdl="http://example.com">...</getUser>"
70
+ # client.request(:get_user, "xmlns:wsdl" => "http://example.com")
71
+ def request(*args, &block)
72
+ raise ArgumentError, "Savon::Client#request requires at least one argument" if args.empty?
73
+
74
+ self.soap = SOAP::XML.new
75
+ preconfigure extract_options(args)
76
+ process &block if block
77
+ soap.wsse = wsse
78
+
79
+ response = SOAP::Request.new(http, soap).response
80
+ set_cookie response.http.headers
81
+ response
82
+ end
83
+
84
+ private
85
+
86
+ # Writer for the <tt>Savon::SOAP::XML</tt> object.
87
+ attr_writer :soap
88
+
89
+ # Accessor for the original self of a given block.
90
+ attr_accessor :original_self
91
+
92
+ # Passes a cookie from the last request +headers+ to the next one.
93
+ def set_cookie(headers)
94
+ http.headers["Cookie"] = headers["Set-Cookie"] if headers["Set-Cookie"]
95
+ end
96
+
97
+ # Expects an Array of +args+ and returns an Array containing the namespace (might be +nil+),
98
+ # the SOAP input and a Hash of attributes for the input tag (which might be empty).
99
+ def extract_options(args)
100
+ attributes = Hash === args.last ? args.pop : {}
101
+ namespace = args.size > 1 ? args.shift.to_sym : nil
102
+ input = args.first
103
+
104
+ [namespace, input, attributes]
105
+ end
106
+
107
+ # Expects an Array of +args+ to preconfigure the system.
108
+ def preconfigure(args)
109
+ soap.endpoint = wsdl.endpoint
110
+ soap.namespace_identifier = args[0]
111
+ soap.namespace = wsdl.namespace
112
+ soap.element_form_default = wsdl.element_form_default if wsdl.document?
113
+
114
+ body = args[2].delete(:body)
115
+ soap.body = body if body
116
+
117
+ wsdl.type_namespaces.each do |path, uri|
118
+ soap.use_namespace(path, uri)
119
+ end
120
+
121
+ wsdl.type_definitions.each do |path, type|
122
+ soap.types[path] = type
123
+ end
124
+
125
+ set_soap_action args[1]
126
+ set_soap_input *args
127
+ end
128
+
129
+ # Expects an +input+ and sets the +SOAPAction+ HTTP headers.
130
+ def set_soap_action(input_tag)
131
+ soap_action = wsdl.soap_action(input_tag.to_sym) if wsdl.document?
132
+ soap_action ||= Gyoku::XMLKey.create(input_tag).to_sym
133
+ http.headers["SOAPAction"] = %{"#{soap_action}"}
134
+ end
135
+
136
+ # Expects a +namespace+, +input+ and +attributes+ and sets the SOAP input.
137
+ def set_soap_input(namespace, input, attributes)
138
+ new_input_tag = wsdl.soap_input(input.to_sym) if wsdl.document?
139
+ new_input_tag ||= Gyoku::XMLKey.create(input)
140
+ soap.input = [namespace, new_input_tag.to_sym, attributes]
141
+ end
142
+
143
+ # Processes a given +block+. Yields objects if the block expects any arguments.
144
+ # Otherwise evaluates the block in the context of this object.
145
+ def process(offset = 0, &block)
146
+ block.arity > 0 ? yield_objects(offset, &block) : evaluate(&block)
147
+ end
148
+
149
+ # Yields a number of objects to a given +block+ depending on how many arguments
150
+ # the block is expecting.
151
+ def yield_objects(offset, &block)
152
+ yield *[soap, wsdl, http, wsse][offset, block.arity]
153
+ end
154
+
155
+ # Evaluates a given +block+ inside this object. Stores the original block binding.
156
+ def evaluate(&block)
157
+ self.original_self = eval "self", block.binding
158
+ instance_eval &block
159
+ end
160
+
161
+ # Handles calls to undefined methods by delegating to the original block binding.
162
+ def method_missing(method, *args, &block)
163
+ super unless original_self
164
+ original_self.send method, *args, &block
165
+ end
166
+
167
+ end
168
+ end
@@ -0,0 +1,14 @@
1
+ module Savon
2
+ module CoreExt
3
+ module Object
4
+
5
+ # Returns +true+ if the Object is nil, false or empty. Implementation from ActiveSupport.
6
+ def blank?
7
+ respond_to?(:empty?) ? empty? : !self
8
+ end unless method_defined?(:blank?)
9
+
10
+ end
11
+ end
12
+ end
13
+
14
+ Object.send :include, Savon::CoreExt::Object
@@ -0,0 +1,23 @@
1
+ require "savon/soap"
2
+
3
+ module Savon
4
+ module CoreExt
5
+ module String
6
+
7
+ # Returns the String in snake_case.
8
+ def snakecase
9
+ str = dup
10
+ str.gsub! /::/, '/'
11
+ str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
12
+ str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
13
+ str.tr! ".", "_"
14
+ str.tr! "-", "_"
15
+ str.downcase!
16
+ str
17
+ end unless method_defined?(:snakecase)
18
+
19
+ end
20
+ end
21
+ end
22
+
23
+ String.send :include, Savon::CoreExt::String
@@ -0,0 +1,6 @@
1
+ module Savon
2
+
3
+ # Base class for Savon errors.
4
+ class Error < RuntimeError; end
5
+
6
+ end
@@ -0,0 +1,129 @@
1
+ require "logger"
2
+ require "savon/soap"
3
+
4
+ module Savon
5
+ module Global
6
+
7
+ # Sets whether to log HTTP requests.
8
+ attr_writer :log
9
+
10
+ # Returns whether to log HTTP requests. Defaults to +true+.
11
+ def log?
12
+ @log != false
13
+ end
14
+
15
+ # Sets the logger to use.
16
+ attr_writer :logger
17
+
18
+ # Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
19
+ def logger
20
+ @logger ||= ::Logger.new STDOUT
21
+ end
22
+
23
+ # Sets the log level.
24
+ attr_writer :log_level
25
+
26
+ # Returns the log level. Defaults to :debug.
27
+ def log_level
28
+ @log_level ||= :debug
29
+ end
30
+
31
+ # Logs a given +message+.
32
+ def log(message)
33
+ logger.send log_level, filtered(message) if log?
34
+ end
35
+
36
+ # Sets the log filter.
37
+ attr_writer :log_filter
38
+
39
+ # Returns the log filter. Defaults to blank.
40
+ def log_filter
41
+ @log_filter ||= ''
42
+ end
43
+
44
+ # Filters Message based on log filter
45
+ def filtered(message)
46
+ xml = Nokogiri::XML(message)
47
+ return message if @log_filter.empty? || !xml.errors.empty?
48
+
49
+ @log_filter.each do |filter|
50
+ xml.xpath("//*[local-name()='#{filter}']").map { |node| node.content = '***FILTERED***' }
51
+ end
52
+ return xml.root.to_s
53
+ end
54
+
55
+ # Sets whether to raise HTTP errors and SOAP faults.
56
+ attr_writer :raise_errors
57
+
58
+ # Returns whether to raise errors. Defaults to +true+.
59
+ def raise_errors?
60
+ @raise_errors != false
61
+ end
62
+
63
+ # Sets the global SOAP version.
64
+ def soap_version=(version)
65
+ raise ArgumentError, "Invalid SOAP version: #{version}" unless SOAP::Versions.include? version
66
+ @version = version
67
+ end
68
+
69
+ # Returns SOAP version. Defaults to +DefaultVersion+.
70
+ def soap_version
71
+ @version ||= SOAP::DefaultVersion
72
+ end
73
+
74
+ # Returns whether to strip namespaces in a SOAP response Hash.
75
+ # Defaults to +true+.
76
+ def strip_namespaces?
77
+ Savon.deprecate("use Nori.strip_namespaces? instead of Savon.strip_namespaces?")
78
+ Nori.strip_namespaces?
79
+ end
80
+
81
+ # Sets whether to strip namespaces in a SOAP response Hash.
82
+ def strip_namespaces=(strip)
83
+ Savon.deprecate("use Nori.strip_namespaces= instead of Savon.strip_namespaces=")
84
+ Nori.strip_namespaces = strip
85
+ end
86
+
87
+ # Returns the global env_namespace.
88
+ attr_reader :env_namespace
89
+
90
+ # Sets the global env_namespace.
91
+ attr_writer :env_namespace
92
+
93
+ # Returns the global soap_header.
94
+ attr_reader :soap_header
95
+
96
+ # Sets the global soap_header.
97
+ attr_writer :soap_header
98
+
99
+ # Expects a +message+ and raises a warning if configured.
100
+ def deprecate(message)
101
+ warn("Deprecation: #{message}") if deprecate?
102
+ end
103
+
104
+ # Sets whether to warn about deprecations.
105
+ def deprecate=(deprecate)
106
+ @deprecate = deprecate
107
+ end
108
+
109
+ # Returns whether to warn about deprecation.
110
+ def deprecate?
111
+ @deprecate != false
112
+ end
113
+
114
+ # Reset to default configuration.
115
+ def reset_config!
116
+ self.log = true
117
+ self.logger = ::Logger.new STDOUT
118
+ self.log_level = :debug
119
+ self.raise_errors = true
120
+ self.soap_version = SOAP::DefaultVersion
121
+ self.strip_namespaces = true
122
+ self.env_namespace = nil
123
+ self.soap_header = {}
124
+ self.log_filter = ''
125
+ end
126
+
127
+ end
128
+ end
129
+
@@ -0,0 +1,42 @@
1
+ require "savon/error"
2
+ require "savon/soap/xml"
3
+
4
+ module Savon
5
+ module HTTP
6
+
7
+ # = Savon::HTTP::Error
8
+ #
9
+ # Represents an HTTP error. Contains the original <tt>HTTPI::Response</tt>.
10
+ class Error < Error
11
+
12
+ # Expects an <tt>HTTPI::Response</tt>.
13
+ def initialize(http)
14
+ self.http = http
15
+ end
16
+
17
+ # Accessor for the <tt>HTTPI::Response</tt>.
18
+ attr_accessor :http
19
+
20
+ # Returns whether an HTTP error is present.
21
+ def present?
22
+ http.error?
23
+ end
24
+
25
+ # Returns the HTTP error message.
26
+ def to_s
27
+ return "" unless present?
28
+
29
+ @message ||= begin
30
+ message = "HTTP error (#{http.code})"
31
+ message << ": #{http.body}" unless http.body.empty?
32
+ end
33
+ end
34
+
35
+ # Returns the HTTP response as a Hash.
36
+ def to_hash
37
+ @hash = { :code => http.code, :headers => http.headers, :body => http.body }
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,59 @@
1
+ require "savon/error"
2
+ require "savon/soap/xml"
3
+
4
+ module Savon
5
+ module SOAP
6
+
7
+ # = Savon::SOAP::Fault
8
+ #
9
+ # Represents a SOAP fault. Contains the original <tt>HTTPI::Response</tt>.
10
+ class Fault < Error
11
+
12
+ # Expects an <tt>HTTPI::Response</tt>.
13
+ def initialize(http)
14
+ self.http = http
15
+ end
16
+
17
+ # Accessor for the <tt>HTTPI::Response</tt>.
18
+ attr_accessor :http
19
+
20
+ # Returns whether a SOAP fault is present.
21
+ def present?
22
+ @present ||= http.body.include?("Fault>") && (soap1_fault? || soap2_fault?)
23
+ end
24
+
25
+ # Returns the SOAP fault message.
26
+ def to_s
27
+ return "" unless present?
28
+ @message ||= message_by_version to_hash[:fault]
29
+ end
30
+
31
+ # Returns the SOAP response body as a Hash.
32
+ def to_hash
33
+ @hash ||= Nori.parse(http.body)[:envelope][:body]
34
+ end
35
+
36
+ private
37
+
38
+ # Returns whether the response contains a SOAP 1.1 fault.
39
+ def soap1_fault?
40
+ http.body.include?("faultcode>") && http.body.include?("faultstring>")
41
+ end
42
+
43
+ # Returns whether the response contains a SOAP 1.2 fault.
44
+ def soap2_fault?
45
+ http.body.include?("Code>") && http.body.include?("Reason>")
46
+ end
47
+
48
+ # Returns the SOAP fault message by version.
49
+ def message_by_version(fault)
50
+ if fault[:faultcode]
51
+ "(#{fault[:faultcode]}) #{fault[:faultstring]}"
52
+ elsif fault[:code]
53
+ "(#{fault[:code][:value]}) #{fault[:reason][:text]}"
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,71 @@
1
+ require "httpi"
2
+ require "savon/soap/response"
3
+
4
+ module Savon
5
+ module SOAP
6
+
7
+ # = Savon::SOAP::Request
8
+ #
9
+ # Executes SOAP requests.
10
+ class Request
11
+
12
+ # Content-Types by SOAP version.
13
+ ContentType = { 1 => "text/xml;charset=UTF-8", 2 => "application/soap+xml;charset=UTF-8" }
14
+
15
+ # Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object
16
+ # to execute a SOAP request and returns the response.
17
+ def self.execute(request, soap)
18
+ new(request, soap).response
19
+ end
20
+
21
+ # Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object.
22
+ def initialize(request, soap)
23
+ self.request = setup(request, soap)
24
+ end
25
+
26
+ # Accessor for the <tt>HTTPI::Request</tt>.
27
+ attr_accessor :request
28
+
29
+ # Executes the request and returns the response.
30
+ def response
31
+ @response ||= with_logging { HTTPI.post request }
32
+ end
33
+
34
+ private
35
+
36
+ # Sets up the +request+ using a given +soap+ object.
37
+ def setup(request, soap)
38
+ url, body = soap.endpoint, soap.to_xml
39
+
40
+ request.url = url
41
+ request.body = body
42
+ request.headers["Content-Type"] ||= ContentType[soap.version]
43
+ request.headers["Content-Length"] ||= body.length.to_s
44
+
45
+ request
46
+ end
47
+
48
+ # Logs the HTTP request, yields to a given +block+ and returns a <tt>Savon::SOAP::Response</tt>.
49
+ def with_logging
50
+ log_request request.url, request.headers, request.body
51
+ response = yield
52
+ log_response response.code, response.body
53
+ SOAP::Response.new response
54
+ end
55
+
56
+ # Logs the SOAP request +url+, +headers+ and +body+.
57
+ def log_request(url, headers, body)
58
+ Savon.log "SOAP request: #{url}"
59
+ Savon.log headers.map { |key, value| "#{key}: #{value}" }.join(", ")
60
+ Savon.log body
61
+ end
62
+
63
+ # Logs the SOAP response +code+ and +body+.
64
+ def log_response(code, body)
65
+ Savon.log "SOAP response (status #{code}):"
66
+ Savon.log body
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,109 @@
1
+ require "savon/soap/xml"
2
+ require "savon/soap/fault"
3
+ require "savon/http/error"
4
+
5
+ module Savon
6
+ module SOAP
7
+
8
+ # = Savon::SOAP::Response
9
+ #
10
+ # Represents the SOAP response and contains the HTTP response.
11
+ class Response
12
+
13
+ # Expects an <tt>HTTPI::Response</tt> and handles errors.
14
+ def initialize(response)
15
+ self.http = response
16
+ raise_errors if Savon.raise_errors?
17
+ end
18
+
19
+ attr_accessor :http
20
+
21
+ # Returns whether the request was successful.
22
+ def success?
23
+ !soap_fault? && !http_error?
24
+ end
25
+
26
+ # Returns whether there was a SOAP fault.
27
+ def soap_fault?
28
+ soap_fault.present?
29
+ end
30
+
31
+ # Returns the <tt>Savon::SOAP::Fault</tt>.
32
+ def soap_fault
33
+ @soap_fault ||= Fault.new http
34
+ end
35
+
36
+ # Returns whether there was an HTTP error.
37
+ def http_error?
38
+ http_error.present?
39
+ end
40
+
41
+ # Returns the <tt>Savon::HTTP::Error</tt>.
42
+ def http_error
43
+ @http_error ||= HTTP::Error.new http
44
+ end
45
+
46
+ # Shortcut accessor for the SOAP response body Hash.
47
+ def [](key)
48
+ body[key]
49
+ end
50
+
51
+ # Returns the SOAP response header as a Hash.
52
+ def header
53
+ hash[:envelope][:header]
54
+ end
55
+
56
+ # Returns the SOAP response body as a Hash.
57
+ def body
58
+ hash[:envelope][:body]
59
+ end
60
+
61
+ alias to_hash body
62
+
63
+ # Traverses the SOAP response body Hash for a given +path+ of Hash keys and returns
64
+ # the value as an Array. Defaults to return an empty Array in case the path does not
65
+ # exist or returns nil.
66
+ def to_array(*path)
67
+ result = path.inject body do |memo, key|
68
+ return [] unless memo[key]
69
+ memo[key]
70
+ end
71
+
72
+ result.kind_of?(Array) ? result.compact : [result].compact
73
+ end
74
+
75
+ # Returns the complete SOAP response XML without normalization.
76
+ def hash
77
+ @hash ||= Nori.parse(to_xml)
78
+ end
79
+
80
+ # Returns the SOAP response XML.
81
+ def to_xml
82
+ http.body
83
+ end
84
+
85
+ # Returns a <tt>Nokogiri::XML::Document</tt> for the SOAP response XML.
86
+ def doc
87
+ @doc ||= Nokogiri::XML(to_xml)
88
+ end
89
+
90
+ # Returns an Array of <tt>Nokogiri::XML::Node</tt> objects retrieved with the given +path+.
91
+ # Automatically adds all of the document's namespaces unless a +namespaces+ hash is provided.
92
+ def xpath(path, namespaces = nil)
93
+ doc.xpath(path, namespaces || xml_namespaces)
94
+ end
95
+
96
+ private
97
+
98
+ def raise_errors
99
+ raise soap_fault if soap_fault?
100
+ raise http_error if http_error?
101
+ end
102
+
103
+ def xml_namespaces
104
+ @xml_namespaces ||= doc.collect_namespaces
105
+ end
106
+
107
+ end
108
+ end
109
+ end