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 CHANGED
@@ -1 +1 @@
1
- 0.5.3
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
- # stdlib
24
- require "logger"
25
- require "net/http"
26
- require "net/https"
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
- require "builder"
34
- require "crack/xml"
35
-
36
- # savon
37
- require "savon/core_ext"
38
- require "savon/validation"
39
- require "savon/wsse"
40
- require "savon/soap"
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 the Net::HTTPResponse of the last SOAP request.
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.soap_actions.include? method
20
+ return true if @wsdl.respond_to? method
59
21
  super
60
22
  end
61
23
 
62
24
  private
63
25
 
64
- # Behaves as usual, but dispatches SOAP requests to SOAP actions matching
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
- soap_action = @wsdl.mapped_soap_actions[method]
68
- super unless soap_action
69
- #soap_action = "User.GetApiKey"
28
+ super unless @wsdl.respond_to? method
70
29
 
71
- soap_body, options = args[0] || {}, args[1] || {}
72
- validate_arguments! soap_body, options, block
73
- dispatch soap_action, soap_body, options, block
30
+ setup method, &block
31
+ dispatch method
74
32
  end
75
33
 
76
- # Dispatches a given +soap_action+ with a given +soap_body+, +options+
77
- # and a +block+.
78
- def dispatch(soap_action, soap_body, options, block = nil)
79
- @soap = SOAP.new soap_action, soap_body, options, @wsdl.namespace_uri
80
- @response = @request.soap @soap
81
- response_process(block).call @response
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
- # Returns the response process to use.
85
- def response_process(block)
86
- block || self.class.response_process
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
- # Validates the given +soap_body+, +options+ and +response_process+.
90
- def validate_arguments!(soap_body, options = {}, response_process = nil)
91
- validate! :soap_body, soap_body if soap_body
92
- validate! :response_process, response_process if response_process
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
@@ -1,6 +1,3 @@
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/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
@@ -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
@@ -1,5 +1,10 @@
1
1
  class String
2
2
 
3
+ # Returns a random String of a given +length+.
4
+ def self.random(length = 100)
5
+ (0...length).map { ("a".."z").to_a[rand(26)] }.join
6
+ end
7
+
3
8
  # Returns the String in snake_case.
4
9
  def snakecase
5
10
  str = dup
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
- validate! :endpoint, endpoint
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 (+soap+) instance
49
- # and returns the Net::HTTPResponse.
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
- soap_request
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.body
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
- # Accessor for the default SOAP version.
20
- attr_accessor :version
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 a SOAP +action+, +body+, +options+ and the +namespace_uri+.
25
- def initialize(action, body, options, namespace_uri)
26
- @action, @body = action, body
27
- @options, @namespace_uri = options, namespace_uri
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
- # Returns the SOAP action.
31
- attr_reader :action
38
+ # Sets the WSSE options.
39
+ attr_writer :wsse
32
40
 
33
- # Returns the SOAP options.
34
- attr_reader :options
41
+ # Accessor for the SOAP action.
42
+ attr_accessor :action
35
43
 
36
- # Returns the XML for a SOAP request.
37
- def body
38
- builder = Builder::XmlMarkup.new
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
- @xml_body ||= builder.env :Envelope, envelope_namespaces do |xml|
41
- xml.env(:Header) { envelope_header xml }
42
- xml.env(:Body) { envelope_body xml }
43
- end
48
+ # Returns the SOAP header. Defaults to an empty Hash.
49
+ def header
50
+ @header ||= {}
44
51
  end
45
52
 
46
- # Returns the SOAP version to use.
47
- def version
48
- @options[:soap_version] || self.class.version
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
- private
67
+ # Sets the SOAP version.
68
+ def version=(version)
69
+ @version = version if Savon::SOAPVersions.include? version
70
+ end
52
71
 
53
- # Returns a Hash of namespaces for the SOAP envelope.
54
- def envelope_namespaces
55
- { "xmlns:env" => SOAPNamespace[version], "xmlns:wsdl" => @namespace_uri }
72
+ # Returns the SOAP version. Defaults to the global default.
73
+ def version
74
+ @version ||= self.class.version
56
75
  end
57
76
 
58
- # Expects an instance of Builder::XmlMarkup and returns the XML for the
59
- # SOAP envelope header.
60
- def envelope_header(xml)
61
- wsse_header xml if wsse?
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
- # Expects an instance of Builder::XmlMarkup and returns the XML for the
65
- # SOAP envelope body.
66
- def envelope_body(xml)
67
- xml.wsdl(@action.to_sym) { xml << (@body.to_soap_xml rescue @body.to_s) }
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