savon 0.7.9 → 0.8.0.beta.1

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.
Files changed (74) hide show
  1. data/.gitignore +9 -0
  2. data/.rspec +1 -0
  3. data/.yardopts +2 -0
  4. data/CHANGELOG.md +332 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +37 -0
  8. data/Rakefile +28 -39
  9. data/autotest/discover.rb +1 -0
  10. data/lib/savon.rb +10 -31
  11. data/lib/savon/client.rb +116 -98
  12. data/lib/savon/core_ext/array.rb +36 -22
  13. data/lib/savon/core_ext/datetime.rb +15 -6
  14. data/lib/savon/core_ext/hash.rb +122 -94
  15. data/lib/savon/core_ext/object.rb +19 -11
  16. data/lib/savon/core_ext/string.rb +62 -57
  17. data/lib/savon/core_ext/symbol.rb +13 -5
  18. data/lib/savon/error.rb +6 -0
  19. data/lib/savon/global.rb +75 -0
  20. data/lib/savon/http/error.rb +42 -0
  21. data/lib/savon/soap.rb +8 -283
  22. data/lib/savon/soap/fault.rb +48 -0
  23. data/lib/savon/soap/request.rb +61 -0
  24. data/lib/savon/soap/response.rb +65 -0
  25. data/lib/savon/soap/xml.rb +132 -0
  26. data/lib/savon/version.rb +2 -2
  27. data/lib/savon/wsdl/document.rb +107 -0
  28. data/lib/savon/wsdl/parser.rb +90 -0
  29. data/lib/savon/wsdl/request.rb +35 -0
  30. data/lib/savon/wsse.rb +42 -104
  31. data/savon.gemspec +26 -0
  32. data/spec/fixtures/response/response_fixture.rb +26 -26
  33. data/spec/fixtures/response/xml/list.xml +18 -0
  34. data/spec/fixtures/wsdl/wsdl_fixture.rb +6 -0
  35. data/spec/fixtures/wsdl/wsdl_fixture.yml +4 -4
  36. data/spec/savon/client_spec.rb +274 -51
  37. data/spec/savon/core_ext/datetime_spec.rb +1 -1
  38. data/spec/savon/core_ext/hash_spec.rb +40 -4
  39. data/spec/savon/core_ext/object_spec.rb +1 -1
  40. data/spec/savon/core_ext/string_spec.rb +0 -12
  41. data/spec/savon/http/error_spec.rb +52 -0
  42. data/spec/savon/savon_spec.rb +90 -0
  43. data/spec/savon/soap/fault_spec.rb +80 -0
  44. data/spec/savon/soap/request_spec.rb +45 -0
  45. data/spec/savon/soap/response_spec.rb +153 -0
  46. data/spec/savon/soap/xml_spec.rb +249 -0
  47. data/spec/savon/soap_spec.rb +4 -177
  48. data/spec/savon/{wsdl_spec.rb → wsdl/document_spec.rb} +54 -17
  49. data/spec/savon/wsdl/request_spec.rb +15 -0
  50. data/spec/savon/wsse_spec.rb +123 -92
  51. data/spec/spec_helper.rb +19 -4
  52. data/spec/support/endpoint.rb +25 -0
  53. metadata +97 -97
  54. data/.autotest +0 -5
  55. data/CHANGELOG +0 -176
  56. data/README.rdoc +0 -64
  57. data/lib/savon/core_ext.rb +0 -8
  58. data/lib/savon/core_ext/net_http.rb +0 -19
  59. data/lib/savon/core_ext/uri.rb +0 -10
  60. data/lib/savon/logger.rb +0 -56
  61. data/lib/savon/request.rb +0 -138
  62. data/lib/savon/response.rb +0 -174
  63. data/lib/savon/wsdl.rb +0 -137
  64. data/lib/savon/wsdl_stream.rb +0 -85
  65. data/spec/basic_spec_helper.rb +0 -11
  66. data/spec/endpoint_helper.rb +0 -23
  67. data/spec/http_stubs.rb +0 -26
  68. data/spec/integration/http_basic_auth_spec.rb +0 -16
  69. data/spec/integration/server.rb +0 -51
  70. data/spec/savon/core_ext/net_http_spec.rb +0 -38
  71. data/spec/savon/core_ext/uri_spec.rb +0 -19
  72. data/spec/savon/request_spec.rb +0 -117
  73. data/spec/savon/response_spec.rb +0 -179
  74. data/spec/spec.opts +0 -4
@@ -0,0 +1,48 @@
1
+ require "savon/error"
2
+ require "savon/soap/xml"
3
+
4
+ module Savon
5
+ module SOAP
6
+
7
+ # = Savon::SOAP::Fault
8
+ #
9
+ # Represents a SOAP fault. Contains the original <tt>HTTPI::Response</tt>.
10
+ class Fault < Error
11
+
12
+ # Expects an <tt>HTTPI::Response</tt>.
13
+ def initialize(http)
14
+ self.http = http
15
+ end
16
+
17
+ # Accessor for the <tt>HTTPI::Response</tt>.
18
+ attr_accessor :http
19
+
20
+ # Returns whether a SOAP fault is present.
21
+ def present?
22
+ @present ||= http.body =~ /<soap:Fault>/
23
+ end
24
+
25
+ # Returns the SOAP fault message.
26
+ def to_s
27
+ return "" unless present?
28
+ @message ||= message_by_version to_hash[:fault]
29
+ end
30
+
31
+ # Returns the SOAP response body as a Hash.
32
+ def to_hash
33
+ @hash ||= Savon::SOAP::XML.to_hash http.body
34
+ end
35
+
36
+ private
37
+
38
+ def message_by_version(fault)
39
+ if fault[:faultcode]
40
+ "(#{fault[:faultcode]}) #{fault[:faultstring]}"
41
+ elsif fault[:code]
42
+ "(#{fault[:code][:value]}) #{fault[:reason][:text]}"
43
+ end
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,61 @@
1
+ require "httpi"
2
+ require "savon/soap/response"
3
+
4
+ module Savon
5
+ module SOAP
6
+
7
+ # = Savon::SOAP::Request
8
+ #
9
+ # Executes SOAP requests.
10
+ class Request
11
+
12
+ # Content-Types by SOAP version.
13
+ ContentType = { 1 => "text/xml;charset=UTF-8", 2 => "application/soap+xml;charset=UTF-8" }
14
+
15
+ # Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object.
16
+ def initialize(request, soap)
17
+ self.request = setup(request, soap)
18
+ end
19
+
20
+ # Accessor for the <tt>HTTPI::Request</tt>.
21
+ attr_accessor :request
22
+
23
+ # Executes the request and returns the response.
24
+ def response
25
+ @response ||= with_logging { HTTPI.post request }
26
+ end
27
+
28
+ private
29
+
30
+ # Sets up the +request+ using a given +soap+ object.
31
+ def setup(request, soap)
32
+ request.url = soap.endpoint
33
+ request.headers["Content-Type"] ||= ContentType[soap.version]
34
+ request.body = soap.to_xml
35
+ request
36
+ end
37
+
38
+ # Logs the HTTP request, yields to a given +block+ and returns a <tt>Savon::SOAP::Response</tt>.
39
+ def with_logging
40
+ log_request request.url, request.headers, request.body
41
+ response = yield
42
+ log_response response.code, response.body
43
+ SOAP::Response.new response
44
+ end
45
+
46
+ # Logs the SOAP request +url+, +headers+ and +body+.
47
+ def log_request(url, headers, body)
48
+ Savon.log "SOAP request: #{url}"
49
+ Savon.log headers.map { |key, value| "#{key}: #{value}" }.join(", ")
50
+ Savon.log body
51
+ end
52
+
53
+ # Logs the SOAP response +code+ and +body+.
54
+ def log_response(code, body)
55
+ Savon.log "SOAP response (status #{code}):"
56
+ Savon.log body
57
+ end
58
+
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,65 @@
1
+ require "savon/soap/xml"
2
+ require "savon/soap/fault"
3
+ require "savon/http/error"
4
+
5
+ module Savon
6
+ module SOAP
7
+
8
+ # = Savon::SOAP::Response
9
+ #
10
+ # Represents the SOAP response and contains the HTTP response.
11
+ class Response
12
+
13
+ # Expects an <tt>HTTPI::Response</tt> and handles errors.
14
+ def initialize(response)
15
+ self.http = response
16
+ raise_errors if Savon.raise_errors?
17
+ end
18
+
19
+ attr_accessor :http
20
+
21
+ # Returns whether the request was successful.
22
+ def success?
23
+ !soap_fault? && !http_error?
24
+ end
25
+
26
+ # Returns whether there was a SOAP fault.
27
+ def soap_fault?
28
+ soap_fault.present?
29
+ end
30
+
31
+ # Returns the <tt>Savon::SOAP::Fault</tt>.
32
+ def soap_fault
33
+ @soap_fault ||= Fault.new http
34
+ end
35
+
36
+ # Returns whether there was an HTTP error.
37
+ def http_error?
38
+ http_error.present?
39
+ end
40
+
41
+ # Returns the <tt>Savon::HTTP::Error</tt>.
42
+ def http_error
43
+ @http_error ||= HTTP::Error.new http
44
+ end
45
+
46
+ # Returns the SOAP response body as a Hash.
47
+ def to_hash
48
+ @hash ||= Savon::SOAP::XML.to_hash to_xml
49
+ end
50
+
51
+ # Returns the SOAP response XML.
52
+ def to_xml
53
+ http.body
54
+ end
55
+
56
+ private
57
+
58
+ def raise_errors
59
+ raise soap_fault if soap_fault?
60
+ raise http_error if http_error?
61
+ end
62
+
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,132 @@
1
+ require "builder"
2
+ require "crack/xml"
3
+ require "savon/soap"
4
+ require "savon/core_ext/hash"
5
+
6
+ module Savon
7
+ module SOAP
8
+
9
+ # = Savon::SOAP::XML
10
+ #
11
+ # Represents the SOAP request XML. Contains various global and per request/instance settings
12
+ # like the SOAP version, header, body and namespaces.
13
+ class XML
14
+
15
+ # XML Schema Type namespaces.
16
+ SchemaTypes = {
17
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
18
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
19
+ }
20
+
21
+ def self.to_hash(xml)
22
+ (Crack::XML.parse(xml) rescue {}).find_soap_body
23
+ end
24
+
25
+ # Accepts an +endpoint+, an +input+ tag and a SOAP +body+.
26
+ def initialize(endpoint = nil, input = nil, body = nil)
27
+ self.endpoint = endpoint if endpoint
28
+ self.input = input if input
29
+ self.body = body if body
30
+ end
31
+
32
+ # Accessor for the SOAP +input+ tag.
33
+ attr_accessor :input
34
+
35
+ # Accessor for the SOAP +endpoint+.
36
+ attr_accessor :endpoint
37
+
38
+ # Sets the SOAP +version+.
39
+ def version=(version)
40
+ raise ArgumentError, "Invalid SOAP version: #{version}" unless SOAP::Versions.include? version
41
+ @version = version
42
+ end
43
+
44
+ # Returns the SOAP +version+. Defaults to <tt>Savon.soap_version</tt>.
45
+ def version
46
+ @version ||= Savon.soap_version
47
+ end
48
+
49
+ # Sets the SOAP +header+ Hash.
50
+ attr_writer :header
51
+
52
+ # Returns the SOAP +header+. Defaults to an empty Hash.
53
+ def header
54
+ @header ||= {}
55
+ end
56
+
57
+ # Sets the +namespaces+ Hash.
58
+ attr_writer :namespaces
59
+
60
+ # Returns the +namespaces+. Defaults to a Hash containing the <tt>xmlns:env</tt> namespace.
61
+ def namespaces
62
+ @namespaces ||= { "xmlns:env" => SOAP::Namespace[version] }
63
+ end
64
+
65
+ # Sets the default namespace identifier.
66
+ attr_writer :namespace_identifier
67
+
68
+ # Returns the default namespace identifier.
69
+ def namespace_identifier
70
+ @namespace_identifier ||= :wsdl
71
+ end
72
+
73
+ # Accessor for the default namespace URI.
74
+ attr_accessor :namespace
75
+
76
+ # Accessor for the <tt>Savon::WSSE</tt> object.
77
+ attr_accessor :wsse
78
+
79
+ # Accessor for the SOAP +body+. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
80
+ # or any other Object responding to to_s.
81
+ attr_accessor :body
82
+
83
+ # Accepts a +block+ and yields a <tt>Builder::XmlMarkup</tt> object to let you create custom XML.
84
+ def xml
85
+ @xml = yield builder if block_given?
86
+ end
87
+
88
+ # Accepts an XML String and lets you specify a completely custom request body.
89
+ attr_writer :xml
90
+
91
+ # Returns the XML for a SOAP request.
92
+ def to_xml
93
+ @xml ||= builder.env :Envelope, complete_namespaces do |xml|
94
+ xml.env(:Header) { xml << header_for_xml } unless header_for_xml.empty?
95
+ xml.env(:Body) { xml.tag!(*input) { xml << body_to_xml } }
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ # Returns a new <tt>Builder::XmlMarkup</tt> object.
102
+ def builder
103
+ builder = Builder::XmlMarkup.new
104
+ builder.instruct!
105
+ builder
106
+ end
107
+
108
+ # Returns the complete Hash of namespaces.
109
+ def complete_namespaces
110
+ defaults = SchemaTypes.dup
111
+ defaults["xmlns:#{namespace_identifier}"] = namespace if namespace
112
+ defaults.merge namespaces
113
+ end
114
+
115
+ # Returns the SOAP header as an XML String.
116
+ def header_for_xml
117
+ header.to_soap_xml + wsse_header
118
+ end
119
+
120
+ # Returns the WSSE header or an empty String in case WSSE was not set.
121
+ def wsse_header
122
+ wsse.respond_to?(:to_xml) ? wsse.to_xml : ""
123
+ end
124
+
125
+ # Returns the SOAP body as an XML String.
126
+ def body_to_xml
127
+ body.respond_to?(:to_soap_xml) ? body.to_soap_xml : body.to_s
128
+ end
129
+
130
+ end
131
+ end
132
+ end
@@ -1,5 +1,5 @@
1
1
  module Savon
2
2
 
3
- Version = "0.7.9"
3
+ Version = "0.8.0.beta.1"
4
4
 
5
- end
5
+ end
@@ -0,0 +1,107 @@
1
+ require "rexml/document"
2
+
3
+ require "savon/wsdl/request"
4
+ require "savon/wsdl/parser"
5
+
6
+ module Savon
7
+ module WSDL
8
+
9
+ # = Savon::WSDL::Document
10
+ #
11
+ # Represents the WSDL of your service, including information like the namespace URI,
12
+ # the SOAP endpoint and available SOAP actions.
13
+ class Document
14
+
15
+ # Accepts an <tt>HTTPI::Request</tt> and a +document+.
16
+ def initialize(request = nil, document = nil)
17
+ self.request = request
18
+ self.document = document
19
+ end
20
+
21
+ # Accessor for the <tt>HTTPI::Request</tt> to use.
22
+ attr_accessor :request
23
+
24
+ def present?
25
+ !!@document
26
+ end
27
+
28
+ # Returns the namespace URI of the WSDL.
29
+ def namespace
30
+ @namespace ||= parser.namespace
31
+ end
32
+
33
+ # Sets the SOAP namespace.
34
+ attr_writer :namespace
35
+
36
+ # Returns the SOAP endpoint.
37
+ def endpoint
38
+ @endpoint ||= parser.endpoint
39
+ end
40
+
41
+ # Sets the SOAP endpoint.
42
+ attr_writer :endpoint
43
+
44
+ # Returns an Array of available SOAP actions.
45
+ def soap_actions
46
+ @soap_actions ||= parser.operations.keys
47
+ end
48
+
49
+ # Returns the SOAP action for a given +key+.
50
+ def soap_action(key)
51
+ operations[key][:action] if present? && operations[key]
52
+ end
53
+
54
+ # Returns the SOAP input for a given +key+.
55
+ def soap_input(key)
56
+ operations[key][:input].to_sym if present? && operations[key]
57
+ end
58
+
59
+ # Returns a Hash of SOAP operations.
60
+ def operations
61
+ @operations ||= parser.operations
62
+ end
63
+
64
+ # Sets the location of the WSDL document to use. This can either be a URL
65
+ # or a path to a local file.
66
+ attr_writer :document
67
+
68
+ # Returns the raw WSDL document.
69
+ def document
70
+ @wsdl_document ||= begin
71
+ raise ArgumentError, "No WSDL document given" if @document.blank?
72
+ remote? ? http_request : read_file
73
+ end
74
+ end
75
+
76
+ alias :to_xml :document
77
+
78
+ private
79
+
80
+ # Returns whether the WSDL document is located on the Web.
81
+ def remote?
82
+ @document =~ /^http/
83
+ end
84
+
85
+ # Executes an HTTP GET request to retrieve a remote WSDL document.
86
+ def http_request
87
+ request.url = @document
88
+ Request.new(request).response.body
89
+ end
90
+
91
+ # Reads the WSDL document from a local file.
92
+ def read_file
93
+ File.read @document
94
+ end
95
+
96
+ # Parses the WSDL document and returns the <tt>Savon::WSDL::Parser</tt>.
97
+ def parser
98
+ @parser ||= begin
99
+ parser = Parser.new
100
+ REXML::Document.parse_stream document, parser
101
+ parser
102
+ end
103
+ end
104
+
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,90 @@
1
+ require "savon/core_ext/object"
2
+ require "savon/core_ext/string"
3
+
4
+ module Savon
5
+ module WSDL
6
+
7
+ # = Savon::WSDL::Parser
8
+ #
9
+ # Serves as a stream listener for parsing WSDL documents.
10
+ class Parser
11
+
12
+ # The main sections of a WSDL document.
13
+ Sections = %w(definitions types message portType binding service)
14
+
15
+ def initialize
16
+ @path, @operations, @namespaces = [], {}, {}
17
+ end
18
+
19
+ # Returns the namespace URI.
20
+ attr_reader :namespace
21
+
22
+ # Returns the SOAP operations.
23
+ attr_reader :operations
24
+
25
+ # Returns the SOAP endpoint.
26
+ attr_reader :endpoint
27
+
28
+ # Hook method called when the stream parser encounters a starting tag.
29
+ def tag_start(tag, attrs)
30
+ # read xml namespaces if root element
31
+ read_namespaces(attrs) if @path.empty?
32
+
33
+ tag, namespace = tag.split(":").reverse
34
+ @path << tag
35
+
36
+ if @section == :binding && tag == "binding"
37
+ # ensure that we are in an wsdl/soap namespace
38
+ @section = nil unless @namespaces[namespace].starts_with? "http://schemas.xmlsoap.org/wsdl/soap"
39
+ end
40
+
41
+ @section = tag.to_sym if Sections.include?(tag) && depth <= 2
42
+
43
+ @namespace ||= attrs["targetNamespace"] if @section == :definitions
44
+ @endpoint ||= URI(URI.escape(attrs["location"])) if @section == :service && tag == "address"
45
+
46
+ operation_from tag, attrs if @section == :binding && tag == "operation"
47
+ end
48
+
49
+ # Returns our current depth in the WSDL document.
50
+ def depth
51
+ @path.size
52
+ end
53
+
54
+ # Reads namespace definitions from a given +attrs+ Hash.
55
+ def read_namespaces(attrs)
56
+ attrs.each do |key, value|
57
+ @namespaces[key.strip_namespace] = value if key.starts_with? "xmlns:"
58
+ end
59
+ end
60
+
61
+ # Hook method called when the stream parser encounters a closing tag.
62
+ def tag_end(tag)
63
+ @path.pop
64
+
65
+ if @section == :binding && @input && tag.strip_namespace == "operation"
66
+ # no soapAction attribute found till now
67
+ operation_from tag, "soapAction" => @input
68
+ end
69
+ end
70
+
71
+ # Stores available operations from a given tag +name+ and +attrs+.
72
+ def operation_from(tag, attrs)
73
+ @input = attrs["name"] if attrs["name"]
74
+
75
+ if attrs["soapAction"]
76
+ @action = !attrs["soapAction"].blank? ? attrs["soapAction"] : @input
77
+ @input = @action.split("/").last if !@input || @input.empty?
78
+
79
+ @operations[@input.snakecase.to_sym] = { :action => @action, :input => @input }
80
+ @input, @action = nil, nil
81
+ end
82
+ end
83
+
84
+ # Catches calls to unimplemented hook methods.
85
+ def method_missing(method, *args)
86
+ end
87
+
88
+ end
89
+ end
90
+ end