savon 0.7.4 → 0.7.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +6 -0
- data/README.rdoc +62 -0
- data/Rakefile +13 -9
- data/lib/savon.rb +16 -16
- data/lib/savon/client.rb +8 -21
- data/lib/savon/core_ext.rb +8 -2
- data/lib/savon/core_ext/array.rb +17 -0
- data/lib/savon/core_ext/datetime.rb +1 -1
- data/lib/savon/core_ext/hash.rb +51 -48
- data/lib/savon/core_ext/string.rb +15 -3
- data/lib/savon/core_ext/uri.rb +1 -1
- data/lib/savon/request.rb +2 -6
- data/lib/savon/response.rb +6 -3
- data/lib/savon/soap.rb +25 -8
- data/lib/savon/wsdl.rb +10 -3
- data/lib/savon/wsdl_stream.rb +5 -8
- data/lib/savon/wsse.rb +9 -13
- data/readme/client.rdoc +18 -0
- data/readme/errors.rdoc +11 -0
- data/readme/logging.rdoc +11 -0
- data/readme/participate.rdoc +21 -0
- data/readme/request.rdoc +37 -0
- data/readme/response.rdoc +46 -0
- data/readme/soap.rdoc +71 -0
- data/readme/value_mapping.rdoc +49 -0
- data/readme/wsdl.rdoc +39 -0
- data/readme/wsse.rdoc +28 -0
- data/spec/basic_spec_helper.rb +0 -1
- data/spec/savon/client_spec.rb +1 -1
- data/spec/savon/core_ext/array_spec.rb +19 -0
- data/spec/savon/core_ext/hash_spec.rb +94 -63
- data/spec/savon/core_ext/string_spec.rb +15 -0
- data/spec/savon/request_spec.rb +2 -2
- data/spec/savon/soap_spec.rb +40 -16
- metadata +17 -6
- data/README.textile +0 -71
- data/spec/savon/savon_spec.rb +0 -23
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
== 0.7.5 (2010-02-19)
|
2
|
+
* Fix for issue #34 (soap_actions returns empty for wsdl12).
|
3
|
+
* Fix for issue #36 (Custom WSDL actions broken).
|
4
|
+
* Added feature requested in issue #35 (Setting an attribute on an element?).
|
5
|
+
|
1
6
|
== 0.7.4 (2010-02-02)
|
2
7
|
* Fix for issue #33 (undefined method 'start_with?').
|
3
8
|
|
@@ -137,3 +142,4 @@
|
|
137
142
|
|
138
143
|
== 0.5.0 (2009-11-29)
|
139
144
|
* Complete rewrite.
|
145
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
= Savon
|
2
|
+
|
3
|
+
==== Heavy metal Ruby SOAP client library
|
4
|
+
|
5
|
+
{RDoc}[http://rdoc.info/projects/rubiii/savon] | {Wiki}[http://wiki.github.com/rubiii/savon] | {Metrics}[http://getcaliper.com/caliper/project?repo=git://github.com/rubiii/savon.git]
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
$ gem install savon
|
10
|
+
|
11
|
+
Savon expects you to be familiar with SOAP, WSDL and tools like soapUI.
|
12
|
+
|
13
|
+
== Instantiate a client
|
14
|
+
|
15
|
+
Instantiate Savon::Client, passing in the WSDL of your service.
|
16
|
+
|
17
|
+
client = Savon::Client.new "http://example.com/UserService?wsdl"
|
18
|
+
|
19
|
+
== Calling a SOAP action
|
20
|
+
|
21
|
+
Assuming your service applies to the defaults, you can now call any available SOAP action.
|
22
|
+
|
23
|
+
response = client.get_all_users
|
24
|
+
|
25
|
+
Savon lets you call SOAP actions using snake_case, because even though they will propably be written in lowerCamelCase or CamelCase, it just feels much more natural.
|
26
|
+
|
27
|
+
== The WSDL object
|
28
|
+
|
29
|
+
Savon::WSDL represents the WSDL of your service, including information like the namespace URI and available SOAP actions.
|
30
|
+
|
31
|
+
client.wsdl.soap_actions
|
32
|
+
=> [:get_all_users, :get_user_by_id, :user_magic]
|
33
|
+
|
34
|
+
== The SOAP object
|
35
|
+
|
36
|
+
Savon::SOAP represents the SOAP request. Pass a block to your SOAP call and the SOAP object is passed to it as the first argument. The object allows setting the SOAP version, header, body and namespaces per request.
|
37
|
+
|
38
|
+
response = client.get_user_by_id { |soap| soap.body = { :id => 666 } }
|
39
|
+
|
40
|
+
== The WSSE object
|
41
|
+
|
42
|
+
Savon::WSSE represents WSSE authentication. Pass a block to your SOAP call and the WSSE object is passed to it as the second argument. The object allows setting the WSSE username, password and whether to use digest authentication.
|
43
|
+
|
44
|
+
response = client.get_user_by_id do |soap, wsse|
|
45
|
+
wsse.username = "gorilla"
|
46
|
+
wsse.password = "secret"
|
47
|
+
soap.body = { :id => 666 }
|
48
|
+
end
|
49
|
+
|
50
|
+
== The Response object
|
51
|
+
|
52
|
+
Savon::Response represents the HTTP and SOAP response. It contains and raises errors in case of an HTTP error or SOAP fault (unless disabled). Also you can get the response as XML (for parsing it with an XML library) or translated into a Hash.
|
53
|
+
|
54
|
+
== HTTP errors and SOAP faults
|
55
|
+
|
56
|
+
Savon raises a Savon::SOAPFault in case of a SOAP fault and a Savon::HTTPError in case of an HTTP error.
|
57
|
+
More information: {Errors}[http://wiki.github.com/rubiii/savon/errors]
|
58
|
+
|
59
|
+
== Logging
|
60
|
+
|
61
|
+
Savon logs each request and response to STDOUT. But there are a couple of options to change the default behavior.
|
62
|
+
More information: {Logging}[http://wiki.github.com/rubiii/savon/logging]
|
data/Rakefile
CHANGED
@@ -1,16 +1,14 @@
|
|
1
1
|
require "rake"
|
2
2
|
require "spec/rake/spectask"
|
3
3
|
require "spec/rake/verify_rcov"
|
4
|
-
require "rake/rdoctask"
|
5
4
|
|
6
|
-
task :default => :
|
5
|
+
task :default => :spec
|
7
6
|
|
8
7
|
Spec::Rake::SpecTask.new do |spec|
|
9
8
|
spec.spec_files = FileList["spec/{savon}/**/*_spec.rb"]
|
10
9
|
spec.spec_opts << "--color"
|
11
10
|
spec.libs += ["lib", "spec"]
|
12
11
|
spec.rcov = true
|
13
|
-
spec.rcov_dir = "rcov"
|
14
12
|
end
|
15
13
|
|
16
14
|
RCov::VerifyTask.new(:spec_verify => :spec) do |verify|
|
@@ -30,16 +28,22 @@ task :spec_integration do
|
|
30
28
|
end
|
31
29
|
end
|
32
30
|
|
33
|
-
desc "" # make this task invisible
|
31
|
+
desc "" # make this task invisible
|
34
32
|
Spec::Rake::SpecTask.new(:run_integration_spec) do |spec|
|
35
33
|
spec.spec_files = FileList["spec/{integration}/**/*_spec.rb"]
|
36
34
|
spec.spec_opts << "--color"
|
37
35
|
spec.libs += ["lib", "spec"]
|
38
36
|
end
|
39
37
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
rdoc
|
44
|
-
|
38
|
+
begin
|
39
|
+
require "hanna/rdoctask"
|
40
|
+
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
rdoc.title = "Savon - Heavy metal Ruby SOAP client library"
|
43
|
+
rdoc.rdoc_dir = "doc"
|
44
|
+
rdoc.rdoc_files.include("**/*.rdoc").include("lib/**/*.rb")
|
45
|
+
rdoc.options << "--line-numbers"
|
46
|
+
end
|
47
|
+
rescue LoadError
|
48
|
+
puts "'gem install hanna' for documentation"
|
45
49
|
end
|
data/lib/savon.rb
CHANGED
@@ -1,14 +1,5 @@
|
|
1
1
|
module Savon
|
2
2
|
|
3
|
-
# Supported SOAP versions.
|
4
|
-
SOAPVersions = [1, 2]
|
5
|
-
|
6
|
-
# SOAP xs:dateTime format.
|
7
|
-
SOAPDateTimeFormat = "%Y-%m-%dT%H:%M:%SZ"
|
8
|
-
|
9
|
-
# SOAP xs:dateTime Regexp.
|
10
|
-
SOAPDateTimeRegexp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
11
|
-
|
12
3
|
# Raised in case of an HTTP error.
|
13
4
|
class HTTPError < StandardError; end
|
14
5
|
|
@@ -18,13 +9,22 @@ module Savon
|
|
18
9
|
end
|
19
10
|
|
20
11
|
# standard libs
|
21
|
-
|
22
|
-
|
12
|
+
require "logger"
|
13
|
+
require "net/https"
|
14
|
+
require "base64"
|
15
|
+
require "digest/sha1"
|
16
|
+
require "rexml/document"
|
23
17
|
|
24
|
-
#
|
25
|
-
|
26
|
-
|
18
|
+
# gem dependencies
|
19
|
+
require "builder"
|
20
|
+
require "crack/xml"
|
27
21
|
|
28
22
|
# core files
|
29
|
-
|
30
|
-
|
23
|
+
require "savon/core_ext"
|
24
|
+
require "savon/wsse"
|
25
|
+
require "savon/soap"
|
26
|
+
require "savon/request"
|
27
|
+
require "savon/response"
|
28
|
+
require "savon/wsdl_stream"
|
29
|
+
require "savon/wsdl"
|
30
|
+
require "savon/client"
|
data/lib/savon/client.rb
CHANGED
@@ -29,30 +29,20 @@ module Savon
|
|
29
29
|
|
30
30
|
# Dispatches requests to SOAP actions matching a given +method+ name.
|
31
31
|
def method_missing(method, *args, &block) #:doc:
|
32
|
-
|
33
|
-
super
|
32
|
+
soap_action = soap_action_from method.to_s
|
33
|
+
super unless @wsdl.respond_to? soap_action
|
34
34
|
|
35
|
-
setup_objects operation_from(
|
35
|
+
setup_objects @wsdl.operation_from(soap_action), &block
|
36
36
|
Response.new @request.soap(@soap)
|
37
37
|
end
|
38
38
|
|
39
39
|
# Sets whether to use Savon::WSDL by a given +method+ name and
|
40
40
|
# removes exclamation marks from the given +method+ name.
|
41
|
-
def
|
42
|
-
|
43
|
-
@wsdl.enabled = false
|
44
|
-
method[0, method.length-1].to_sym
|
45
|
-
else
|
46
|
-
@wsdl.enabled = true
|
47
|
-
method.to_sym
|
48
|
-
end
|
49
|
-
end
|
41
|
+
def soap_action_from(method)
|
42
|
+
@wsdl.enabled = !method.ends_with?("!")
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
def operation_from(soap_call)
|
54
|
-
return @wsdl.operations[soap_call] if @wsdl.enabled?
|
55
|
-
{ :action => soap_call.to_soap_key, :input => soap_call.to_soap_key }
|
44
|
+
method.chop! if method.ends_with?("!")
|
45
|
+
method.to_sym
|
56
46
|
end
|
57
47
|
|
58
48
|
# Returns the SOAP endpoint.
|
@@ -63,11 +53,8 @@ module Savon
|
|
63
53
|
# Expects a SOAP operation Hash and sets up Savon::SOAP and Savon::WSSE.
|
64
54
|
# Yields them to a given +block+ in case one was given.
|
65
55
|
def setup_objects(operation, &block)
|
66
|
-
@soap, @wsse = SOAP.new, WSSE.new
|
67
|
-
@soap.action, @soap.input, @soap.endpoint = operation[:action], operation[:input], soap_endpoint
|
68
|
-
|
56
|
+
@soap, @wsse = SOAP.new(operation, soap_endpoint), WSSE.new
|
69
57
|
yield_objects &block if block
|
70
|
-
|
71
58
|
@soap.namespaces["xmlns:wsdl"] ||= @wsdl.namespace_uri if @wsdl.enabled?
|
72
59
|
@soap.wsse = @wsse
|
73
60
|
end
|
data/lib/savon/core_ext.rb
CHANGED
@@ -1,2 +1,8 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require "savon/core_ext/object"
|
2
|
+
require "savon/core_ext/string"
|
3
|
+
require "savon/core_ext/symbol"
|
4
|
+
require "savon/core_ext/datetime"
|
5
|
+
require "savon/core_ext/array"
|
6
|
+
require "savon/core_ext/hash"
|
7
|
+
require "savon/core_ext/uri"
|
8
|
+
require "savon/core_ext/net_http"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Array
|
2
|
+
|
3
|
+
# Translates the Array into SOAP request compatible XML. See: Hash#to_soap_xml.
|
4
|
+
def to_soap_xml(key)
|
5
|
+
xml = Builder::XmlMarkup.new
|
6
|
+
|
7
|
+
each do |item|
|
8
|
+
case item
|
9
|
+
when Array, Hash then xml.tag!(key) { xml << item.to_soap_xml }
|
10
|
+
else xml.tag!(key) { xml << item.to_soap_value }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
xml.target!
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/savon/core_ext/hash.rb
CHANGED
@@ -1,78 +1,81 @@
|
|
1
1
|
class Hash
|
2
2
|
|
3
|
-
#
|
4
|
-
|
5
|
-
|
6
|
-
# Error message for spurious :@inorder elements.
|
7
|
-
InOrderSpurious = "Spurious elements in :@inorder %s"
|
8
|
-
|
9
|
-
# Returns the values from the 'soap:Body' element or an empty Hash
|
10
|
-
# in case the node could not be found.
|
3
|
+
# Returns the values from the soap:Body element or an empty Hash in case the soap:Body tag could
|
4
|
+
# not be found.
|
11
5
|
def find_soap_body
|
12
|
-
envelope = self[
|
6
|
+
envelope = self[keys.first] || {}
|
13
7
|
body_key = envelope.keys.find { |key| /.+:Body/ =~ key } rescue nil
|
14
8
|
body_key ? envelope[body_key].map_soap_response : {}
|
15
9
|
end
|
16
10
|
|
17
|
-
#
|
11
|
+
# Translates the Hash into SOAP request compatible XML.
|
12
|
+
#
|
13
|
+
# === Example:
|
14
|
+
#
|
15
|
+
# { :find_user => { :id => 123, "wsdl:Key" => "api" } }.to_soap_xml
|
16
|
+
# # => "<findUser><id>123</id><wsdl:Key>api</wsdl:Key></findUser>"
|
18
17
|
#
|
19
|
-
#
|
20
|
-
# the
|
18
|
+
# Comes with a way to control the order of XML tags in case you're foced to do so (parameterOrder).
|
19
|
+
# Specify an optional Array under the :order! key reflecting the order of your keys.
|
20
|
+
# An ArgumentError is raised unless the Array contains the exact same/all keys of your Hash.
|
21
21
|
#
|
22
|
-
# ===
|
22
|
+
# === Example:
|
23
23
|
#
|
24
|
-
# { :find_user => { :id =>
|
25
|
-
# => "<findUser><id>
|
24
|
+
# { :find_user => { :name => "Eve", :id => 123, :order! => [:id, :name] } }.to_soap_xml
|
25
|
+
# # => "<findUser><id>123</id><name>Eve</name></findUser>"
|
26
26
|
#
|
27
|
-
#
|
28
|
-
#
|
27
|
+
# You can also specify attributes for XML tags by via an optional Hash under the :attributes! key.
|
28
|
+
#
|
29
|
+
# === Example:
|
30
|
+
#
|
31
|
+
# { :find_user => { :person => "Eve", :attributes! => { :person => { :id => 123 } } } }
|
32
|
+
# # => "<findUser><person id="123">Eve</person></findUser>"
|
29
33
|
def to_soap_xml
|
30
|
-
|
31
|
-
|
32
|
-
|
34
|
+
xml = Builder::XmlMarkup.new
|
35
|
+
attributes = delete(:attributes!) || {}
|
36
|
+
|
37
|
+
order.each do |key|
|
38
|
+
attrs = attributes[key] || {}
|
39
|
+
value = self[key]
|
40
|
+
key = key.to_soap_key
|
41
|
+
|
42
|
+
case value
|
43
|
+
when Array then xml << value.to_soap_xml(key)
|
44
|
+
when Hash then xml.tag!(key, attrs) { xml << value.to_soap_xml }
|
45
|
+
else xml.tag!(key, attrs) { xml << value.to_soap_value }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
xml.target!
|
33
50
|
end
|
34
51
|
|
35
|
-
# Maps keys and values of a Hash created from SOAP response XML to
|
36
|
-
# more convenient Ruby Objects.
|
52
|
+
# Maps keys and values of a Hash created from SOAP response XML to more convenient Ruby Objects.
|
37
53
|
def map_soap_response
|
38
54
|
inject({}) do |hash, (key, value)|
|
39
|
-
key = key.strip_namespace.snakecase.to_sym
|
40
|
-
|
41
55
|
value = case value
|
42
56
|
when Hash then value["xsi:nil"] ? nil : value.map_soap_response
|
43
57
|
when Array then value.map { |a_value| a_value.map_soap_response rescue a_value }
|
44
58
|
when String then value.map_soap_response
|
45
59
|
end
|
46
|
-
|
60
|
+
|
61
|
+
hash.merge key.strip_namespace.snakecase.to_sym => value
|
47
62
|
end
|
48
63
|
end
|
49
64
|
|
50
65
|
private
|
51
66
|
|
52
|
-
#
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
else
|
63
|
-
@soap_xml.tag!(key.to_soap_key) { @soap_xml << value.to_soap_value }
|
64
|
-
end
|
65
|
-
end
|
67
|
+
# Deletes and returns an Array of keys stored under the :order! key. Defaults to return the actual
|
68
|
+
# keys of this Hash if no :order! key could be found. Raises an ArgumentError in case the :order!
|
69
|
+
# Array does not match the Hash keys.
|
70
|
+
def order
|
71
|
+
order = delete :order!
|
72
|
+
order = keys unless order.kind_of? Array
|
73
|
+
|
74
|
+
missing, spurious = keys - order, order - keys
|
75
|
+
raise ArgumentError, "Missing elements in :order! #{missing.inspect}" unless missing.empty?
|
76
|
+
raise ArgumentError, "Spurious elements in :order! #{spurious.inspect}" unless spurious.empty?
|
66
77
|
|
67
|
-
|
68
|
-
# Raises an error in case an :@inorder Array does not match the Hash keys.
|
69
|
-
def inorder(hash)
|
70
|
-
inorder = hash.delete :@inorder
|
71
|
-
hash_keys = hash.keys
|
72
|
-
inorder = hash_keys unless inorder.kind_of? Array
|
73
|
-
raise InOrderMissing % (hash_keys - inorder).inspect unless (hash_keys - inorder).empty?
|
74
|
-
raise InOrderSpurious % (inorder - hash_keys).inspect unless (inorder - hash_keys).empty?
|
75
|
-
inorder
|
78
|
+
order
|
76
79
|
end
|
77
80
|
|
78
81
|
end
|
@@ -26,14 +26,26 @@ class String
|
|
26
26
|
str
|
27
27
|
end
|
28
28
|
|
29
|
+
# Returns whether the String starts with a given +prefix+.
|
30
|
+
def starts_with?(prefix)
|
31
|
+
prefix = prefix.to_s
|
32
|
+
self[0, prefix.length] == prefix
|
33
|
+
end unless defined? starts_with?
|
34
|
+
|
35
|
+
# Returns whether the String ends with a given +suffix+.
|
36
|
+
def ends_with?(suffix)
|
37
|
+
suffix = suffix.to_s
|
38
|
+
self[-suffix.length, suffix.length] == suffix
|
39
|
+
end unless defined? ends_with?
|
40
|
+
|
29
41
|
# Returns the String without namespace.
|
30
42
|
def strip_namespace
|
31
|
-
|
43
|
+
split(":").last
|
32
44
|
end
|
33
45
|
|
34
|
-
# Translates SOAP response values to
|
46
|
+
# Translates SOAP response values to Ruby Objects.
|
35
47
|
def map_soap_response
|
36
|
-
return DateTime.parse(
|
48
|
+
return DateTime.parse(self) if Savon::SOAP::DateTimeRegexp === self
|
37
49
|
return true if self.strip.downcase == "true"
|
38
50
|
return false if self.strip.downcase == "false"
|
39
51
|
self
|
data/lib/savon/core_ext/uri.rb
CHANGED
data/lib/savon/request.rb
CHANGED
@@ -125,6 +125,7 @@ module Savon
|
|
125
125
|
when :wsdl then Net::HTTP::Get.new @endpoint.request_uri
|
126
126
|
when :soap then Net::HTTP::Post.new @soap.endpoint.request_uri, soap_headers.merge(headers)
|
127
127
|
end
|
128
|
+
|
128
129
|
request.basic_auth(*@basic_auth) if @basic_auth
|
129
130
|
yield request if block_given?
|
130
131
|
request
|
@@ -137,12 +138,7 @@ module Savon
|
|
137
138
|
|
138
139
|
# Logs a given +message+.
|
139
140
|
def log(message)
|
140
|
-
self.class.logger.send self.class.log_level, message if log?
|
141
|
-
end
|
142
|
-
|
143
|
-
# Returns whether to log.
|
144
|
-
def log?
|
145
|
-
self.class.log? && self.class.logger.respond_to?(self.class.log_level)
|
141
|
+
self.class.logger.send self.class.log_level, message if self.class.log?
|
146
142
|
end
|
147
143
|
|
148
144
|
end
|