tictoc-savon 0.7.9

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