tictoc-savon 0.7.9

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 (59) hide show
  1. data/.autotest +5 -0
  2. data/CHANGELOG +176 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +64 -0
  5. data/Rakefile +50 -0
  6. data/lib/savon.rb +35 -0
  7. data/lib/savon/client.rb +131 -0
  8. data/lib/savon/core_ext.rb +8 -0
  9. data/lib/savon/core_ext/array.rb +31 -0
  10. data/lib/savon/core_ext/datetime.rb +10 -0
  11. data/lib/savon/core_ext/hash.rb +107 -0
  12. data/lib/savon/core_ext/net_http.rb +19 -0
  13. data/lib/savon/core_ext/object.rb +16 -0
  14. data/lib/savon/core_ext/string.rb +69 -0
  15. data/lib/savon/core_ext/symbol.rb +8 -0
  16. data/lib/savon/core_ext/uri.rb +10 -0
  17. data/lib/savon/logger.rb +56 -0
  18. data/lib/savon/request.rb +138 -0
  19. data/lib/savon/response.rb +174 -0
  20. data/lib/savon/soap.rb +302 -0
  21. data/lib/savon/version.rb +5 -0
  22. data/lib/savon/wsdl.rb +137 -0
  23. data/lib/savon/wsdl_stream.rb +85 -0
  24. data/lib/savon/wsse.rb +163 -0
  25. data/spec/basic_spec_helper.rb +11 -0
  26. data/spec/endpoint_helper.rb +23 -0
  27. data/spec/fixtures/gzip/gzip_response_fixture.rb +7 -0
  28. data/spec/fixtures/gzip/message.gz +0 -0
  29. data/spec/fixtures/response/response_fixture.rb +36 -0
  30. data/spec/fixtures/response/xml/authentication.xml +14 -0
  31. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  32. data/spec/fixtures/response/xml/soap_fault.xml +8 -0
  33. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  34. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  35. data/spec/fixtures/wsdl/wsdl_fixture.yml +42 -0
  36. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  37. data/spec/fixtures/wsdl/xml/geotrust.xml +156 -0
  38. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  39. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  40. data/spec/http_stubs.rb +26 -0
  41. data/spec/integration/http_basic_auth_spec.rb +16 -0
  42. data/spec/integration/server.rb +51 -0
  43. data/spec/savon/client_spec.rb +86 -0
  44. data/spec/savon/core_ext/array_spec.rb +49 -0
  45. data/spec/savon/core_ext/datetime_spec.rb +21 -0
  46. data/spec/savon/core_ext/hash_spec.rb +190 -0
  47. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  48. data/spec/savon/core_ext/object_spec.rb +34 -0
  49. data/spec/savon/core_ext/string_spec.rb +99 -0
  50. data/spec/savon/core_ext/symbol_spec.rb +12 -0
  51. data/spec/savon/core_ext/uri_spec.rb +19 -0
  52. data/spec/savon/request_spec.rb +117 -0
  53. data/spec/savon/response_spec.rb +179 -0
  54. data/spec/savon/soap_spec.rb +202 -0
  55. data/spec/savon/wsdl_spec.rb +107 -0
  56. data/spec/savon/wsse_spec.rb +132 -0
  57. data/spec/spec.opts +4 -0
  58. data/spec/spec_helper.rb +5 -0
  59. metadata +229 -0
@@ -0,0 +1,174 @@
1
+ module Savon
2
+
3
+ # = Savon::Response
4
+ #
5
+ # Savon::Response represents both HTTP and SOAP response.
6
+ #
7
+ # == SOAP fault
8
+ #
9
+ # Assuming the default behavior of raising errors is disabled, you can ask the response object
10
+ # if there was a SOAP fault or an HTTP error and get the SOAP fault or HTTP error message.
11
+ #
12
+ # response.soap_fault?
13
+ # # => true
14
+ #
15
+ # response.soap_fault
16
+ # # => "(soap:Server) Fault occurred while processing."
17
+ #
18
+ # response.http_error?
19
+ # # => true
20
+ #
21
+ # response.http_error
22
+ # # => "Not found (404)"
23
+ #
24
+ # == Response as XML
25
+ #
26
+ # To get the raw SOAP response XML, you can call to_xml or to_s on the response object.
27
+ #
28
+ # response.to_xml
29
+ # => "<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
30
+ # => "..."
31
+ # => "</soap:Envelope>"
32
+ #
33
+ # == Response as a Hash
34
+ #
35
+ # You can also let Savon translate the SOAP response body to a Hash.
36
+ #
37
+ # response.to_hash
38
+ # => { :findUserByIdResponse => {
39
+ # => :id => "123",
40
+ # => :username => "eve"
41
+ # => :active => true
42
+ # => }
43
+ #
44
+ # When translating the SOAP response to a Hash, some XML tags and values are converted to more
45
+ # convenient Ruby objects. Translation is done through John Nunemaker's {Crack}[http://github.com/jnunemaker/crack]
46
+ # library along with some custom mapping.
47
+ #
48
+ # * XML tags (Hash keys) are converted to snake_case Symbols and namespaces are stripped off
49
+ # * SOAP xs:nil values are converted to nil objects
50
+ # * XML values specified in xs:DateTime format are converted to DateTime objects
51
+ # * XML values of "true" and "false" are converted to TrueClass and FalseClass
52
+ #
53
+ # == Net::HTTP response
54
+ #
55
+ # If for some reason you need to access the Net::HTTP response object ... you can.
56
+ #
57
+ # bc. response.http
58
+ # => #<Net::HTTPOK:0x7f749a1aa4a8>
59
+ class Response
60
+
61
+ # The maximum HTTP response code considered to be OK.
62
+ MaxNonErrorResponseCode = 299
63
+
64
+ # The global setting of whether to raise errors.
65
+ @@raise_errors = true
66
+
67
+ # Sets the global setting of whether to raise errors.
68
+ def self.raise_errors=(raise_errors)
69
+ @@raise_errors = raise_errors
70
+ end
71
+
72
+ # Returns the global setting of whether to raise errors.
73
+ def self.raise_errors?
74
+ @@raise_errors
75
+ end
76
+
77
+ # Expects a Net::HTTPResponse and handles errors.
78
+ def initialize(http)
79
+ @http = http
80
+
81
+ handle_soap_fault
82
+ handle_http_error
83
+ end
84
+
85
+ # Returns whether there was a SOAP fault.
86
+ def soap_fault?
87
+ !@soap_fault.blank?
88
+ end
89
+
90
+ # Returns the SOAP fault message.
91
+ attr_reader :soap_fault
92
+
93
+ # Returns whether there was an HTTP error.
94
+ def http_error?
95
+ !@http_error.blank?
96
+ end
97
+
98
+ # Returns the HTTP error message.
99
+ attr_reader :http_error
100
+
101
+ # Returns the SOAP response body as a Hash.
102
+ def to_hash
103
+ @hash ||= (Crack::XML.parse(body) rescue {}).find_soap_body
104
+ end
105
+
106
+ # Returns the SOAP response XML.
107
+ def to_xml
108
+ body
109
+ end
110
+
111
+ # Returns the HTTP response object.
112
+ attr_reader :http
113
+
114
+ alias :to_s :to_xml
115
+
116
+ private
117
+
118
+ # Returns the response body.
119
+ def body
120
+ @body || gzipped_body? ? decoded_body : @http.body
121
+ end
122
+
123
+ # Returns whether the body is gzipped.
124
+ def gzipped_body?
125
+ @http["content-encoding"] == "gzip" || @http.body[0..1] == "\x1f\x8b"
126
+ end
127
+
128
+ # Returns the gzip decoded body.
129
+ def decoded_body
130
+ gz = Zlib::GzipReader.new StringIO.new(@http.body)
131
+ gz.read
132
+ ensure
133
+ gz.close
134
+ end
135
+
136
+ # Handles SOAP faults. Raises a Savon::SOAPFault unless the default behavior of raising errors
137
+ # was turned off.
138
+ def handle_soap_fault
139
+ if soap_fault_message
140
+ @soap_fault = soap_fault_message
141
+ raise Savon::SOAPFault, @soap_fault if self.class.raise_errors?
142
+ end
143
+ end
144
+
145
+ # Returns a SOAP fault message in case a SOAP fault was found.
146
+ def soap_fault_message
147
+ @soap_fault_message ||= soap_fault_message_by_version to_hash[:fault]
148
+ end
149
+
150
+ # Expects a Hash that might contain information about a SOAP fault. Returns the SOAP fault
151
+ # message in case one was found.
152
+ def soap_fault_message_by_version(soap_fault)
153
+ return unless soap_fault
154
+
155
+ if soap_fault.keys.include? :faultcode
156
+ "(#{soap_fault[:faultcode]}) #{soap_fault[:faultstring]}"
157
+ elsif soap_fault.keys.include? :code
158
+ "(#{soap_fault[:code][:value]}) #{soap_fault[:reason][:text]}"
159
+ end
160
+ end
161
+
162
+ # Handles HTTP errors. Raises a Savon::HTTPError unless the default behavior of raising errors
163
+ # was turned off.
164
+ def handle_http_error
165
+ if @http.code.to_i > MaxNonErrorResponseCode
166
+ @http_error = "#{@http.message} (#{@http.code})"
167
+ @http_error << ": #{body}" unless body.empty?
168
+ raise Savon::HTTPError, http_error if self.class.raise_errors?
169
+ end
170
+ end
171
+
172
+ end
173
+ end
174
+
data/lib/savon/soap.rb ADDED
@@ -0,0 +1,302 @@
1
+ module Savon
2
+
3
+ # = Savon::SOAP
4
+ #
5
+ # Savon::SOAP represents the SOAP request. Pass a block to your SOAP call and the SOAP object is
6
+ # passed to it as the first argument. The object allows setting the SOAP version, header, body
7
+ # and namespaces per request.
8
+ #
9
+ # == Body
10
+ #
11
+ # The body method lets you specify parameters to be received by the SOAP action.
12
+ #
13
+ # You can either pass in a hash (which will be translated to XML via Hash.to_soap_xml):
14
+ #
15
+ # response = client.get_user_by_id do |soap|
16
+ # soap.body = { :id => 123 }
17
+ # end
18
+ #
19
+ # Or a string containing the raw XML:
20
+ #
21
+ # response = client.get_user_by_id do |soap|
22
+ # soap.body = "<id>123</id>"
23
+ # end
24
+ #
25
+ # Request output:
26
+ #
27
+ # <env:Envelope
28
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
29
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
30
+ # <env:Body>
31
+ # <wsdl:getUserById><id>123</id></wsdl:getUserById>
32
+ # </env:Body>
33
+ # </env:Envelope>
34
+ #
35
+ # Please look at the documentation of Hash.to_soap_xml for some more information.
36
+ #
37
+ # == Version
38
+ #
39
+ # Savon defaults to SOAP 1.1. In case your service uses SOAP 1.2, you can use the version method
40
+ # to change the default per request.
41
+ #
42
+ # response = client.get_all_users do |soap|
43
+ # soap.version = 2
44
+ # end
45
+ #
46
+ # You can also change the default to SOAP 1.2 for all request:
47
+ #
48
+ # Savon::SOAP.version = 2
49
+ #
50
+ # == Header
51
+ #
52
+ # If you need to add custom XML into the SOAP header, you can use the header method.
53
+ #
54
+ # The value is expected to be a hash (which will be translated to XML via Hash.to_soap_xml):
55
+ #
56
+ # response = client.get_all_users do |soap|
57
+ # soap.header["specialApiKey"] = "secret"
58
+ # end
59
+ #
60
+ # Or a string containing the raw XML:
61
+ #
62
+ # response = client.get_all_users do |soap|
63
+ # soap.header = "<specialApiKey>secret</specialApiKey>"
64
+ # end
65
+ #
66
+ # Request output:
67
+ #
68
+ # <env:Envelope
69
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
70
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
71
+ # <env:Header>
72
+ # <specialApiKey>secret</specialApiKey>
73
+ # </env:Header>
74
+ # <env:Body>
75
+ # <wsdl:getAllUsers></wsdl:getAllUsers>
76
+ # </env:Body>
77
+ # </env:Envelope>
78
+ #
79
+ # == Namespaces
80
+ #
81
+ # The namespaces method contains a hash of attributes for the SOAP envelope. You can overwrite it
82
+ # or add additional attributes.
83
+ #
84
+ # response = client.get_all_users do |soap|
85
+ # soap.namespaces["xmlns:domains"] = "http://domains.example.com"
86
+ # end
87
+ #
88
+ # Request output:
89
+ #
90
+ # <env:Envelope
91
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
92
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
93
+ # xmlns:domains="http://domains.example.com">
94
+ # <env:Body>
95
+ # <wsdl:getAllUsers></wsdl:getAllUsers>
96
+ # </env:Body>
97
+ # </env:Envelope>
98
+ #
99
+ # == Input
100
+ #
101
+ # You can change the name of the SOAP input tag in case you need to.
102
+ #
103
+ # response = client.get_all_users do |soap|
104
+ # soap.input = "GetAllUsersRequest"
105
+ # end
106
+ #
107
+ # Request output:
108
+ #
109
+ # <env:Envelope
110
+ # xmlns:wsdl="http://example.com/user/1.0/UserService"
111
+ # xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
112
+ # <env:Body>
113
+ # <wsdl:GetAllUsersRequest></wsdl:GetAllUsersRequest>
114
+ # </env:Body>
115
+ # </env:Envelope>
116
+ class SOAP
117
+
118
+ # Supported SOAP versions.
119
+ Versions = [1, 2]
120
+
121
+ # SOAP namespaces by SOAP version.
122
+ Namespace = {
123
+ 1 => "http://schemas.xmlsoap.org/soap/envelope/",
124
+ 2 => "http://www.w3.org/2003/05/soap-envelope"
125
+ }
126
+
127
+ # Content-Types by SOAP version.
128
+ ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
129
+
130
+ # SOAP xs:dateTime format.
131
+ DateTimeFormat = "%Y-%m-%dT%H:%M:%S%Z"
132
+
133
+ # SOAP xs:dateTime Regexp.
134
+ DateTimeRegexp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
135
+
136
+ # The global SOAP version.
137
+ @@version = 1
138
+
139
+ # Returns the global SOAP version.
140
+ def self.version
141
+ @@version
142
+ end
143
+
144
+ # Sets the global SOAP version.
145
+ def self.version=(version)
146
+ @@version = version if Versions.include? version
147
+ end
148
+
149
+ # Sets the global SOAP header. Expected to be a Hash that can be translated to XML via
150
+ # Hash.to_soap_xml or any other Object responding to to_s.
151
+ def self.header=(header)
152
+ @@header = header
153
+ end
154
+
155
+ # Returns the global SOAP header. Defaults to an empty Hash.
156
+ def self.header
157
+ @@header ||= {}
158
+ end
159
+
160
+ # Sets the global namespaces. Expected to be a Hash containing the namespaces (keys) and the
161
+ # corresponding URI's (values).
162
+ def self.namespaces=(namespaces)
163
+ @@namespaces = namespaces if namespaces.kind_of? Hash
164
+ end
165
+
166
+ # Returns the global namespaces. A Hash containing the namespaces (keys) and the corresponding
167
+ # URI's (values).
168
+ def self.namespaces
169
+ @@namespaces ||= {}
170
+ end
171
+
172
+ # Initialzes the SOAP object. Expects a SOAP +operation+ Hash along with an +endpoint+.
173
+ def initialize(action, input, endpoint)
174
+ @action, @input = action, input
175
+ @endpoint = endpoint.kind_of?(URI) ? endpoint : URI(endpoint)
176
+ @builder = Builder::XmlMarkup.new
177
+ end
178
+
179
+ # Sets the WSSE options.
180
+ attr_writer :wsse
181
+
182
+ # Sets the SOAP action.
183
+ attr_writer :action
184
+
185
+ # Returns the SOAP action.
186
+ def action
187
+ @action ||= ""
188
+ end
189
+
190
+ # Sets the SOAP input.
191
+ attr_writer :input
192
+
193
+ # Returns the SOAP input.
194
+ def input
195
+ @input ||= ""
196
+ end
197
+
198
+ # Accessor for the SOAP endpoint.
199
+ attr_accessor :endpoint
200
+
201
+ # Sets the SOAP header. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
202
+ # or any other Object responding to to_s.
203
+ attr_writer :header
204
+
205
+ # Returns the SOAP header. Defaults to an empty Hash.
206
+ def header
207
+ @header ||= {}
208
+ end
209
+
210
+ # Accessor for the SOAP body. Expected to be a Hash that can be translated to XML via Hash.to_soap_xml
211
+ # or any other Object responding to to_s.
212
+ attr_accessor :body
213
+
214
+ # Accessor for overwriting the default SOAP request. Let's you specify completely custom XML.
215
+ attr_accessor :xml
216
+
217
+ # Sets the namespaces. Expected to be a Hash containing the namespaces (keys) and the
218
+ # corresponding URI's (values).
219
+ attr_writer :namespaces
220
+
221
+ # Returns the namespaces. A Hash containing the namespaces (keys) and the corresponding URI's
222
+ # (values). Defaults to a Hash containing an +xmlns:env+ key and the namespace for the current
223
+ # SOAP version.
224
+ def namespaces
225
+ @namespaces ||= { "xmlns:env" => Namespace[version] }
226
+ end
227
+
228
+ # Convenience method for setting the +xmlns:wsdl+ namespace.
229
+ def namespace=(namespace)
230
+ namespaces["xmlns:wsdl"] = namespace
231
+ end
232
+
233
+ # Sets the SOAP version.
234
+ def version=(version)
235
+ @version = version if Versions.include? version
236
+ end
237
+
238
+ # Returns the SOAP version. Defaults to the global default.
239
+ def version
240
+ @version ||= self.class.version
241
+ end
242
+
243
+ # Returns the SOAP envelope XML.
244
+ def to_xml
245
+ unless @xml
246
+ @builder.instruct!
247
+ @xml = @builder.env :Envelope, merged_namespaces do |xml|
248
+ xml.env(:Header) { xml << merged_header } unless merged_header.empty?
249
+ xml_body xml
250
+ end
251
+ end
252
+ @xml
253
+ end
254
+
255
+ private
256
+
257
+ # Returns a String containing the global and per request header.
258
+ def merged_header
259
+ if self.class.header.kind_of?(Hash) && header.kind_of?(Hash)
260
+ merged_header = self.class.header.merge(header).to_soap_xml
261
+ else
262
+ global_header = self.class.header.to_soap_xml rescue self.class.header.to_s
263
+ request_header = header.to_soap_xml rescue header.to_s
264
+ merged_header = global_header + request_header
265
+ end
266
+ merged_header + wsse_header
267
+ end
268
+
269
+ # Returns the WSSE header or an empty String in case WSSE was not set.
270
+ def wsse_header
271
+ @wsse.respond_to?(:header) ? @wsse.header : ""
272
+ end
273
+
274
+ # Adds a SOAP XML body to a given +xml+ Object.
275
+ def xml_body(xml)
276
+ xml.env(:Body) do
277
+ xml.tag!(:wsdl, *input_array) { xml << (@body.to_soap_xml rescue @body.to_s) }
278
+ end
279
+ end
280
+
281
+ # Returns a Hash containing the global and per request namespaces.
282
+ def merged_namespaces
283
+ self.class.namespaces.merge namespaces
284
+ end
285
+
286
+ # Returns an Array of SOAP input names to append to the wsdl namespace. Defaults to use the
287
+ # name of the SOAP action. May return an empty Array in case the specified SOAP input seems
288
+ # to be invalid.
289
+ def input_array
290
+ if input.kind_of?(Array) && !input.blank?
291
+ [input[0].to_sym, input[1]]
292
+ elsif !input.blank?
293
+ [input.to_sym]
294
+ elsif !action.blank?
295
+ [action.to_sym]
296
+ else
297
+ []
298
+ end
299
+ end
300
+
301
+ end
302
+ end