s-savon 0.8.6

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 (61) hide show
  1. data/.gitignore +9 -0
  2. data/.rspec +1 -0
  3. data/.yardopts +2 -0
  4. data/CHANGELOG.md +461 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +37 -0
  8. data/Rakefile +40 -0
  9. data/lib/savon.rb +14 -0
  10. data/lib/savon/client.rb +157 -0
  11. data/lib/savon/core_ext/hash.rb +70 -0
  12. data/lib/savon/core_ext/object.rb +14 -0
  13. data/lib/savon/core_ext/string.rb +51 -0
  14. data/lib/savon/core_ext/time.rb +14 -0
  15. data/lib/savon/error.rb +6 -0
  16. data/lib/savon/global.rb +75 -0
  17. data/lib/savon/http/error.rb +42 -0
  18. data/lib/savon/soap.rb +24 -0
  19. data/lib/savon/soap/fault.rb +59 -0
  20. data/lib/savon/soap/request.rb +61 -0
  21. data/lib/savon/soap/response.rb +80 -0
  22. data/lib/savon/soap/xml.rb +187 -0
  23. data/lib/savon/version.rb +5 -0
  24. data/lib/savon/wsdl/document.rb +112 -0
  25. data/lib/savon/wsdl/parser.rb +102 -0
  26. data/lib/savon/wsdl/request.rb +35 -0
  27. data/lib/savon/wsse.rb +150 -0
  28. data/savon.gemspec +29 -0
  29. data/spec/fixtures/gzip/message.gz +0 -0
  30. data/spec/fixtures/response/another_soap_fault.xml +14 -0
  31. data/spec/fixtures/response/authentication.xml +14 -0
  32. data/spec/fixtures/response/header.xml +13 -0
  33. data/spec/fixtures/response/list.xml +18 -0
  34. data/spec/fixtures/response/multi_ref.xml +39 -0
  35. data/spec/fixtures/response/soap_fault.xml +8 -0
  36. data/spec/fixtures/response/soap_fault12.xml +18 -0
  37. data/spec/fixtures/wsdl/authentication.xml +63 -0
  38. data/spec/fixtures/wsdl/geotrust.xml +156 -0
  39. data/spec/fixtures/wsdl/namespaced_actions.xml +307 -0
  40. data/spec/fixtures/wsdl/no_namespace.xml +115 -0
  41. data/spec/fixtures/wsdl/two_bindings.xml +25 -0
  42. data/spec/savon/client_spec.rb +346 -0
  43. data/spec/savon/core_ext/hash_spec.rb +121 -0
  44. data/spec/savon/core_ext/object_spec.rb +19 -0
  45. data/spec/savon/core_ext/string_spec.rb +57 -0
  46. data/spec/savon/core_ext/time_spec.rb +13 -0
  47. data/spec/savon/http/error_spec.rb +52 -0
  48. data/spec/savon/savon_spec.rb +85 -0
  49. data/spec/savon/soap/fault_spec.rb +89 -0
  50. data/spec/savon/soap/request_spec.rb +45 -0
  51. data/spec/savon/soap/response_spec.rb +174 -0
  52. data/spec/savon/soap/xml_spec.rb +335 -0
  53. data/spec/savon/soap_spec.rb +21 -0
  54. data/spec/savon/wsdl/document_spec.rb +132 -0
  55. data/spec/savon/wsdl/parser_spec.rb +99 -0
  56. data/spec/savon/wsdl/request_spec.rb +15 -0
  57. data/spec/savon/wsse_spec.rb +213 -0
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/endpoint.rb +25 -0
  60. data/spec/support/fixture.rb +37 -0
  61. metadata +251 -0
@@ -0,0 +1,42 @@
1
+ require "savon/error"
2
+ require "savon/soap/xml"
3
+
4
+ module Savon
5
+ module HTTP
6
+
7
+ # = Savon::HTTP::Error
8
+ #
9
+ # Represents an HTTP error. Contains the original <tt>HTTPI::Response</tt>.
10
+ class Error < 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 an HTTP error is present.
21
+ def present?
22
+ http.error?
23
+ end
24
+
25
+ # Returns the HTTP error message.
26
+ def to_s
27
+ return "" unless present?
28
+
29
+ @message ||= begin
30
+ message = "HTTP error (#{http.code})"
31
+ message << ": #{http.body}" unless http.body.empty?
32
+ end
33
+ end
34
+
35
+ # Returns the HTTP response as a Hash.
36
+ def to_hash
37
+ @hash = { :code => http.code, :headers => http.headers, :body => http.body }
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,24 @@
1
+ module Savon
2
+
3
+ # = Savon::SOAP
4
+ #
5
+ # Contains various SOAP details.
6
+ module SOAP
7
+
8
+ # Default SOAP version.
9
+ DefaultVersion = 1
10
+
11
+ # Supported SOAP versions.
12
+ Versions = 1..2
13
+
14
+ # SOAP namespaces by SOAP version.
15
+ Namespace = {
16
+ 1 => "http://schemas.xmlsoap.org/soap/envelope/",
17
+ 2 => "http://www.w3.org/2003/05/soap-envelope"
18
+ }
19
+
20
+ # SOAP xs:dateTime Regexp.
21
+ DateTimeRegexp = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
22
+
23
+ end
24
+ end
@@ -0,0 +1,59 @@
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.include?("Fault>") && (soap1_fault? || soap2_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
+ # Returns whether the response contains a SOAP 1.1 fault.
39
+ def soap1_fault?
40
+ http.body.include?("faultcode>") && http.body.include?("faultstring>")
41
+ end
42
+
43
+ # Returns whether the response contains a SOAP 1.2 fault.
44
+ def soap2_fault?
45
+ http.body.include?("Code>") && http.body.include?("Reason>")
46
+ end
47
+
48
+ # Returns the SOAP fault message by version.
49
+ def message_by_version(fault)
50
+ if fault[:faultcode]
51
+ "(#{fault[:faultcode]}) #{fault[:faultstring]}"
52
+ elsif fault[:code]
53
+ "(#{fault[:code][:value]}) #{fault[:reason][:text]}"
54
+ end
55
+ end
56
+
57
+ end
58
+ end
59
+ 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,80 @@
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 header as a Hash.
47
+ def header
48
+ @header_hash ||= basic_hash.find_soap_header
49
+ end
50
+
51
+ # Returns the SOAP response body as a Hash.
52
+ def to_hash
53
+ @hash ||= Savon::SOAP::XML.to_hash basic_hash
54
+ end
55
+
56
+ # Returns the SOAP response body as an Array.
57
+ def to_array(*path)
58
+ Savon::SOAP::XML.to_array to_hash, *path
59
+ end
60
+
61
+ # Returns the complete SOAP response XML without normalization.
62
+ def basic_hash
63
+ @basic_hash ||= Savon::SOAP::XML.parse http.body
64
+ end
65
+
66
+ # Returns the SOAP response XML.
67
+ def to_xml
68
+ http.body
69
+ end
70
+
71
+ private
72
+
73
+ def raise_errors
74
+ raise soap_fault if soap_fault?
75
+ raise http_error if http_error?
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,187 @@
1
+ require "builder"
2
+ require "crack/xml"
3
+ require "gyoku"
4
+
5
+ require "savon/soap"
6
+ require "savon/core_ext/hash"
7
+
8
+ module Savon
9
+ module SOAP
10
+
11
+ # = Savon::SOAP::XML
12
+ #
13
+ # Represents the SOAP request XML. Contains various global and per request/instance settings
14
+ # like the SOAP version, header, body and namespaces.
15
+ class XML
16
+
17
+ # XML Schema Type namespaces.
18
+ SchemaTypes = {
19
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
20
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
21
+ }
22
+
23
+ # Converts the given SOAP response +value+ (XML or Hash) into a normalized Hash.
24
+ def self.to_hash(value)
25
+ value = parse value unless value.kind_of? Hash
26
+ value.find_soap_body
27
+ end
28
+
29
+ # Converts a given SOAP response +xml+ to a Hash.
30
+ def self.parse(xml)
31
+ Crack::XML.parse(xml) rescue {}
32
+ end
33
+
34
+ # Expects a SOAP response XML or Hash, traverses it for a given +path+ of Hash keys
35
+ # and returns the value as an Array. Defaults to return an empty Array in case the
36
+ # path does not exist or returns nil.
37
+ def self.to_array(object, *path)
38
+ hash = object.kind_of?(Hash) ? object : to_hash(object)
39
+
40
+ result = path.inject hash do |memo, key|
41
+ return [] unless memo[key]
42
+ memo[key]
43
+ end
44
+
45
+ result.kind_of?(Array) ? result.compact : [result].compact
46
+ end
47
+
48
+ # Accepts an +endpoint+, an +input+ tag and a SOAP +body+.
49
+ def initialize(endpoint = nil, input = nil, body = nil)
50
+ self.endpoint = endpoint if endpoint
51
+ self.input = input if input
52
+ self.body = body if body
53
+ end
54
+
55
+ # Accessor for the SOAP +input+ tag.
56
+ attr_accessor :input
57
+
58
+ # Accessor for the SOAP +endpoint+.
59
+ attr_accessor :endpoint
60
+
61
+ # Sets the SOAP +version+.
62
+ def version=(version)
63
+ raise ArgumentError, "Invalid SOAP version: #{version}" unless SOAP::Versions.include? version
64
+ @version = version
65
+ end
66
+
67
+ # Returns the SOAP +version+. Defaults to <tt>Savon.soap_version</tt>.
68
+ def version
69
+ @version ||= Savon.soap_version
70
+ end
71
+
72
+ # Sets the SOAP +header+ Hash.
73
+ attr_writer :header
74
+
75
+ # Returns the SOAP +header+. Defaults to an empty Hash.
76
+ def header
77
+ @header ||= {}
78
+ end
79
+
80
+ # Sets the SOAP envelope namespace.
81
+ attr_writer :env_namespace
82
+
83
+ # Returns the SOAP envelope namespace. Defaults to :env.
84
+ def env_namespace
85
+ @env_namespace ||= :soap
86
+ end
87
+
88
+ # Sets the +namespaces+ Hash.
89
+ attr_writer :namespaces
90
+
91
+ # Returns the +namespaces+. Defaults to a Hash containing the SOAP envelope namespace.
92
+ def namespaces
93
+ @namespaces ||= begin
94
+ key = env_namespace.blank? ? "xmlns" : "xmlns:#{env_namespace}"
95
+ { key => SOAP::Namespace[version] }
96
+ end
97
+ end
98
+
99
+ # Sets the default namespace identifier.
100
+ attr_writer :namespace_identifier
101
+
102
+ # Returns the default namespace identifier.
103
+ def namespace_identifier
104
+ @namespace_identifier ||= :wsdl
105
+ end
106
+
107
+ # Returns whether all local elements should be namespaced. Might be set to :qualified,
108
+ # but defaults to :unqualified.
109
+ def element_form_default
110
+ @element_form_default ||= :unqualified
111
+ end
112
+
113
+ # Sets whether all local elements should be namespaced.
114
+ attr_writer :element_form_default
115
+
116
+ # Accessor for the default namespace URI.
117
+ attr_accessor :namespace
118
+
119
+ # Accessor for the <tt>Savon::WSSE</tt> object.
120
+ attr_accessor :wsse
121
+
122
+ # Accessor for the SOAP +body+. Expected to be a Hash that can be translated to XML via Gyoku.xml
123
+ # or any other Object responding to to_s.
124
+ attr_accessor :body
125
+
126
+ # Accepts a +block+ and yields a <tt>Builder::XmlMarkup</tt> object to let you create custom XML.
127
+ def xml
128
+ @xml = yield builder if block_given?
129
+ end
130
+
131
+ # Accepts an XML String and lets you specify a completely custom request body.
132
+ attr_writer :xml
133
+
134
+ # Returns the XML for a SOAP request.
135
+ def to_xml
136
+ @xml ||= tag(builder, :Envelope, complete_namespaces) do |xml|
137
+ tag(xml, :Header) { xml << header_for_xml } unless header_for_xml.empty?
138
+ input.nil? ? tag(xml, :Body) : tag(xml, :Body) { xml.tag!(*input) { xml << body_to_xml } }
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ # Returns a new <tt>Builder::XmlMarkup</tt> object.
145
+ def builder
146
+ builder = Builder::XmlMarkup.new
147
+ builder.instruct!
148
+ builder
149
+ end
150
+
151
+ # Expects a builder +xml+ instance, a tag +name+ and accepts optional +namespaces+
152
+ # and a block to create an XML tag.
153
+ def tag(xml, name, namespaces = {}, &block)
154
+ return xml.tag! name, namespaces, &block if env_namespace.blank?
155
+ xml.tag! env_namespace, name, namespaces, &block
156
+ end
157
+
158
+ # Returns the complete Hash of namespaces.
159
+ def complete_namespaces
160
+ defaults = SchemaTypes.dup
161
+ defaults["xmlns:#{namespace_identifier}"] = namespace if namespace
162
+ defaults.merge namespaces
163
+ end
164
+
165
+ # Returns the SOAP header as an XML String.
166
+ def header_for_xml
167
+ @header_for_xml ||= Gyoku.xml(header) + wsse_header
168
+ end
169
+
170
+ # Returns the WSSE header or an empty String in case WSSE was not set.
171
+ def wsse_header
172
+ wsse.respond_to?(:to_xml) ? wsse.to_xml : ""
173
+ end
174
+
175
+ # Returns the SOAP body as an XML String.
176
+ def body_to_xml
177
+ return body.to_s unless body.kind_of? Hash
178
+ unless namespace_identifier == :wsdl
179
+ Gyoku.xml body, :element_form_default => element_form_default
180
+ else
181
+ Gyoku.xml body, :element_form_default => element_form_default, :namespace => namespace_identifier
182
+ end
183
+ end
184
+
185
+ end
186
+ end
187
+ end