savon 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +58 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/lib/savon.rb +44 -0
- data/lib/savon/service.rb +135 -0
- data/lib/savon/wsdl.rb +70 -0
- data/spec/fixtures/soap_fault.xml +8 -0
- data/spec/fixtures/user_response.xml +13 -0
- data/spec/fixtures/user_wsdl.xml +106 -0
- data/spec/savon/service_spec.rb +71 -0
- data/spec/savon/wsdl_spec.rb +65 -0
- data/spec/spec_helper.rb +100 -0
- metadata +113 -0
data/README.rdoc
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
= Savon
|
2
|
+
|
3
|
+
Savon::Service is a SOAP client library to enjoy. The goal is to minimize
|
4
|
+
the overhead of working with SOAP services and provide a lightweight
|
5
|
+
alternative to other libraries.
|
6
|
+
|
7
|
+
== Install
|
8
|
+
|
9
|
+
$ gem install rubiii-savon -s http://gems.github.com
|
10
|
+
|
11
|
+
== Dependencies
|
12
|
+
|
13
|
+
rubiii-apricoteatsgorilla >= 0.5.2
|
14
|
+
hpricot 0.8.241 (the latest JRuby-compatible version)
|
15
|
+
|
16
|
+
Hpricot 0.8.241 is also available at: {Apricot eats Gorilla Downloads}[http://github.com/rubiii/apricoteatsgorilla/downloads]
|
17
|
+
|
18
|
+
== How to use
|
19
|
+
|
20
|
+
Instantiate a new Savon::Service instance passing in the WSDL of your service.
|
21
|
+
|
22
|
+
proxy = Savon::Service.new("http://example.com/ExampleService?wsdl")
|
23
|
+
|
24
|
+
Call the SOAP service method of your choice on your Savon::Service instance.
|
25
|
+
|
26
|
+
response = proxy.get_all_users
|
27
|
+
|
28
|
+
Or pass in a Hash of options for the SOAP service to receive.
|
29
|
+
|
30
|
+
response = proxy.find_user_by_id(:id => 123)
|
31
|
+
|
32
|
+
Or specify a custom XPath-Expression to start translating the SOAP response at.
|
33
|
+
By default the response is translated starting at "//return".
|
34
|
+
|
35
|
+
response = proxy.find_user_by_id(nil, "//user/email")
|
36
|
+
|
37
|
+
=== Check for available SOAP actions
|
38
|
+
|
39
|
+
Access the WSDL to get an Array of SOAP actions found in the WSDL document.
|
40
|
+
|
41
|
+
proxy.wsdl.soap_actions
|
42
|
+
# => [ "getAllUsers", "findUserById" ]
|
43
|
+
|
44
|
+
=== Handle HTTP error and SOAP faults
|
45
|
+
|
46
|
+
Savon::Service raises a Savon::SOAPFault in case of a SOAP fault and a
|
47
|
+
Savon::HTTPError in case of an HTTP error.
|
48
|
+
|
49
|
+
=== Logging request and response
|
50
|
+
|
51
|
+
You should specify the logger to use before working with any service.
|
52
|
+
|
53
|
+
# example for Ruby on Rails
|
54
|
+
Savon.logger = RAILS_DEFAULT_LOGGER
|
55
|
+
|
56
|
+
Of course you can also specify the log level if needed. By default it's set to :debug.
|
57
|
+
|
58
|
+
Savon.log_level = :info
|
data/Rakefile
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
require "spec/rake/spectask"
|
4
|
+
require "rake/rdoctask"
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
Spec::Rake::SpecTask.new do |spec|
|
9
|
+
spec.spec_files = FileList["spec/**/*_spec.rb"]
|
10
|
+
spec.spec_opts << "--color"
|
11
|
+
end
|
12
|
+
|
13
|
+
Rake::RDocTask.new do |rdoc|
|
14
|
+
rdoc.title = "Savon"
|
15
|
+
rdoc.rdoc_dir = "rdoc"
|
16
|
+
rdoc.main = "README.rdoc"
|
17
|
+
rdoc.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
18
|
+
rdoc.options = ["--line-numbers", "--inline-source"]
|
19
|
+
end
|
20
|
+
|
21
|
+
begin
|
22
|
+
require "jeweler"
|
23
|
+
Jeweler::Tasks.new do |spec|
|
24
|
+
spec.name = "savon"
|
25
|
+
spec.author = "Daniel Harrington"
|
26
|
+
spec.email = "me@rubiii.com"
|
27
|
+
spec.homepage = "http://github.com/rubiii/savon"
|
28
|
+
spec.summary = "SOAP client library to enjoy"
|
29
|
+
spec.description = spec.summary
|
30
|
+
|
31
|
+
spec.files = FileList["[A-Z]*", "{lib,spec}/**/*.{rb,xml}"]
|
32
|
+
|
33
|
+
spec.rdoc_options += [
|
34
|
+
"--title", "Savon",
|
35
|
+
"--main", "README.rdoc",
|
36
|
+
"--line-numbers",
|
37
|
+
"--inline-source"
|
38
|
+
]
|
39
|
+
|
40
|
+
spec.add_runtime_dependency("hpricot", "0.8.241")
|
41
|
+
spec.add_runtime_dependency("rubiii-apricoteatsgorilla", "0.5.9")
|
42
|
+
|
43
|
+
spec.add_development_dependency("rspec", ">= 1.2.8")
|
44
|
+
spec.add_development_dependency("rr", ">= 0.10.0")
|
45
|
+
end
|
46
|
+
rescue LoadError
|
47
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
48
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2.7
|
data/lib/savon.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# Raised by the <tt>on_http_error</tt> method in case of an HTTP error.
|
4
|
+
# <tt>on_http_error</tt> may be overwritten to customize error handling.
|
5
|
+
class HTTPError < StandardError; end
|
6
|
+
|
7
|
+
# Raised by the <tt>on_soap_fault</tt> method in case of a SOAP fault.
|
8
|
+
# <tt>on_soap_fault</tt> may be overwritten to customize error handling.
|
9
|
+
class SOAPFault < StandardError; end
|
10
|
+
|
11
|
+
# The logger to use.
|
12
|
+
@@logger = nil
|
13
|
+
|
14
|
+
# The log level to use.
|
15
|
+
@@log_level = :debug
|
16
|
+
|
17
|
+
# Sets the logger to use.
|
18
|
+
def self.logger=(logger)
|
19
|
+
@@logger = logger
|
20
|
+
end
|
21
|
+
|
22
|
+
# Sets the log level to use.
|
23
|
+
def self.log_level=(log_level)
|
24
|
+
@@log_level = log_level
|
25
|
+
end
|
26
|
+
|
27
|
+
# Logs a given +message+ using the +@@logger+ instance or yields the logger
|
28
|
+
# to a given +block+ for logging multiple messages at once.
|
29
|
+
def self.log(message = nil)
|
30
|
+
if @@logger
|
31
|
+
@@logger.send(@@log_level, message) if message
|
32
|
+
yield @@logger if block_given?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
%w(net/http uri rubygems hpricot apricoteatsgorilla).each do |gem|
|
39
|
+
require gem
|
40
|
+
end
|
41
|
+
|
42
|
+
%w(service wsdl).each do |file|
|
43
|
+
require File.join(File.dirname(__FILE__), "savon", file)
|
44
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# == Savon::Service
|
4
|
+
#
|
5
|
+
# Savon::Service is a SOAP client library to enjoy. The goal is to minimize
|
6
|
+
# the overhead of working with SOAP services and provide a lightweight
|
7
|
+
# alternative to other libraries.
|
8
|
+
#
|
9
|
+
# ==== Example
|
10
|
+
#
|
11
|
+
# proxy = Savon::Service.new("http://example.com/ExampleService?wsdl")
|
12
|
+
# response = proxy.find_user_by_id(:id => 123)
|
13
|
+
class Service
|
14
|
+
|
15
|
+
# Supported SOAP versions.
|
16
|
+
SOAPVersions = [1, 2]
|
17
|
+
|
18
|
+
# Content-Types by SOAP version.
|
19
|
+
ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
|
20
|
+
|
21
|
+
# Accessor for the WSSE username.
|
22
|
+
attr_accessor :wsse_username
|
23
|
+
|
24
|
+
# Accessor for the WSSE password.
|
25
|
+
attr_accessor :wsse_password
|
26
|
+
|
27
|
+
# Sets whether the WSSE password should be encrypted.
|
28
|
+
attr_writer :wsse_password_digest
|
29
|
+
|
30
|
+
# Initializer expects an +endpoint+ URI and takes an optional SOAP +version+.
|
31
|
+
def initialize(endpoint, version = 1)
|
32
|
+
raise ArgumentError, "Invalid endpoint: #{endpoint}" unless /^http.+/ === endpoint
|
33
|
+
raise ArgumentError, "Invalid version: #{version}" unless SOAPVersions.include? version
|
34
|
+
@endpoint = URI(endpoint)
|
35
|
+
@version = version
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns an instance of Savon::WSDL.
|
39
|
+
def wsdl
|
40
|
+
@wsdl ||= WSDL.new(@endpoint, http)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns whether the WSSE password should be encrypted. Defaults to +false+.
|
44
|
+
def wsse_password_digest?
|
45
|
+
@wsse_password_digest == true
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# Dispatches a SOAP request, handles any HTTP errors and SOAP faults
|
51
|
+
# and returns the SOAP response.
|
52
|
+
def dispatch(soap_action, soap_body, response_xpath)
|
53
|
+
ApricotEatsGorilla.nodes_to_namespace = { :wsdl => wsdl.choice_elements }
|
54
|
+
headers, body = build_request_parameters(soap_action, soap_body)
|
55
|
+
|
56
|
+
Savon.log("SOAP request: #{@endpoint}")
|
57
|
+
Savon.log(headers.map { |k, v| "#{k}: #{v}" }.join(", "))
|
58
|
+
Savon.log(body)
|
59
|
+
|
60
|
+
response = http.request_post(@endpoint.path, body, headers)
|
61
|
+
|
62
|
+
Savon.log("SOAP response (status #{response.code}):")
|
63
|
+
Savon.log(response.body)
|
64
|
+
|
65
|
+
soap_fault = ApricotEatsGorilla[response.body, "//soap:Fault"]
|
66
|
+
raise_soap_fault(soap_fault) if soap_fault && !soap_fault.empty?
|
67
|
+
raise_http_error(response) if response.code.to_i >= 300
|
68
|
+
|
69
|
+
ApricotEatsGorilla[response.body, response_xpath]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Expects the requested +soap_action+ and +soap_body+ and builds and
|
73
|
+
# returns the request header and body to dispatch a SOAP request.
|
74
|
+
def build_request_parameters(soap_action, soap_body)
|
75
|
+
headers = { "Content-Type" => ContentType[@version], "SOAPAction" => soap_action }
|
76
|
+
namespaces = { :wsdl => wsdl.namespace_uri }
|
77
|
+
|
78
|
+
body = ApricotEatsGorilla.soap_envelope(namespaces, wsse, @version) do
|
79
|
+
ApricotEatsGorilla["wsdl:#{soap_action}" => soap_body]
|
80
|
+
end
|
81
|
+
[headers, body]
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the WSSE arguments if :wsse_username and :wsse_password are set.
|
85
|
+
def wsse
|
86
|
+
if @wsse_username && @wsse_password
|
87
|
+
{ :username => @wsse_username, :password => @wsse_password,
|
88
|
+
:password_digest => wsse_password_digest? }
|
89
|
+
else
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Expects a Hash containing information about a SOAP fault and raises
|
95
|
+
# a Savon::SOAPFault.
|
96
|
+
def raise_soap_fault(soap_fault)
|
97
|
+
message = case @version
|
98
|
+
when 1
|
99
|
+
"#{soap_fault[:faultcode]}: #{soap_fault[:faultstring]}"
|
100
|
+
else
|
101
|
+
"#{soap_fault[:code][:value]}: #{soap_fault[:reason][:text]}"
|
102
|
+
end
|
103
|
+
raise SOAPFault, message
|
104
|
+
end
|
105
|
+
|
106
|
+
# Expects a Net::HTTPResponse and raises a Savon::HTTPError.
|
107
|
+
def raise_http_error(response)
|
108
|
+
raise HTTPError, "#{response.message} (#{response.code}): #{response.body}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns a Net::HTTP instance.
|
112
|
+
def http
|
113
|
+
@http ||= Net::HTTP.new(@endpoint.host, @endpoint.port)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Catches calls to SOAP actions, checks if the method called was found in
|
117
|
+
# the WSDL and dispatches the SOAP action if it's valid. Takes an optional
|
118
|
+
# Hash of options to be passed to the SOAP action and an optional XPath-
|
119
|
+
# Expression to define a custom XML root node to start parsing the SOAP
|
120
|
+
# response at.
|
121
|
+
def method_missing(method, *args)
|
122
|
+
soap_action = camelize(method)
|
123
|
+
super unless wsdl.soap_actions.include? soap_action
|
124
|
+
soap_body = args[0] || {}
|
125
|
+
response_xpath = args[1] || "//return"
|
126
|
+
dispatch(soap_action, soap_body, response_xpath)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Converts a given +string+ from snake_case to lowerCamelCase.
|
130
|
+
def camelize(string)
|
131
|
+
string.to_s.gsub(/_(.)/) { $1.upcase } if string
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
data/lib/savon/wsdl.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# Savon::WSDL represents the WSDL document.
|
4
|
+
class WSDL
|
5
|
+
|
6
|
+
# Returns the namespace URI.
|
7
|
+
def namespace_uri
|
8
|
+
@namespace ||= parse_namespace_uri
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns an Array of available SOAP actions.
|
12
|
+
def soap_actions
|
13
|
+
@soap_actions ||= parse_soap_actions
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns an Array of choice elements.
|
17
|
+
def choice_elements
|
18
|
+
@choice_elements ||= parse_choice_elements
|
19
|
+
end
|
20
|
+
|
21
|
+
# Initializer expects the endpoint +uri+ and a Net::HTTP instance (+http+).
|
22
|
+
def initialize(uri, http)
|
23
|
+
@uri, @http = uri, http
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns the body of the Net::HTTPResponse from the WSDL request.
|
27
|
+
def to_s
|
28
|
+
@response ? @response.body : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Returns an Hpricot::Document of the WSDL. Retrieves the WSDL from the
|
34
|
+
# endpoint URI in case it wasn't retrieved already.
|
35
|
+
def document
|
36
|
+
unless @document
|
37
|
+
@response = @http.get("#{@uri.path}?#{@uri.query}")
|
38
|
+
@document = Hpricot.XML(@response.body)
|
39
|
+
raise ArgumentError, "Unable to find WSDL at: #{@uri}" if
|
40
|
+
!soap_actions || soap_actions.empty?
|
41
|
+
end
|
42
|
+
@document
|
43
|
+
end
|
44
|
+
|
45
|
+
# Parses the WSDL for the namespace URI.
|
46
|
+
def parse_namespace_uri
|
47
|
+
definitions = document.at("//wsdl:definitions")
|
48
|
+
definitions.get_attribute("targetNamespace") if definitions
|
49
|
+
end
|
50
|
+
|
51
|
+
# Parses the WSDL for available SOAP actions.
|
52
|
+
def parse_soap_actions
|
53
|
+
soap_actions = document.search("[@soapAction]")
|
54
|
+
|
55
|
+
soap_actions.collect do |soap_action|
|
56
|
+
soap_action.parent.get_attribute("name")
|
57
|
+
end if soap_actions
|
58
|
+
end
|
59
|
+
|
60
|
+
# Parses the WSDL for choice elements.
|
61
|
+
def parse_choice_elements
|
62
|
+
choice_elements = document.search("//xs:choice//xs:element")
|
63
|
+
|
64
|
+
choice_elements.collect do |choice_element|
|
65
|
+
choice_element.get_attribute("ref").sub(/(.+):/, "")
|
66
|
+
end if choice_elements
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
|
2
|
+
<soap:Body>
|
3
|
+
<ns2:findUserResponse xmlns:ns2="http://v1_0.ws.user.example.com">
|
4
|
+
<return>
|
5
|
+
<id>123</id>
|
6
|
+
<email>thedude@example.com</email>
|
7
|
+
<username>thedude</username>
|
8
|
+
<firstname>The</firstname>
|
9
|
+
<lastname>Dude</lastname>
|
10
|
+
</return>
|
11
|
+
</ns2:findUserResponse>
|
12
|
+
</soap:Body>
|
13
|
+
</soap:Envelope>
|
@@ -0,0 +1,106 @@
|
|
1
|
+
<wsdl:definitions
|
2
|
+
name="UserWebService"
|
3
|
+
targetNamespace="http://v1_0.ws.user.example.com"
|
4
|
+
xmlns:ns1="http://cxf.apache.org/bindings/xformat"
|
5
|
+
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
|
6
|
+
xmlns:tns="http://v1_0.ws.user.example.com"
|
7
|
+
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
|
8
|
+
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
9
|
+
|
10
|
+
<wsdl:types>
|
11
|
+
<xs:schema
|
12
|
+
attributeFormDefault="unqualified"
|
13
|
+
elementFormDefault="unqualified"
|
14
|
+
targetNamespace="http://v1_0.ws.user.example.com"
|
15
|
+
xmlns:tns="http://v1_0.ws.user.example.com"
|
16
|
+
xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
17
|
+
<xs:element name="findUser" type="tns:findUser" />
|
18
|
+
<xs:element name="findUserRequest" type="tns:findUserRequest" />
|
19
|
+
<xs:element name="baseFindUserRequest" type="tns:baseFindUserRequest" />
|
20
|
+
<xs:element name="idCredential" type="tns:idCredential" />
|
21
|
+
<xs:element name="emailCredential" type="tns:emailCredential" />
|
22
|
+
<xs:element name="findUserResponse" type="tns:findUserResponse" />
|
23
|
+
<xs:element name="userResponse" type="tns:userResponse" />
|
24
|
+
|
25
|
+
<xs:complexType name="findUser">
|
26
|
+
<xs:sequence>
|
27
|
+
<xs:element minOccurs="1" name="requestData" type="tns:findUserRequest" />
|
28
|
+
</xs:sequence>
|
29
|
+
</xs:complexType>
|
30
|
+
<xs:complexType name="findUserRequest">
|
31
|
+
<xs:complexContent>
|
32
|
+
<xs:extension base="tns:baseFindUserRequest">
|
33
|
+
<xs:sequence>
|
34
|
+
<xs:element minOccurs="0" name="mandant" type="tns:mandant" />
|
35
|
+
</xs:sequence>
|
36
|
+
</xs:extension>
|
37
|
+
</xs:complexContent>
|
38
|
+
</xs:complexType>
|
39
|
+
<xs:complexType name="baseFindUserRequest">
|
40
|
+
<xs:choice>
|
41
|
+
<xs:element ref="tns:idCredential" />
|
42
|
+
<xs:element ref="tns:emailCredential" />
|
43
|
+
</xs:choice>
|
44
|
+
</xs:complexType>
|
45
|
+
<xs:complexType name="idCredential">
|
46
|
+
<xs:sequence>
|
47
|
+
<xs:element name="id" type="xs:string" />
|
48
|
+
<xs:element name="token" type="xs:string" />
|
49
|
+
</xs:sequence>
|
50
|
+
</xs:complexType>
|
51
|
+
<xs:complexType name="emailCredential">
|
52
|
+
<xs:sequence>
|
53
|
+
<xs:element name="email" type="xs:string" />
|
54
|
+
<xs:element name="token" type="xs:string" />
|
55
|
+
</xs:sequence>
|
56
|
+
</xs:complexType>
|
57
|
+
<xs:complexType name="findUserResponse">
|
58
|
+
<xs:sequence>
|
59
|
+
<xs:element minOccurs="0" name="return" type="tns:userResponse" />
|
60
|
+
</xs:sequence>
|
61
|
+
</xs:complexType>
|
62
|
+
<xs:complexType name="userResponse">
|
63
|
+
<xs:sequence>
|
64
|
+
<xs:element minOccurs="0" name="id" type="xs:string" />
|
65
|
+
<xs:element minOccurs="0" name="email" type="xs:string" />
|
66
|
+
<xs:element minOccurs="0" name="username" type="xs:string" />
|
67
|
+
<xs:element minOccurs="0" name="firstname" type="xs:string" />
|
68
|
+
<xs:element minOccurs="0" name="lastname" type="xs:string" />
|
69
|
+
</xs:sequence>
|
70
|
+
</xs:complexType>
|
71
|
+
</xs:schema>
|
72
|
+
</wsdl:types>
|
73
|
+
|
74
|
+
<wsdl:message name="findUser">
|
75
|
+
<wsdl:part element="tns:findUser" name="parameters"></wsdl:part>
|
76
|
+
</wsdl:message>
|
77
|
+
<wsdl:message name="findUserResponse">
|
78
|
+
<wsdl:part element="tns:findUserResponse" name="parameters"></wsdl:part>
|
79
|
+
</wsdl:message>
|
80
|
+
|
81
|
+
<wsdl:portType name="UserWebService">
|
82
|
+
<wsdl:operation name="findUser">
|
83
|
+
<wsdl:input message="tns:findUser" name="findUser"></wsdl:input>
|
84
|
+
<wsdl:output message="tns:findUserResponse" name="findUserResponse"></wsdl:output>
|
85
|
+
</wsdl:operation>
|
86
|
+
</wsdl:portType>
|
87
|
+
|
88
|
+
<wsdl:binding name="UserWebServiceSoapBinding" type="tns:UserWebService">
|
89
|
+
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
|
90
|
+
<wsdl:operation name="findUser">
|
91
|
+
<soap:operation soapAction="" style="document" />
|
92
|
+
<wsdl:input name="findUser">
|
93
|
+
<soap:body use="literal" />
|
94
|
+
</wsdl:input>
|
95
|
+
<wsdl:output name="findUserResponse">
|
96
|
+
<soap:body use="literal" />
|
97
|
+
</wsdl:output>
|
98
|
+
</wsdl:operation>
|
99
|
+
</wsdl:binding>
|
100
|
+
|
101
|
+
<wsdl:service name="UserWebService">
|
102
|
+
<wsdl:port binding="tns:UserWebServiceSoapBinding" name="UserWebServicePort">
|
103
|
+
<soap:address location="http://example.com/user/1.0/UserService" />
|
104
|
+
</wsdl:port>
|
105
|
+
</wsdl:service>
|
106
|
+
</wsdl:definitions>
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe Savon::Service do
|
4
|
+
include SpecHelper
|
5
|
+
|
6
|
+
# initialize
|
7
|
+
describe "initialize" do
|
8
|
+
it "raises an ArgumentError when called with an invalid endpoint" do
|
9
|
+
["", nil, "invalid", 123].each do |argument|
|
10
|
+
lambda { Savon::Service.new(argument) }.should raise_error(ArgumentError)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
it "raises an ArgumentError when called with an invalid version" do
|
15
|
+
["", nil, "invalid", 123].each do |argument|
|
16
|
+
lambda { Savon::Service.new("http://example.com", argument) }.
|
17
|
+
should raise_error(ArgumentError)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# wsdl
|
23
|
+
describe "wsdl" do
|
24
|
+
before { @service = new_service_instance }
|
25
|
+
|
26
|
+
it "returns an instance of Savon::WSDL" do
|
27
|
+
@service.wsdl.should be_a(Savon::WSDL)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns the exact same Savon::WSDL instance every time" do
|
31
|
+
@service.wsdl.should equal(@service.wsdl)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# method_missing
|
36
|
+
describe "method_missing" do
|
37
|
+
before { @service = new_service_instance }
|
38
|
+
|
39
|
+
it "raises a NoMethodError when called with an invalid soap_action" do
|
40
|
+
lambda { @service.invalid_action }.should raise_error(NoMethodError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "by default returns content from the response using the '//return' XPath" do
|
44
|
+
@service.find_user.should == { :firstname => "The", :lastname => "Dude",
|
45
|
+
:email => "thedude@example.com", :username => "thedude", :id => "123" }
|
46
|
+
end
|
47
|
+
|
48
|
+
it "returns the content of the response starting at a custom XPath" do
|
49
|
+
@service.find_user(nil, "//email").should == "thedude@example.com"
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns nil if a given XPath does not match anything from the SOAP response" do
|
53
|
+
@service.find_user(nil, "//doesNotMatchAnything").should be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it "raises a Savon::SOAPFault in case of a SOAP fault" do
|
57
|
+
@service = new_service_instance(:soap_fault => true)
|
58
|
+
lambda { @service.find_user }.should raise_error(Savon::SOAPFault)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "raises a Savon::HTTPError in case the server returned an error code and no SOAP fault" do
|
62
|
+
@service = new_service_instance(:http_error => true)
|
63
|
+
lambda { @service.find_user }.should raise_error(Savon::HTTPError)
|
64
|
+
end
|
65
|
+
|
66
|
+
it "raises a Savon::SOAPFault in case the server returned an error code and a SOAP fault" do
|
67
|
+
@service = new_service_instance(:soap_fault => true, :http_error => true)
|
68
|
+
lambda { @service.find_user }.should raise_error(Savon::SOAPFault)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "..", "spec_helper")
|
2
|
+
|
3
|
+
describe Savon::WSDL do
|
4
|
+
include SpecHelper
|
5
|
+
|
6
|
+
#namespace_uri
|
7
|
+
describe "namespace_uri" do
|
8
|
+
before { @wsdl = new_wsdl }
|
9
|
+
|
10
|
+
it "returns the namespace URI from the WSDL" do
|
11
|
+
@wsdl.namespace_uri == UserFixture.namespace_uri
|
12
|
+
end
|
13
|
+
|
14
|
+
it "returns the same object every time" do
|
15
|
+
@wsdl.namespace_uri.should equal(@wsdl.namespace_uri)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# soap_actions
|
20
|
+
describe "soap_actions" do
|
21
|
+
before { @wsdl = new_wsdl }
|
22
|
+
|
23
|
+
it "returns the SOAP actions from the WSDL" do
|
24
|
+
@wsdl.soap_actions == UserFixture.soap_actions
|
25
|
+
end
|
26
|
+
|
27
|
+
it "returns the same object every time" do
|
28
|
+
@wsdl.soap_actions.should equal(@wsdl.soap_actions)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# choice_elements
|
33
|
+
describe "choice_elements" do
|
34
|
+
before { @wsdl = new_wsdl }
|
35
|
+
|
36
|
+
it "returns the choice elements from the WSDL" do
|
37
|
+
@wsdl.choice_elements == UserFixture.choice_elements
|
38
|
+
end
|
39
|
+
|
40
|
+
it "returns the same object every time" do
|
41
|
+
@wsdl.choice_elements.should equal(@wsdl.choice_elements)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# initialize
|
46
|
+
describe "initialize" do
|
47
|
+
it "expects an endpoint URI and a Net::HTTP instance" do
|
48
|
+
@wsdl = Savon::WSDL.new(some_uri, some_http)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# to_s
|
53
|
+
describe "to_s" do
|
54
|
+
before { @wsdl = new_wsdl }
|
55
|
+
|
56
|
+
it "returns nil before the WSDL document was retrieved" do
|
57
|
+
@wsdl.to_s.should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it "returns the response body when available" do
|
61
|
+
@wsdl.soap_actions # trigger http request
|
62
|
+
@wsdl.to_s.should == UserFixture.user_wsdl
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
gem "rspec", ">= 1.2.8"
|
3
|
+
require "spec"
|
4
|
+
require "rr"
|
5
|
+
require File.join(File.dirname(__FILE__), "..", "lib", "savon")
|
6
|
+
|
7
|
+
Spec::Runner.configure do |config|
|
8
|
+
config.mock_with :rr
|
9
|
+
end
|
10
|
+
|
11
|
+
module SpecHelper
|
12
|
+
def some_url
|
13
|
+
"http://example.com"
|
14
|
+
end
|
15
|
+
|
16
|
+
def some_uri
|
17
|
+
URI(some_url)
|
18
|
+
end
|
19
|
+
|
20
|
+
def some_http
|
21
|
+
Net::HTTP.new(some_uri.host, some_uri.port)
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_wsdl
|
25
|
+
Savon::WSDL.new(some_uri, UserFixture.http_mock)
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_service_instance(options = {})
|
29
|
+
service = Savon::Service.new(some_url)
|
30
|
+
service.instance_variable_set("@http", UserFixture.http_mock(options))
|
31
|
+
service
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class UserFixture
|
36
|
+
extend RR::Adapters::RRMethods
|
37
|
+
|
38
|
+
class << self
|
39
|
+
include SpecHelper
|
40
|
+
|
41
|
+
def namespace_uri
|
42
|
+
"http://v1_0.ws.user.example.com"
|
43
|
+
end
|
44
|
+
|
45
|
+
def soap_actions
|
46
|
+
%w(findUser)
|
47
|
+
end
|
48
|
+
|
49
|
+
def choice_elements
|
50
|
+
%w(idCredential emailCredential)
|
51
|
+
end
|
52
|
+
|
53
|
+
def user_wsdl
|
54
|
+
load_fixture("user_wsdl.xml")
|
55
|
+
end
|
56
|
+
|
57
|
+
def user_response
|
58
|
+
load_fixture("user_response.xml")
|
59
|
+
end
|
60
|
+
|
61
|
+
def soap_fault
|
62
|
+
load_fixture("soap_fault.xml")
|
63
|
+
end
|
64
|
+
|
65
|
+
def http_mock(options = {})
|
66
|
+
response_body = options[:soap_fault] ? soap_fault : user_response
|
67
|
+
response_code = options[:http_error] ? 500 : 200
|
68
|
+
response_body = "" if options[:http_error] && !options[:soap_fault]
|
69
|
+
generate_http_mock(soap_response_mock(response_body, response_code))
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def load_fixture(file)
|
75
|
+
file_path = File.join(File.dirname(__FILE__), "fixtures", file)
|
76
|
+
IO.readlines(file_path, "").to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
def generate_http_mock(soap_response)
|
80
|
+
mock = some_http
|
81
|
+
stub(mock).get(anything) { wsdl_response_mock(user_wsdl) }
|
82
|
+
stub(mock).request_post(anything, anything, anything) { soap_response }
|
83
|
+
mock
|
84
|
+
end
|
85
|
+
|
86
|
+
def wsdl_response_mock(response_body)
|
87
|
+
mock = mock()
|
88
|
+
stub(mock).body { response_body }
|
89
|
+
mock
|
90
|
+
end
|
91
|
+
|
92
|
+
def soap_response_mock(response_body, response_code)
|
93
|
+
mock = mock()
|
94
|
+
stub(mock).body { response_body }
|
95
|
+
stub(mock).code { response_code }
|
96
|
+
stub(mock).message { "whatever" }
|
97
|
+
mock
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: savon
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel Harrington
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-06 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: hpricot
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - "="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.8.241
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rubiii-apricoteatsgorilla
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.5.9
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rspec
|
37
|
+
type: :development
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.2.8
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: rr
|
47
|
+
type: :development
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.10.0
|
54
|
+
version:
|
55
|
+
description: SOAP client library to enjoy
|
56
|
+
email: me@rubiii.com
|
57
|
+
executables: []
|
58
|
+
|
59
|
+
extensions: []
|
60
|
+
|
61
|
+
extra_rdoc_files:
|
62
|
+
- README.rdoc
|
63
|
+
files:
|
64
|
+
- README.rdoc
|
65
|
+
- Rakefile
|
66
|
+
- VERSION
|
67
|
+
- lib/savon.rb
|
68
|
+
- lib/savon/service.rb
|
69
|
+
- lib/savon/wsdl.rb
|
70
|
+
- spec/fixtures/soap_fault.xml
|
71
|
+
- spec/fixtures/user_response.xml
|
72
|
+
- spec/fixtures/user_wsdl.xml
|
73
|
+
- spec/savon/service_spec.rb
|
74
|
+
- spec/savon/wsdl_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/rubiii/savon
|
78
|
+
licenses: []
|
79
|
+
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options:
|
82
|
+
- --charset=UTF-8
|
83
|
+
- --title
|
84
|
+
- Savon
|
85
|
+
- --main
|
86
|
+
- README.rdoc
|
87
|
+
- --line-numbers
|
88
|
+
- --inline-source
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: "0"
|
96
|
+
version:
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: "0"
|
102
|
+
version:
|
103
|
+
requirements: []
|
104
|
+
|
105
|
+
rubyforge_project:
|
106
|
+
rubygems_version: 1.3.5
|
107
|
+
signing_key:
|
108
|
+
specification_version: 3
|
109
|
+
summary: SOAP client library to enjoy
|
110
|
+
test_files:
|
111
|
+
- spec/savon/service_spec.rb
|
112
|
+
- spec/savon/wsdl_spec.rb
|
113
|
+
- spec/spec_helper.rb
|