savon 1.2.0 → 2.0.0

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 (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