savon 0.7.9 → 0.8.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
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