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 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