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 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
+
@@ -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 => :spec_verify
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 for "rake -T"
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
- Rake::RDocTask.new do |rdoc|
41
- rdoc.title = "Savon"
42
- rdoc.rdoc_dir = "rdoc"
43
- rdoc.rdoc_files.include("lib/**/*.rb")
44
- rdoc.options = ["--line-numbers", "--inline-source"]
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
@@ -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
- stdlibs = %w(logger net/https openssl base64 digest/sha1 rexml/document)
22
- stdlibs.each { |stdlib| require stdlib }
12
+ require "logger"
13
+ require "net/https"
14
+ require "base64"
15
+ require "digest/sha1"
16
+ require "rexml/document"
23
17
 
24
- # gems
25
- gems = %w(builder crack/xml)
26
- gems.each { |gem| require gem }
18
+ # gem dependencies
19
+ require "builder"
20
+ require "crack/xml"
27
21
 
28
22
  # core files
29
- files = %w(core_ext wsse soap request response wsdl_stream wsdl client)
30
- files.each { |file| require "savon/#{file}" }
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"
@@ -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
- soap_call = soap_call_from method.to_s
33
- super if @wsdl.enabled? && !@wsdl.respond_to?(soap_call)
32
+ soap_action = soap_action_from method.to_s
33
+ super unless @wsdl.respond_to? soap_action
34
34
 
35
- setup_objects operation_from(soap_call), &block
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 soap_call_from(method)
42
- if method[-1, 1] == "!"
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
- # Returns a SOAP operation Hash containing the SOAP action and input
52
- # for a given +soap_call+.
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
@@ -1,2 +1,8 @@
1
- files = %w(object string symbol datetime hash uri net_http)
2
- files.each { |file| require "savon/core_ext/#{file}" }
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
@@ -2,7 +2,7 @@ class DateTime
2
2
 
3
3
  # Returns the DateTime as an xs:dateTime formatted String.
4
4
  def to_soap_value
5
- strftime Savon::SOAPDateTimeFormat
5
+ strftime Savon::SOAP::DateTimeFormat
6
6
  end
7
7
 
8
8
  end
@@ -1,78 +1,81 @@
1
1
  class Hash
2
2
 
3
- # Error message for missing :@inorder elements.
4
- InOrderMissing = "Missing elements in :@inorder %s"
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[self.keys.first] || {}
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
- # Returns the Hash translated into SOAP request compatible XML.
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
- # To control the order of output, add a key of :@inorder with
20
- # the value being an Array listing keys in order.
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
- # === Examples
22
+ # === Example:
23
23
  #
24
- # { :find_user => { :id => 666 } }.to_soap_xml
25
- # => "<findUser><id>666</id></findUser>"
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
- # { :find_user => { :name => "Lucy", :id => 666, :@inorder => [:id, :name] } }.to_soap_xml
28
- # => "<findUser><id>666</id><name>Lucy</name></findUser>"
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
- @soap_xml = Builder::XmlMarkup.new
31
- inorder(self).each { |key| nested_data_to_soap_xml key, self[key] }
32
- @soap_xml.target!
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
- hash.merge key => value
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
- # Expects a Hash +key+ and +value+ and recursively creates an XML structure
53
- # representing the Hash content.
54
- def nested_data_to_soap_xml(key, value)
55
- case value
56
- when Array
57
- value.map { |subitem| nested_data_to_soap_xml key, subitem }
58
- when Hash
59
- @soap_xml.tag!(key.to_soap_key) do
60
- inorder(value).each { |subkey| nested_data_to_soap_xml subkey, value[subkey] }
61
- end
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
- # Takes a +hash+, removes the :@inorder marker and returns its keys.
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
- gsub /(.+:)(.+)/, '\2'
43
+ split(":").last
32
44
  end
33
45
 
34
- # Translates SOAP response values to more convenient Ruby Objects.
46
+ # Translates SOAP response values to Ruby Objects.
35
47
  def map_soap_response
36
- return DateTime.parse( self ) if Savon::SOAPDateTimeRegexp === self
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
@@ -3,7 +3,7 @@ module URI
3
3
 
4
4
  # Returns whether the URI hints to SSL.
5
5
  def ssl?
6
- /^https/ === @scheme
6
+ @scheme.starts_with? "https"
7
7
  end
8
8
 
9
9
  end
@@ -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