savon 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/CHANGELOG.md +119 -104
  2. data/README.md +12 -11
  3. data/Rakefile +0 -6
  4. data/lib/savon.rb +16 -14
  5. data/lib/savon/block_interface.rb +26 -0
  6. data/lib/savon/builder.rb +142 -0
  7. data/lib/savon/client.rb +36 -135
  8. data/lib/savon/header.rb +42 -0
  9. data/lib/savon/http_error.rb +27 -0
  10. data/lib/savon/log_message.rb +23 -25
  11. data/lib/savon/message.rb +35 -0
  12. data/lib/savon/mock.rb +5 -0
  13. data/lib/savon/mock/expectation.rb +70 -0
  14. data/lib/savon/mock/spec_helper.rb +62 -0
  15. data/lib/savon/model.rb +39 -61
  16. data/lib/savon/operation.rb +62 -0
  17. data/lib/savon/options.rb +265 -0
  18. data/lib/savon/qualified_message.rb +49 -0
  19. data/lib/savon/request.rb +92 -0
  20. data/lib/savon/response.rb +97 -0
  21. data/lib/savon/soap_fault.rb +40 -0
  22. data/lib/savon/version.rb +1 -1
  23. data/savon.gemspec +10 -8
  24. data/spec/integration/options_spec.rb +536 -0
  25. data/spec/integration/request_spec.rb +31 -16
  26. data/spec/integration/support/application.rb +80 -0
  27. data/spec/integration/support/server.rb +84 -0
  28. data/spec/savon/builder_spec.rb +81 -0
  29. data/spec/savon/client_spec.rb +90 -488
  30. data/spec/savon/http_error_spec.rb +49 -0
  31. data/spec/savon/log_message_spec.rb +33 -0
  32. data/spec/savon/mock_spec.rb +127 -0
  33. data/spec/savon/model_spec.rb +110 -99
  34. data/spec/savon/observers_spec.rb +92 -0
  35. data/spec/savon/operation_spec.rb +49 -0
  36. data/spec/savon/request_spec.rb +145 -0
  37. data/spec/savon/{soap/response_spec.rb → response_spec.rb} +22 -59
  38. data/spec/savon/soap_fault_spec.rb +94 -0
  39. data/spec/spec_helper.rb +5 -3
  40. data/spec/support/fixture.rb +5 -1
  41. metadata +202 -197
  42. data/lib/savon/config.rb +0 -46
  43. data/lib/savon/error.rb +0 -6
  44. data/lib/savon/hooks/group.rb +0 -68
  45. data/lib/savon/hooks/hook.rb +0 -61
  46. data/lib/savon/http/error.rb +0 -42
  47. data/lib/savon/logger.rb +0 -39
  48. data/lib/savon/null_logger.rb +0 -10
  49. data/lib/savon/soap.rb +0 -21
  50. data/lib/savon/soap/fault.rb +0 -59
  51. data/lib/savon/soap/invalid_response_error.rb +0 -13
  52. data/lib/savon/soap/request.rb +0 -86
  53. data/lib/savon/soap/request_builder.rb +0 -205
  54. data/lib/savon/soap/response.rb +0 -117
  55. data/lib/savon/soap/xml.rb +0 -257
  56. data/spec/savon/config_spec.rb +0 -38
  57. data/spec/savon/hooks/group_spec.rb +0 -71
  58. data/spec/savon/hooks/hook_spec.rb +0 -16
  59. data/spec/savon/http/error_spec.rb +0 -52
  60. data/spec/savon/logger_spec.rb +0 -51
  61. data/spec/savon/savon_spec.rb +0 -33
  62. data/spec/savon/soap/fault_spec.rb +0 -89
  63. data/spec/savon/soap/request_builder_spec.rb +0 -207
  64. data/spec/savon/soap/request_spec.rb +0 -112
  65. data/spec/savon/soap/xml_spec.rb +0 -357
  66. data/spec/savon/soap_spec.rb +0 -16
@@ -1,205 +0,0 @@
1
- module Savon
2
- module SOAP
3
-
4
- # = Savon::SOAP::RequestBuilder
5
- #
6
- # Savon::SOAP::RequestBuilder builds Savon::SOAP::Request instances.
7
- # The RequestBuilder is configured by the client that instantiates it.
8
- # It uses the options set by the client to build an appropriate request.
9
- class RequestBuilder
10
-
11
- # Initialize a new +RequestBuilder+ with the given SOAP operation.
12
- # The operation may be specified using a symbol or a string.
13
- def initialize(operation, options = {})
14
- @operation = operation
15
- assign_options(options)
16
- end
17
-
18
- # Writer for the <tt>HTTPI::Request</tt> object.
19
- attr_writer :http
20
-
21
- # Writer for the <tt>Savon::SOAP::XML</tt> object.
22
- attr_writer :soap
23
-
24
- # Writer for the <tt>Akami::WSSE</tt> object.
25
- attr_writer :wsse
26
-
27
- # Writer for the <tt>Wasabi::Document</tt> object.
28
- attr_writer :wsdl
29
-
30
- # Writer for the <tt>Savon::Config</tt> object.
31
- attr_writer :config
32
-
33
- # Writer for the attributes of the SOAP input tag. Accepts a Hash.
34
- attr_writer :attributes
35
-
36
- # Writer for the namespace identifer of the <tt>Savon::SOAP::XML</tt>
37
- # object.
38
- attr_writer :namespace_identifier
39
-
40
- # Writer for the SOAP action of the <tt>Savon::SOAP::XML</tt> object.
41
- attr_writer :soap_action
42
-
43
- # Reader for the operation of the request being built by the request builder.
44
- attr_reader :operation
45
-
46
- # Builds and returns a <tt>Savon::SOAP::Request</tt> object. You may optionally
47
- # pass a block to the method that will be run after the initial configuration of
48
- # the dependencies. +self+ will be yielded to the block if the block accepts an
49
- # argument.
50
- def request(&post_configuration_block)
51
- configure_dependencies
52
-
53
- if post_configuration_block
54
- # Only yield self to the block if our block takes an argument
55
- args = [] and (args << self if post_configuration_block.arity == 1)
56
- post_configuration_block.call(*args)
57
- end
58
-
59
- Request.new(config, http, soap)
60
- end
61
-
62
- # Returns the identifier for the default namespace. If an operation namespace
63
- # identifier is defined for the current operation in the WSDL document, this
64
- # namespace identifier is used. Otherwise, the +@namespace_identifier+ instance
65
- # variable is used.
66
- def namespace_identifier
67
- if operation_namespace_defined_in_wsdl?
68
- wsdl.operations[operation][:namespace_identifier].to_sym
69
- else
70
- @namespace_identifier
71
- end
72
- end
73
-
74
- # Returns the namespace identifier to be used for the the SOAP input tag.
75
- # If +@namespace_identifier+ is not +nil+, it will be returned. Otherwise, the
76
- # default namespace identifier as returned by +namespace_identifier+ will be
77
- # returned.
78
- def input_namespace_identifier
79
- @namespace_identifier || namespace_identifier
80
- end
81
-
82
- # Returns the default namespace to be used for the SOAP request. If a namespace
83
- # is defined for the operation in the WSDL document, this namespace will be
84
- # returned. Otherwise, the default WSDL document namespace will be returned.
85
- def namespace
86
- if operation_namespace_defined_in_wsdl?
87
- wsdl.parser.namespaces[namespace_identifier.to_s]
88
- else
89
- wsdl.namespace
90
- end
91
- end
92
-
93
- # Returns true if the operation's namespace is defined within the WSDL
94
- # document.
95
- def operation_namespace_defined_in_wsdl?
96
- return false unless wsdl.document?
97
- (operation = wsdl.operations[self.operation]) && operation[:namespace_identifier]
98
- end
99
-
100
- # Returns the SOAP action. If +@soap_action+ has been defined, this will
101
- # be returned. Otherwise, if there is a WSDL document defined, the SOAP
102
- # action corresponding to the operation will be returned. Failing this,
103
- # the operation name will be used to form the SOAP action.
104
- def soap_action
105
- return @soap_action if @soap_action
106
-
107
- if wsdl.document?
108
- wsdl.soap_action(operation.to_sym)
109
- else
110
- Gyoku::XMLKey.create(operation).to_sym
111
- end
112
- end
113
-
114
- # Returns the SOAP operation input tag. If there is a WSDL document defined,
115
- # and the operation's input tag is defined in the document, this will be
116
- # returned. Otherwise, the operation name will be used to form the input tag.
117
- def soap_input_tag
118
- if wsdl.document? && (input = wsdl.soap_input(operation.to_sym))
119
- input
120
- else
121
- Gyoku::XMLKey.create(operation)
122
- end
123
- end
124
-
125
- # Changes the body of the SOAP request to +body+.
126
- def body=(body)
127
- soap.body = body
128
- end
129
-
130
- # Returns the body of the SOAP request.
131
- def body
132
- soap.body
133
- end
134
-
135
- # Returns the attributes of the SOAP input tag. Defaults to
136
- # an empty Hash.
137
- def attributes
138
- @attributes ||= {}
139
- end
140
-
141
- # Returns the <tt>Savon::Config</tt> object for the request. Defaults
142
- # to a clone of <tt>Savon.config</tt>.
143
- def config
144
- @config ||= Savon.config.clone
145
- end
146
-
147
- # Returns the <tt>HTTPI::Request</tt> object.
148
- def http
149
- @http ||= HTTPI::Request.new
150
- end
151
-
152
- # Returns the <tt>SOAP::XML</tt> object.
153
- def soap
154
- @soap ||= XML.new(config)
155
- end
156
-
157
- # Returns the <tt>Wasabi::Document</tt> object.
158
- def wsdl
159
- @wsdl ||= Wasabi::Document.new
160
- end
161
-
162
- # Returns the <tt>Akami::WSSE</tt> object.
163
- def wsse
164
- @wsse ||= Akami.wsse
165
- end
166
-
167
- private
168
-
169
- def configure_dependencies
170
- soap.endpoint = wsdl.endpoint
171
- soap.element_form_default = wsdl.element_form_default
172
- soap.wsse = wsse
173
-
174
- soap.namespace = namespace
175
- soap.namespace_identifier = namespace_identifier
176
-
177
- add_wsdl_namespaces_to_soap
178
- add_wsdl_types_to_soap
179
-
180
- soap.input = [input_namespace_identifier, soap_input_tag.to_sym, attributes]
181
-
182
- http.headers["SOAPAction"] = %{"#{soap_action}"}
183
- end
184
-
185
- def add_wsdl_namespaces_to_soap
186
- wsdl.type_namespaces.each do |path, uri|
187
- soap.use_namespace(path, uri)
188
- end
189
- end
190
-
191
- def add_wsdl_types_to_soap
192
- wsdl.type_definitions.each do |path, type|
193
- soap.types[path] = type
194
- end
195
- end
196
-
197
- def assign_options(options)
198
- options.each do |option, value|
199
- send(:"#{option}=", value) if value
200
- end
201
- end
202
-
203
- end
204
- end
205
- end
@@ -1,117 +0,0 @@
1
- require "savon/soap/xml"
2
- require "savon/soap/fault"
3
- require "savon/soap/invalid_response_error"
4
- require "savon/http/error"
5
-
6
- module Savon
7
- module SOAP
8
-
9
- # = Savon::SOAP::Response
10
- #
11
- # Represents the SOAP response and contains the HTTP response.
12
- class Response
13
-
14
- # Expects an <tt>HTTPI::Response</tt> and handles errors.
15
- def initialize(config, response)
16
- self.config = config
17
- self.http = response
18
- raise_errors if config.raise_errors
19
- end
20
-
21
- attr_accessor :http, :config
22
-
23
- # Returns whether the request was successful.
24
- def success?
25
- !soap_fault? && !http_error?
26
- end
27
-
28
- # Returns whether there was a SOAP fault.
29
- def soap_fault?
30
- soap_fault.present?
31
- end
32
-
33
- # Returns the <tt>Savon::SOAP::Fault</tt>.
34
- def soap_fault
35
- @soap_fault ||= Fault.new http
36
- end
37
-
38
- # Returns whether there was an HTTP error.
39
- def http_error?
40
- http_error.present?
41
- end
42
-
43
- # Returns the <tt>Savon::HTTP::Error</tt>.
44
- def http_error
45
- @http_error ||= HTTP::Error.new http
46
- end
47
-
48
- # Shortcut accessor for the SOAP response body Hash.
49
- def [](key)
50
- body[key]
51
- end
52
-
53
- # Returns the SOAP response header as a Hash.
54
- def header
55
- if !hash.has_key? :envelope
56
- raise Savon::SOAP::InvalidResponseError, "Unable to parse response body '#{to_xml}'"
57
- end
58
- hash[:envelope][:header]
59
- end
60
-
61
- # Returns the SOAP response body as a Hash.
62
- def body
63
- if !hash.has_key? :envelope
64
- raise Savon::SOAP::InvalidResponseError, "Unable to parse response body '#{to_xml}'"
65
- end
66
- hash[:envelope][:body]
67
- end
68
-
69
- alias to_hash body
70
-
71
- # Traverses the SOAP response body Hash for a given +path+ of Hash keys and returns
72
- # the value as an Array. Defaults to return an empty Array in case the path does not
73
- # exist or returns nil.
74
- def to_array(*path)
75
- result = path.inject body do |memo, key|
76
- return [] unless memo[key]
77
- memo[key]
78
- end
79
-
80
- result.kind_of?(Array) ? result.compact : [result].compact
81
- end
82
-
83
- # Returns the complete SOAP response XML without normalization.
84
- def hash
85
- @hash ||= Nori.parse(to_xml)
86
- end
87
-
88
- # Returns the SOAP response XML.
89
- def to_xml
90
- http.body
91
- end
92
-
93
- # Returns a <tt>Nokogiri::XML::Document</tt> for the SOAP response XML.
94
- def doc
95
- @doc ||= Nokogiri::XML(to_xml)
96
- end
97
-
98
- # Returns an Array of <tt>Nokogiri::XML::Node</tt> objects retrieved with the given +path+.
99
- # Automatically adds all of the document's namespaces unless a +namespaces+ hash is provided.
100
- def xpath(path, namespaces = nil)
101
- doc.xpath(path, namespaces || xml_namespaces)
102
- end
103
-
104
- private
105
-
106
- def raise_errors
107
- raise soap_fault if soap_fault?
108
- raise http_error if http_error?
109
- end
110
-
111
- def xml_namespaces
112
- @xml_namespaces ||= doc.collect_namespaces
113
- end
114
-
115
- end
116
- end
117
- end
@@ -1,257 +0,0 @@
1
- require "builder"
2
- require "gyoku"
3
- require "rexml/document"
4
- require "nori"
5
-
6
- require "savon/soap"
7
-
8
- Nori.configure do |config|
9
- config.strip_namespaces = true
10
- config.convert_tags_to { |tag| tag.snakecase.to_sym }
11
- end
12
-
13
- module Savon
14
- module SOAP
15
-
16
- # = Savon::SOAP::XML
17
- #
18
- # Represents the SOAP request XML. Contains various global and per request/instance settings
19
- # like the SOAP version, header, body and namespaces.
20
- class XML
21
-
22
- # XML Schema Type namespaces.
23
- SCHEMA_TYPES = {
24
- "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
25
- "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
26
- }
27
-
28
- # Expects a +config+ object.
29
- def initialize(config)
30
- self.config = config
31
- end
32
-
33
- attr_accessor :config
34
-
35
- # Accessor for the SOAP +input+ tag.
36
- attr_accessor :input
37
-
38
- # Accessor for the SOAP +endpoint+.
39
- attr_accessor :endpoint
40
-
41
- # Sets the SOAP +version+.
42
- def version=(version)
43
- raise ArgumentError, "Invalid SOAP version: #{version}" unless SOAP::VERSIONS.include? version
44
- @version = version
45
- end
46
-
47
- # Returns the SOAP +version+. Defaults to <tt>Savon.config.soap_version</tt>.
48
- def version
49
- @version ||= config.soap_version
50
- end
51
-
52
- # Sets the SOAP +header+ Hash.
53
- attr_writer :header
54
-
55
- # Returns the SOAP +header+. Defaults to an empty Hash.
56
- def header
57
- @header ||= config.soap_header.nil? ? {} : config.soap_header
58
- end
59
-
60
- # Sets the SOAP envelope namespace.
61
- attr_writer :env_namespace
62
-
63
- # Returns the SOAP envelope namespace. Uses the global namespace if set Defaults to :env.
64
- def env_namespace
65
- @env_namespace ||= config.env_namespace.nil? ? :env : config.env_namespace
66
- end
67
-
68
- # Sets the +namespaces+ Hash.
69
- attr_writer :namespaces
70
-
71
- # Returns the +namespaces+. Defaults to a Hash containing the SOAP envelope namespace.
72
- def namespaces
73
- @namespaces ||= begin
74
- key = ["xmlns"]
75
- key << env_namespace if env_namespace && env_namespace != ""
76
- { key.join(":") => SOAP::NAMESPACE[version] }
77
- end
78
- end
79
-
80
- def namespace_by_uri(uri)
81
- namespaces.each do |candidate_identifier, candidate_uri|
82
- return candidate_identifier.gsub(/^xmlns:/, '') if candidate_uri == uri
83
- end
84
- nil
85
- end
86
-
87
- def used_namespaces
88
- @used_namespaces ||= {}
89
- end
90
-
91
- def use_namespace(path, uri)
92
- @internal_namespace_count ||= 0
93
-
94
- unless identifier = namespace_by_uri(uri)
95
- identifier = "ins#{@internal_namespace_count}"
96
- namespaces["xmlns:#{identifier}"] = uri
97
- @internal_namespace_count += 1
98
- end
99
-
100
- used_namespaces[path] = identifier
101
- end
102
-
103
- def types
104
- @types ||= {}
105
- end
106
-
107
- # Sets the default namespace identifier.
108
- attr_writer :namespace_identifier
109
-
110
- # Returns the default namespace identifier.
111
- def namespace_identifier
112
- @namespace_identifier ||= :wsdl
113
- end
114
-
115
- # Accessor for whether all local elements should be namespaced.
116
- attr_accessor :element_form_default
117
-
118
- # Accessor for the default namespace URI.
119
- attr_accessor :namespace
120
-
121
- # Accessor for the <tt>Savon::WSSE</tt> object.
122
- attr_accessor :wsse
123
-
124
- def signature?
125
- wsse.respond_to?(:signature?) && wsse.signature?
126
- end
127
-
128
- # Returns the SOAP request encoding. Defaults to "UTF-8".
129
- def encoding
130
- @encoding ||= "UTF-8"
131
- end
132
-
133
- # Sets the SOAP request encoding.
134
- attr_writer :encoding
135
-
136
- # Accepts a +block+ and yields a <tt>Builder::XmlMarkup</tt> object to let you create
137
- # custom body XML.
138
- def body
139
- @body = yield builder(nil) if block_given?
140
- @body
141
- end
142
-
143
- # Sets the SOAP +body+. Expected to be a Hash that can be translated to XML via `Gyoku.xml`
144
- # or any other Object responding to to_s.
145
- attr_writer :body
146
-
147
- # Accepts a +block+ and yields a <tt>Builder::XmlMarkup</tt> object to let you create
148
- # a completely custom XML.
149
- def xml(directive_tag = :xml, attrs = {})
150
- @xml = yield builder(directive_tag, attrs) if block_given?
151
- end
152
-
153
- # Accepts an XML String and lets you specify a completely custom request body.
154
- attr_writer :xml
155
-
156
- # Returns the XML for a SOAP request.
157
- def to_xml(clear_cache = false)
158
- if clear_cache
159
- @xml = nil
160
- @header_for_xml = nil
161
- end
162
-
163
- @xml ||= tag(builder, :Envelope, complete_namespaces) do |xml|
164
- tag(xml, :Header) { xml << header_for_xml } unless header_for_xml.empty?
165
-
166
- # TODO: Maybe there should be some sort of plugin architecture where
167
- # classes like WSSE::Signature can hook into this process.
168
- body_attributes = (signature? ? wsse.signature.body_attributes : {})
169
-
170
- if input.nil?
171
- tag(xml, :Body, body_attributes)
172
- else
173
- tag(xml, :Body, body_attributes) { xml.tag!(*add_namespace_to_input) { xml << body_to_xml } }
174
- end
175
- end
176
- end
177
-
178
- private
179
-
180
- # Returns a new <tt>Builder::XmlMarkup</tt> object.
181
- def builder(directive_tag = :xml, attrs = { :encoding => encoding })
182
- builder = Builder::XmlMarkup.new
183
- builder.instruct!(directive_tag, attrs) if directive_tag
184
- builder
185
- end
186
-
187
- # Expects a builder +xml+ instance, a tag +name+ and accepts optional +namespaces+
188
- # and a block to create an XML tag.
189
- def tag(xml, name, namespaces = {}, &block)
190
- if env_namespace && env_namespace != ""
191
- xml.tag! env_namespace, name, namespaces, &block
192
- else
193
- xml.tag! name, namespaces, &block
194
- end
195
- end
196
-
197
- # Returns the complete Hash of namespaces.
198
- def complete_namespaces
199
- defaults = SCHEMA_TYPES.dup
200
- defaults["xmlns:#{namespace_identifier}"] = namespace if namespace
201
- defaults.merge namespaces
202
- end
203
-
204
- # Returns the SOAP header as an XML String.
205
- def header_for_xml
206
- @header_for_xml ||= (Hash === header ? Gyoku.xml(header) : header) + wsse_header
207
- end
208
-
209
- # Returns the WSSE header or an empty String in case WSSE was not set.
210
- def wsse_header
211
- wsse.respond_to?(:to_xml) ? wsse.to_xml : ""
212
- end
213
-
214
- # Returns the SOAP body as an XML String.
215
- def body_to_xml
216
- return body.to_s unless body.kind_of? Hash
217
- body_to_xml = element_form_default == :qualified ? add_namespaces_to_body(body) : body
218
- Gyoku.xml body_to_xml, :element_form_default => element_form_default, :namespace => namespace_identifier
219
- end
220
-
221
- def add_namespaces_to_body(hash, path = [input[1].to_s])
222
- return unless hash
223
- return hash.map { |value| add_namespaces_to_body(value, path) } if hash.kind_of?(Array)
224
- return hash.to_s unless hash.kind_of? Hash
225
-
226
- hash.inject({}) do |newhash, (key, value)|
227
- camelcased_key = Gyoku::XMLKey.create(key)
228
- newpath = path + [camelcased_key]
229
-
230
- if used_namespaces[newpath]
231
- newhash.merge(
232
- "#{used_namespaces[newpath]}:#{camelcased_key}" =>
233
- add_namespaces_to_body(value, types[newpath] ? [types[newpath]] : newpath)
234
- )
235
- else
236
- add_namespaces_to_values(value, path) if key == :order!
237
- newhash.merge(key => value)
238
- end
239
- end
240
- end
241
-
242
- def add_namespace_to_input
243
- return input.compact unless used_namespaces[[input[1].to_s]]
244
- [used_namespaces[[input[1].to_s]], input[1], input[2]]
245
- end
246
-
247
- def add_namespaces_to_values(values, path)
248
- values.collect! { |value|
249
- camelcased_value = Gyoku::XMLKey.create(value)
250
- namespace_path = path + [camelcased_value.to_s]
251
- namespace = used_namespaces[namespace_path]
252
- "#{namespace.blank? ? '' : namespace + ":"}#{camelcased_value}"
253
- }
254
- end
255
- end
256
- end
257
- end