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