savon 0.5.3 → 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/VERSION +1 -1
- data/lib/savon.rb +12 -22
- data/lib/savon/client.rb +26 -63
- data/lib/savon/core_ext.rb +3 -6
- data/lib/savon/core_ext/hash.rb +0 -12
- data/lib/savon/core_ext/string.rb +5 -0
- data/lib/savon/request.rb +17 -15
- data/lib/savon/response.rb +109 -0
- data/lib/savon/soap.rb +67 -33
- data/lib/savon/wsdl.rb +25 -18
- data/lib/savon/wsse.rb +59 -54
- data/spec/fixtures/user_fixture.rb +16 -4
- data/spec/savon/client_spec.rb +20 -129
- data/spec/savon/core_ext/hash_spec.rb +0 -26
- data/spec/savon/core_ext/string_spec.rb +12 -0
- data/spec/savon/request_spec.rb +9 -10
- data/spec/savon/response_spec.rb +133 -0
- data/spec/savon/savon_spec.rb +1 -8
- data/spec/savon/soap_spec.rb +86 -47
- data/spec/savon/wsdl_spec.rb +19 -8
- data/spec/savon/wsse_spec.rb +73 -84
- metadata +5 -5
- data/lib/savon/validation.rb +0 -57
- data/spec/savon/validation_spec.rb +0 -88
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/lib/savon.rb
CHANGED
@@ -1,8 +1,5 @@
|
|
1
1
|
module Savon
|
2
2
|
|
3
|
-
# Current version.
|
4
|
-
VERSION = "0.5.0"
|
5
|
-
|
6
3
|
# Supported SOAP versions.
|
7
4
|
SOAPVersions = [1, 2]
|
8
5
|
|
@@ -20,24 +17,17 @@ module Savon
|
|
20
17
|
|
21
18
|
end
|
22
19
|
|
23
|
-
#
|
24
|
-
|
25
|
-
require
|
26
|
-
|
27
|
-
require "uri"
|
28
|
-
require "base64"
|
29
|
-
require "digest/sha1"
|
30
|
-
require "rexml/document"
|
20
|
+
# standard libs
|
21
|
+
%w(logger net/http net/https uri base64 digest/sha1 rexml/document).each do |lib|
|
22
|
+
require lib
|
23
|
+
end
|
31
24
|
|
32
25
|
# gems
|
33
|
-
|
34
|
-
require
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
require "savon
|
40
|
-
|
41
|
-
require "savon/request"
|
42
|
-
require "savon/wsdl"
|
43
|
-
require "savon/client"
|
26
|
+
%w(builder crack/xml).each do |gem|
|
27
|
+
require gem
|
28
|
+
end
|
29
|
+
|
30
|
+
# core files
|
31
|
+
%w(core_ext wsse soap request response wsdl client).each do |file|
|
32
|
+
require "savon/#{file}"
|
33
|
+
end
|
data/lib/savon/client.rb
CHANGED
@@ -5,41 +5,6 @@ module Savon
|
|
5
5
|
# Heavy metal Ruby SOAP client library. Minimizes the overhead of working
|
6
6
|
# with SOAP services and XML.
|
7
7
|
class Client
|
8
|
-
include Validation
|
9
|
-
|
10
|
-
# Default behavior for processing the SOAP response. Expects an instance
|
11
|
-
# of Net::HTTPResponse and returns the SOAP response body as a Hash.
|
12
|
-
@response_process = lambda do |response|
|
13
|
-
body = Crack::XML.parse response.body
|
14
|
-
body = body.find_regexp [/.+:Envelope/, /.+:Body/]
|
15
|
-
error_handling.call response, body
|
16
|
-
|
17
|
-
body = body.find_regexp /.+/
|
18
|
-
(body["return"] || body).map_soap_response
|
19
|
-
end
|
20
|
-
|
21
|
-
# Default error handling. Expects an instance of Net::HTTPResponse and
|
22
|
-
# a SOAP response body Hash. Raises a Savon::SOAPFault in case of a SOAP
|
23
|
-
# fault or a Savon::HTTPError in case of an HTTP error.
|
24
|
-
@error_handling = lambda do |response, body|
|
25
|
-
soap_fault = body.find_regexp [/.+:Fault/]
|
26
|
-
soap_fault_message = soap_fault.to_soap_fault_message
|
27
|
-
raise Savon::SOAPFault, soap_fault_message if soap_fault_message
|
28
|
-
|
29
|
-
http_error = "#{response.message} (#{response.code})"
|
30
|
-
http_error += ": #{response.body}" unless response.body.empty?
|
31
|
-
raise Savon::HTTPError, http_error if response.code.to_i >= 300
|
32
|
-
end
|
33
|
-
|
34
|
-
class << self
|
35
|
-
|
36
|
-
# Accessor for the default response block.
|
37
|
-
attr_accessor :response_process
|
38
|
-
|
39
|
-
# Accessor for the default error handling.
|
40
|
-
attr_accessor :error_handling
|
41
|
-
|
42
|
-
end
|
43
8
|
|
44
9
|
# Expects a SOAP +endpoint+ String.
|
45
10
|
def initialize(endpoint)
|
@@ -50,48 +15,46 @@ module Savon
|
|
50
15
|
# Returns the Savon::WSDL.
|
51
16
|
attr_reader :wsdl
|
52
17
|
|
53
|
-
# Returns
|
54
|
-
attr_reader :response
|
55
|
-
|
56
|
-
# Behaves as usual, but also returns +true+ for available SOAP actions.
|
18
|
+
# Returns +true+ for available methods and SOAP actions.
|
57
19
|
def respond_to?(method)
|
58
|
-
return true if @wsdl.
|
20
|
+
return true if @wsdl.respond_to? method
|
59
21
|
super
|
60
22
|
end
|
61
23
|
|
62
24
|
private
|
63
25
|
|
64
|
-
#
|
65
|
-
# the given +method+ name.
|
26
|
+
# Dispatches requests to SOAP actions matching a given +method+ name.
|
66
27
|
def method_missing(method, *args, &block) #:doc:
|
67
|
-
|
68
|
-
super unless soap_action
|
69
|
-
#soap_action = "User.GetApiKey"
|
28
|
+
super unless @wsdl.respond_to? method
|
70
29
|
|
71
|
-
|
72
|
-
|
73
|
-
dispatch soap_action, soap_body, options, block
|
30
|
+
setup method, &block
|
31
|
+
dispatch method
|
74
32
|
end
|
75
33
|
|
76
|
-
#
|
77
|
-
#
|
78
|
-
def
|
79
|
-
@soap = SOAP.new
|
80
|
-
@
|
81
|
-
|
34
|
+
# Expects a SOAP action and sets up Savon::SOAP and Savon::WSSE.
|
35
|
+
# Yields them to a given +block+ in case one was given.
|
36
|
+
def setup(soap_action, &block)
|
37
|
+
@soap = SOAP.new @wsdl.soap_actions[soap_action]
|
38
|
+
@wsse = WSSE.new
|
39
|
+
|
40
|
+
yield_parameters &block if block
|
41
|
+
|
42
|
+
@soap.namespaces["xmlns:wsdl"] = @wsdl.namespace_uri
|
43
|
+
@soap.wsse = @wsse
|
82
44
|
end
|
83
45
|
|
84
|
-
#
|
85
|
-
def
|
86
|
-
block
|
46
|
+
# Yields Savon::SOAP and Savon::WSSE to a given +block+.
|
47
|
+
def yield_parameters(&block)
|
48
|
+
case block.arity
|
49
|
+
when 1 then yield @soap
|
50
|
+
when 2 then yield @soap, @wsse
|
51
|
+
end
|
87
52
|
end
|
88
53
|
|
89
|
-
#
|
90
|
-
def
|
91
|
-
|
92
|
-
|
93
|
-
validate! :soap_version, options[:soap_version] if options[:soap_version]
|
94
|
-
validate! :wsse_credentials, options[:wsse] if options[:wsse].kind_of? Hash
|
54
|
+
# Dispatches a given +soap_action+ and returns a Savon::Response instance.
|
55
|
+
def dispatch(soap_action)
|
56
|
+
response = @request.soap @soap
|
57
|
+
Response.new response
|
95
58
|
end
|
96
59
|
|
97
60
|
end
|
data/lib/savon/core_ext.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
|
2
|
-
require "savon/core_ext
|
3
|
-
|
4
|
-
require "savon/core_ext/datetime"
|
5
|
-
require "savon/core_ext/hash"
|
6
|
-
require "savon/core_ext/uri"
|
1
|
+
%w(object string symbol datetime hash uri).each do |file|
|
2
|
+
require "savon/core_ext/#{file}"
|
3
|
+
end
|
data/lib/savon/core_ext/hash.rb
CHANGED
@@ -27,18 +27,6 @@ class Hash
|
|
27
27
|
@soap_xml.target!
|
28
28
|
end
|
29
29
|
|
30
|
-
# Tries to generate a SOAP fault message from the Hash. Returns nil in
|
31
|
-
# case no SOAP fault could be found or generated.
|
32
|
-
def to_soap_fault_message
|
33
|
-
if keys.include? "faultcode"
|
34
|
-
"(#{self['faultcode']}) #{self['faultstring']}"
|
35
|
-
elsif keys.include? "code"
|
36
|
-
"(#{self['code']['value']}) #{self['reason']['text']}"
|
37
|
-
else
|
38
|
-
nil
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
30
|
# Maps keys and values of a Hash created from SOAP response XML to
|
43
31
|
# more convenient Ruby Objects.
|
44
32
|
def map_soap_response
|
data/lib/savon/request.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module Savon
|
2
|
+
|
3
|
+
# == Savon::Request
|
4
|
+
#
|
5
|
+
# Handles both WSDL and SOAP HTTP requests.
|
2
6
|
class Request
|
3
|
-
include Validation
|
4
7
|
|
5
8
|
# Content-Types by SOAP version.
|
6
9
|
ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
|
@@ -30,9 +33,12 @@ module Savon
|
|
30
33
|
attr_accessor :log_level
|
31
34
|
end
|
32
35
|
|
33
|
-
# Expects an endpoint String.
|
36
|
+
# Expects an endpoint String. Raises an exception in case the given
|
37
|
+
# +endpoint+ does not seem to be valid.
|
34
38
|
def initialize(endpoint)
|
35
|
-
|
39
|
+
raise ArgumentError, "Invalid endpoint: #{endpoint}" unless
|
40
|
+
/^(http|https):\/\// === endpoint
|
41
|
+
|
36
42
|
@endpoint = URI endpoint
|
37
43
|
end
|
38
44
|
|
@@ -45,11 +51,15 @@ module Savon
|
|
45
51
|
http.get @endpoint.to_s
|
46
52
|
end
|
47
53
|
|
48
|
-
# Executes a SOAP request using a given Savon::SOAP
|
49
|
-
#
|
54
|
+
# Executes a SOAP request using a given Savon::SOAP instance and
|
55
|
+
# returns the Net::HTTPResponse.
|
50
56
|
def soap(soap)
|
51
57
|
@soap = soap
|
52
|
-
|
58
|
+
|
59
|
+
log_request
|
60
|
+
@response = http.request_post @endpoint.path, @soap.to_xml, http_header
|
61
|
+
log_response
|
62
|
+
@response
|
53
63
|
end
|
54
64
|
|
55
65
|
private
|
@@ -58,15 +68,7 @@ module Savon
|
|
58
68
|
def log_request
|
59
69
|
log "SOAP request: #{@endpoint}"
|
60
70
|
log http_header.map { |key, value| "#{key}: #{value}" }.join ", "
|
61
|
-
log @soap.
|
62
|
-
end
|
63
|
-
|
64
|
-
# Executes a SOAP request and returns the Net::HTTPResponse.
|
65
|
-
def soap_request
|
66
|
-
log_request
|
67
|
-
@response = http.request_post @endpoint.path, @soap.body, http_header
|
68
|
-
log_response
|
69
|
-
@response
|
71
|
+
log @soap.to_xml
|
70
72
|
end
|
71
73
|
|
72
74
|
# Logs the SOAP response.
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# == Savon::Response
|
4
|
+
#
|
5
|
+
# Represents the HTTP and SOAP response.
|
6
|
+
class Response
|
7
|
+
|
8
|
+
# The default of whether to raise errors.
|
9
|
+
@raise_errors = true
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Sets the default of whether to raise errors.
|
14
|
+
attr_writer :raise_errors
|
15
|
+
|
16
|
+
# Returns the default of whether to raise errors.
|
17
|
+
def raise_errors?
|
18
|
+
@raise_errors
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
# Expects a Net::HTTPResponse and handles errors.
|
24
|
+
def initialize(response)
|
25
|
+
@response = response
|
26
|
+
|
27
|
+
handle_soap_fault
|
28
|
+
handle_http_error
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns whether there was a SOAP fault.
|
32
|
+
def soap_fault?
|
33
|
+
@soap_fault
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the SOAP fault message.
|
37
|
+
attr_reader :soap_fault
|
38
|
+
|
39
|
+
# Returns whether there was an HTTP error.
|
40
|
+
def http_error?
|
41
|
+
@http_error
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the HTTP error message.
|
45
|
+
attr_reader :http_error
|
46
|
+
|
47
|
+
# Returns the SOAP response as a Hash.
|
48
|
+
def to_hash
|
49
|
+
@body.find_regexp(/.+/).map_soap_response
|
50
|
+
end
|
51
|
+
|
52
|
+
# Returns the SOAP response XML.
|
53
|
+
def to_xml
|
54
|
+
@response.body
|
55
|
+
end
|
56
|
+
|
57
|
+
alias :to_s :to_xml
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Returns the SOAP response body as a Hash.
|
62
|
+
def body
|
63
|
+
unless @body
|
64
|
+
body = Crack::XML.parse @response.body
|
65
|
+
@body = body.find_regexp [/.+:Envelope/, /.+:Body/]
|
66
|
+
end
|
67
|
+
@body
|
68
|
+
end
|
69
|
+
|
70
|
+
# Handles SOAP faults. Raises a Savon::SOAPFault unless the default
|
71
|
+
# behavior of raising errors was turned off.
|
72
|
+
def handle_soap_fault
|
73
|
+
if soap_fault_message
|
74
|
+
@soap_fault = soap_fault_message
|
75
|
+
raise Savon::SOAPFault, soap_fault_message if self.class.raise_errors?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns a SOAP fault message in case a SOAP fault was found.
|
80
|
+
def soap_fault_message
|
81
|
+
unless @soap_fault_message
|
82
|
+
soap_fault = body.find_regexp [/.+:Fault/]
|
83
|
+
@soap_fault_message = soap_fault_message_by_version(soap_fault)
|
84
|
+
end
|
85
|
+
@soap_fault_message
|
86
|
+
end
|
87
|
+
|
88
|
+
# Expects a Hash that might contain information about a SOAP fault.
|
89
|
+
# Returns the SOAP fault message in case one was found.
|
90
|
+
def soap_fault_message_by_version(soap_fault)
|
91
|
+
if soap_fault.keys.include? "faultcode"
|
92
|
+
"(#{soap_fault['faultcode']}) #{soap_fault['faultstring']}"
|
93
|
+
elsif soap_fault.keys.include? "code"
|
94
|
+
"(#{soap_fault['code']['value']}) #{soap_fault['reason']['text']}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Handles HTTP errors. Raises a Savon::HTTPError unless the default
|
99
|
+
# behavior of raising errors was turned off.
|
100
|
+
def handle_http_error
|
101
|
+
if @response.code.to_i >= 300
|
102
|
+
@http_error = "#{@response.message} (#{@response.code})"
|
103
|
+
@http_error += ": #{@response.body}" unless @response.body.empty?
|
104
|
+
raise Savon::HTTPError, http_error if self.class.raise_errors?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
data/lib/savon/soap.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
module Savon
|
2
|
+
|
3
|
+
# == Savon::SOAP
|
4
|
+
#
|
5
|
+
# Represents the SOAP parameters and envelope.
|
2
6
|
class SOAP
|
3
|
-
include WSSE
|
4
7
|
|
5
8
|
# SOAP namespaces by SOAP version.
|
6
9
|
SOAPNamespace = {
|
@@ -16,55 +19,86 @@ module Savon
|
|
16
19
|
|
17
20
|
class << self
|
18
21
|
|
19
|
-
#
|
20
|
-
|
22
|
+
# Returns the default SOAP version.
|
23
|
+
attr_reader :version
|
24
|
+
|
25
|
+
# Sets the default SOAP version.
|
26
|
+
def version=(version)
|
27
|
+
@version = version if Savon::SOAPVersions.include? version
|
28
|
+
end
|
21
29
|
|
22
30
|
end
|
23
31
|
|
24
|
-
# Expects
|
25
|
-
def initialize(
|
26
|
-
@action
|
27
|
-
@
|
32
|
+
# Expects an +action_map+ containing the name of the SOAP action and input.
|
33
|
+
def initialize(action_map)
|
34
|
+
@action = action_map[:name]
|
35
|
+
@input = action_map[:input]
|
28
36
|
end
|
29
37
|
|
30
|
-
#
|
31
|
-
|
38
|
+
# Sets the WSSE options.
|
39
|
+
attr_writer :wsse
|
32
40
|
|
33
|
-
#
|
34
|
-
|
41
|
+
# Accessor for the SOAP action.
|
42
|
+
attr_accessor :action
|
35
43
|
|
36
|
-
#
|
37
|
-
|
38
|
-
|
44
|
+
# Sets the SOAP header. Expected to be a Hash that can be translated
|
45
|
+
# to XML via Hash.to_soap_xml or any other Object responding to to_s.
|
46
|
+
attr_writer :header
|
39
47
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
48
|
+
# Returns the SOAP header. Defaults to an empty Hash.
|
49
|
+
def header
|
50
|
+
@header ||= {}
|
44
51
|
end
|
45
52
|
|
46
|
-
#
|
47
|
-
|
48
|
-
|
53
|
+
# Sets the SOAP body. Expected to be a Hash that can be translated to
|
54
|
+
# XML via Hash.to_soap_xml or any other Object responding to to_s.
|
55
|
+
attr_writer :body
|
56
|
+
|
57
|
+
# Sets the namespaces. Expected to be a Hash containing the namespaces
|
58
|
+
# (keys) and the corresponding URI's (values).
|
59
|
+
attr_writer :namespaces
|
60
|
+
|
61
|
+
# Returns the namespaces. A Hash containing the namespaces (keys) and
|
62
|
+
# the corresponding URI's (values).
|
63
|
+
def namespaces
|
64
|
+
@namespaces ||= { "xmlns:env" => SOAPNamespace[version] }
|
49
65
|
end
|
50
66
|
|
51
|
-
|
67
|
+
# Sets the SOAP version.
|
68
|
+
def version=(version)
|
69
|
+
@version = version if Savon::SOAPVersions.include? version
|
70
|
+
end
|
52
71
|
|
53
|
-
# Returns
|
54
|
-
def
|
55
|
-
|
72
|
+
# Returns the SOAP version. Defaults to the global default.
|
73
|
+
def version
|
74
|
+
@version ||= self.class.version
|
56
75
|
end
|
57
76
|
|
58
|
-
#
|
59
|
-
|
60
|
-
|
61
|
-
|
77
|
+
# Returns the SOAP envelope XML.
|
78
|
+
def to_xml
|
79
|
+
unless @xml_body
|
80
|
+
builder = Builder::XmlMarkup.new
|
81
|
+
|
82
|
+
@xml_body = builder.env :Envelope, namespaces do |xml|
|
83
|
+
xml.env(:Header) do
|
84
|
+
xml << (header.to_soap_xml rescue header.to_s) + wsse_header
|
85
|
+
end
|
86
|
+
xml.env(:Body) do
|
87
|
+
xml.wsdl(@input.to_sym) do
|
88
|
+
xml << (@body.to_soap_xml rescue @body.to_s)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@xml_body
|
62
94
|
end
|
63
95
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
96
|
+
private
|
97
|
+
|
98
|
+
# Returns the WSSE header or an empty String in case WSSE was not set.
|
99
|
+
def wsse_header
|
100
|
+
return "" unless @wsse.respond_to? :header
|
101
|
+
@wsse.header
|
68
102
|
end
|
69
103
|
|
70
104
|
end
|