savon 2.15.1 → 2.17.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -2
- data/README.md +22 -2
- data/Rakefile +6 -2
- data/lib/savon/block_interface.rb +3 -4
- data/lib/savon/builder.rb +25 -31
- data/lib/savon/client.rb +44 -13
- data/lib/savon/header.rb +12 -12
- data/lib/savon/http_error.rb +1 -3
- data/lib/savon/log_message.rb +2 -3
- data/lib/savon/message.rb +6 -7
- data/lib/savon/mock/expectation.rb +37 -23
- data/lib/savon/mock/spec_helper.rb +7 -11
- data/lib/savon/mock.rb +1 -0
- data/lib/savon/model.rb +12 -17
- data/lib/savon/operation.rb +93 -56
- data/lib/savon/options.rb +253 -153
- data/lib/savon/qualified_message.rb +2 -1
- data/lib/savon/response.rb +30 -23
- data/lib/savon/soap_fault.rb +6 -6
- data/lib/savon/string_utils.rb +3 -4
- data/lib/savon/transport/faraday.rb +70 -0
- data/lib/savon/transport/httpi.rb +136 -0
- data/lib/savon/transport/logging.rb +60 -0
- data/lib/savon/transport/response.rb +44 -0
- data/lib/savon/version.rb +2 -1
- data/lib/savon.rb +2 -3
- metadata +89 -73
- data/lib/savon/request.rb +0 -113
- data/lib/savon/request_logger.rb +0 -54
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e494bfc08714bcce13c230100c2d5677ac2b40cca1a5afb2312534b41c4c81e9
|
|
4
|
+
data.tar.gz: fb3b31cb0684a0e7742f247f121a9d3d5749fc932ea089ae26bf4af59fd48e24
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b79c099e954cb9bb176a898f515d12bcaaa557d5d15045155029e6322f3121b5304daf76e07d0927323f3e6481b43aee754d0bfa516eb0890807f12ecc6c6053
|
|
7
|
+
data.tar.gz: f39e6773c7c20d254857a420f6c54f38b74e1e0e64cf2e51254ca4ab43b482e2bb910860fc1f86ce86c18653e940caae115b5ace3dcad8c83db819370dd77b8c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
# Savon changelog
|
|
2
2
|
|
|
3
|
-
##
|
|
4
|
-
|
|
3
|
+
## 2.17.0 (2026-05-19)
|
|
4
|
+
|
|
5
|
+
**Add opt-in Faraday transport**
|
|
6
|
+
|
|
7
|
+
Callers who set `transport: :faraday` get a memoized `Faraday::Connection` via `client.faraday` and full control over middleware, SSL, auth, and timeouts. Callers who do not set this option see no behavior change. HTTPI remains the default for 2.x.
|
|
8
|
+
|
|
9
|
+
* Add: `transport: :faraday` global option. Defaults to `:httpi` (#992).
|
|
10
|
+
* Add: `client.faraday` returns a memoized `Faraday::Connection` for configuring middleware, SSL, auth, and timeouts when using the Faraday transport.
|
|
11
|
+
* Add: `Savon.client` raises if `transport: :faraday` is set but the faraday gem is not installed, or if any httpi-specific global option (`proxy`, timeouts, `ssl`, auth, `adapter`) is set alongside it. All conflicts are reported with their Faraday equivalents.
|
|
12
|
+
* Change: Observers must return `Savon::Transport::Response` (or `nil`) instead of `HTTPI::Response`. Returning `HTTPI::Response` still works but emits a deprecation warning.
|
|
13
|
+
* Unblocks:
|
|
14
|
+
* redirect following for WSDL fetches via `faraday-follow-redirects` middleware (#1033, savonrb/wasabi#18)
|
|
15
|
+
* digest authentication via `faraday-digestauth` middleware (#1021, savonrb/httpi#250)
|
|
16
|
+
* proxy authentication with special characters in passwords (#941)
|
|
17
|
+
* and setting an `Accept` header for WSDL requests from Rails apps (savonrb/wasabi#115)
|
|
18
|
+
|
|
19
|
+
## 2.16.0 (2026-05-18)
|
|
20
|
+
|
|
21
|
+
**Restore compatibility**
|
|
22
|
+
|
|
23
|
+
If you stayed on 2.12.1 because a later version broke something, this release is for you. The fixes below target the most commonly reported upgrade blockers. Existing code should work without modification.
|
|
24
|
+
|
|
25
|
+
* Fix: Restore `Savon::Response#hash` removed in 2.14.0 (#985). Callers on 2.12.1 that use `response.hash` get the soap body back instead of Ruby's integer object id. A deprecation warning is emitted on each call. Use `#full_hash` going forward.
|
|
26
|
+
* Fix: Require wasabi >= 5.1.0 (#1015, #1016). Wasabi 4.x used message names as soap body element names and SOAPAction header values instead of operation names (savonrb/wasabi#122), causing servers to return a fault or reject the action for operations whose message name carried an `In` suffix.
|
|
27
|
+
* Fix: Stop dumping all WSDL namespaces into every soap envelope (#1014, #942). 2.13.0 injected every namespace from the entire WSDL document into each request, including structural ones that have no place in a request body. Strict servers reject envelopes with unexpected or duplicate declarations.
|
|
28
|
+
* Fix: Raise a proper `SOAPFault` instead of a raw exception when `soap:Fault` contains invalid encoding (#923).
|
|
29
|
+
* Fix: `SOAPFault.present?` was ignoring its `xml` argument and always operating on the instance's own body.
|
|
30
|
+
* Change: Added Ruby 3.4 (#1024) and Ruby 4.0 (#1039) to the CI test matrix.
|
|
5
31
|
|
|
6
32
|
## 2.15.1 (2024-07-08)
|
|
7
33
|
|
data/README.md
CHANGED
|
@@ -3,11 +3,10 @@
|
|
|
3
3
|
Heavy metal SOAP client
|
|
4
4
|
|
|
5
5
|
[Documentation](https://www.rubydoc.info/gems/savon/) | [Support](https://stackoverflow.com/questions/tagged/savon) |
|
|
6
|
-
[Mailing list](https://groups.google.com/forum/#!forum/savonrb)
|
|
6
|
+
[Mailing list](https://groups.google.com/forum/#!forum/savonrb)
|
|
7
7
|
|
|
8
8
|
[](https://github.com/savonrb/savon/actions/workflows/ci.yml)
|
|
9
9
|
[](http://badge.fury.io/rb/savon)
|
|
10
|
-
[](https://codeclimate.com/github/savonrb/savon)
|
|
11
10
|
[](https://coveralls.io/r/savonrb/savon)
|
|
12
11
|
|
|
13
12
|
|
|
@@ -52,6 +51,27 @@ response.body
|
|
|
52
51
|
For more examples, you should check out the
|
|
53
52
|
[integration tests](https://github.com/savonrb/savon/tree/version2/spec/integration).
|
|
54
53
|
|
|
54
|
+
## Faraday transport
|
|
55
|
+
|
|
56
|
+
Savon uses HTTPI for HTTP by default. To opt into Faraday instead, add `faraday` to your Gemfile and set `transport: :faraday`:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
client = Savon.client(
|
|
60
|
+
transport: :faraday,
|
|
61
|
+
wsdl: "http://service.example.com?wsdl"
|
|
62
|
+
)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Configure SSL, auth, timeouts, middleware, and anything else transport-related on the `Faraday::Connection` before making calls:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
client.faraday.ssl.verify = false
|
|
69
|
+
client.faraday.options.timeout = 30
|
|
70
|
+
client.faraday.options.open_timeout = 5
|
|
71
|
+
client.faraday.headers["Authorization"] = "Bearer #{token}"
|
|
72
|
+
client.faraday.use Faraday::Response::Logger
|
|
73
|
+
```
|
|
74
|
+
|
|
55
75
|
## Ruby version support
|
|
56
76
|
|
|
57
77
|
Every savon release is tested with contemporary supported versions of ruby. Historical compatibility information:
|
data/Rakefile
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require "bundler/gem_tasks"
|
|
2
4
|
require "rspec/core/rake_task"
|
|
3
5
|
|
|
@@ -10,5 +12,7 @@ RSpec::Core::RakeTask.new "spec:integration" do |t|
|
|
|
10
12
|
t.pattern = "spec/integration/**/*_spec.rb"
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
task :
|
|
15
|
+
desc "Alias for spec task"
|
|
16
|
+
task test: :spec
|
|
17
|
+
|
|
18
|
+
task default: :spec
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Savon
|
|
3
4
|
class BlockInterface
|
|
4
|
-
|
|
5
5
|
def initialize(target)
|
|
6
6
|
@target = target
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def evaluate(block)
|
|
10
|
-
if block.arity
|
|
10
|
+
if block.arity.positive?
|
|
11
11
|
block.call(@target)
|
|
12
12
|
else
|
|
13
|
-
@original = eval("self", block.binding)
|
|
13
|
+
@original = eval("self", block.binding, __FILE__, __LINE__)
|
|
14
14
|
instance_eval(&block)
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -22,6 +22,5 @@ module Savon
|
|
|
22
22
|
rescue NoMethodError
|
|
23
23
|
@original.send(method, *args, &block)
|
|
24
24
|
end
|
|
25
|
-
|
|
26
25
|
end
|
|
27
26
|
end
|
data/lib/savon/builder.rb
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "savon/header"
|
|
3
4
|
require "savon/message"
|
|
4
5
|
require "nokogiri"
|
|
@@ -12,12 +13,12 @@ module Savon
|
|
|
12
13
|
SCHEMA_TYPES = {
|
|
13
14
|
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
|
14
15
|
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
|
|
15
|
-
}
|
|
16
|
+
}.freeze
|
|
16
17
|
|
|
17
18
|
SOAP_NAMESPACE = {
|
|
18
19
|
1 => "http://schemas.xmlsoap.org/soap/envelope/",
|
|
19
20
|
2 => "http://www.w3.org/2003/05/soap-envelope"
|
|
20
|
-
}
|
|
21
|
+
}.freeze
|
|
21
22
|
|
|
22
23
|
WSA_NAMESPACE = "http://www.w3.org/2005/08/addressing"
|
|
23
24
|
|
|
@@ -34,7 +35,7 @@ module Savon
|
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def pretty
|
|
37
|
-
Nokogiri.XML(to_s).to_xml(:
|
|
38
|
+
Nokogiri.XML(to_s).to_xml(indent: 2)
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
def build_document
|
|
@@ -71,33 +72,36 @@ module Savon
|
|
|
71
72
|
|
|
72
73
|
def to_s
|
|
73
74
|
return @locals[:xml] if @locals.include? :xml
|
|
75
|
+
|
|
74
76
|
build_document
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
private
|
|
78
80
|
|
|
79
81
|
def convert_type_definitions_to_hash
|
|
80
|
-
@wsdl.type_definitions.
|
|
82
|
+
@wsdl.type_definitions.each_with_object({}) do |(path, type), memo|
|
|
81
83
|
memo[path] = type
|
|
82
|
-
memo
|
|
83
84
|
end
|
|
84
85
|
end
|
|
85
86
|
|
|
86
87
|
def convert_type_namespaces_to_hash
|
|
87
|
-
@wsdl.type_namespaces.
|
|
88
|
+
@wsdl.type_namespaces.each_with_object({}) do |(path, uri), memo|
|
|
88
89
|
key, value = use_namespace(path, uri)
|
|
89
90
|
memo[key] = value
|
|
90
|
-
memo
|
|
91
91
|
end
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
def use_namespace(path, uri)
|
|
95
95
|
@internal_namespace_count ||= 0
|
|
96
96
|
|
|
97
|
-
unless identifier = namespace_by_uri(uri)
|
|
98
|
-
|
|
97
|
+
unless (identifier = namespace_by_uri(uri))
|
|
98
|
+
wsdl_identifier = @wsdl.document? ? @wsdl.parser.namespaces.key(uri) : nil
|
|
99
|
+
# The prefix may already be taken by the target namespace or a user-supplied
|
|
100
|
+
# :namespaces override - fall back to ins0, ins1... rather than overwriting it.
|
|
101
|
+
wsdl_identifier = nil if wsdl_identifier && namespaces.key?("xmlns:#{wsdl_identifier}")
|
|
102
|
+
identifier = wsdl_identifier || "ins#{@internal_namespace_count}"
|
|
99
103
|
namespaces["xmlns:#{identifier}"] = uri
|
|
100
|
-
@internal_namespace_count += 1
|
|
104
|
+
@internal_namespace_count += 1 unless wsdl_identifier
|
|
101
105
|
end
|
|
102
106
|
|
|
103
107
|
[path, identifier]
|
|
@@ -116,21 +120,9 @@ module Savon
|
|
|
116
120
|
@globals[:namespace] || @wsdl.namespace
|
|
117
121
|
|
|
118
122
|
# check env_namespace
|
|
119
|
-
namespaces["xmlns#{env_namespace && env_namespace !=
|
|
123
|
+
namespaces["xmlns#{env_namespace && env_namespace != '' ? ":#{env_namespace}" : ''}"] =
|
|
120
124
|
SOAP_NAMESPACE[@globals[:soap_version]]
|
|
121
125
|
|
|
122
|
-
if @wsdl&.document
|
|
123
|
-
@wsdl.parser.namespaces.each do |identifier, path|
|
|
124
|
-
next if identifier == 'xmlns' # Do not include xmlns namespace as this causes issues for some servers (https://github.com/savonrb/savon/issues/986)
|
|
125
|
-
|
|
126
|
-
prefixed_identifier = "xmlns:#{identifier}"
|
|
127
|
-
|
|
128
|
-
next if namespaces.key?(prefixed_identifier)
|
|
129
|
-
|
|
130
|
-
namespaces[prefixed_identifier] = path
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
126
|
namespaces
|
|
135
127
|
end
|
|
136
128
|
end
|
|
@@ -145,8 +137,9 @@ module Savon
|
|
|
145
137
|
|
|
146
138
|
def namespaced_message_tag
|
|
147
139
|
tag_name = message_tag
|
|
148
|
-
return [tag_name] if @wsdl.document?
|
|
149
|
-
|
|
140
|
+
return [tag_name] if @wsdl.document? && @wsdl.soap_input(@operation_name.to_sym).is_a?(Hash)
|
|
141
|
+
|
|
142
|
+
if namespace_identifier.nil?
|
|
150
143
|
[tag_name, message_attributes]
|
|
151
144
|
elsif @used_namespaces[[tag_name.to_s]]
|
|
152
145
|
[@used_namespaces[[tag_name.to_s]], tag_name, message_attributes]
|
|
@@ -164,6 +157,7 @@ module Savon
|
|
|
164
157
|
message_tag = serialized_message_tag[1]
|
|
165
158
|
@wsdl.soap_input(@operation_name.to_sym)[message_tag].each_pair do |message, type|
|
|
166
159
|
break if @locals[:message].nil?
|
|
160
|
+
|
|
167
161
|
message_locals = @locals[:message][StringUtils.snakecase(message).to_sym]
|
|
168
162
|
message_content = Message.new(message_tag, namespace_identifier, @types, @used_namespaces, message_locals, :unqualified, @globals[:convert_request_keys_to], @globals[:unwrap]).to_s
|
|
169
163
|
messages += "<#{message} xsi:type=\"#{type.join(':')}\">#{message_content}</#{message}>"
|
|
@@ -177,7 +171,7 @@ module Savon
|
|
|
177
171
|
message_tag = wsdl_tag_name.keys.first if wsdl_tag_name.is_a?(Hash)
|
|
178
172
|
message_tag ||= @locals[:message_tag]
|
|
179
173
|
message_tag ||= wsdl_tag_name
|
|
180
|
-
message_tag ||= Gyoku.xml_tag(@operation_name, :
|
|
174
|
+
message_tag ||= Gyoku.xml_tag(@operation_name, key_converter: @globals[:convert_request_keys_to])
|
|
181
175
|
|
|
182
176
|
message_tag.to_sym
|
|
183
177
|
end
|
|
@@ -187,7 +181,7 @@ module Savon
|
|
|
187
181
|
end
|
|
188
182
|
|
|
189
183
|
def body_message
|
|
190
|
-
if @wsdl.document?
|
|
184
|
+
if @wsdl.document? && @wsdl.soap_input(@operation_name.to_sym).is_a?(Hash)
|
|
191
185
|
serialized_messages
|
|
192
186
|
else
|
|
193
187
|
message.to_s
|
|
@@ -221,7 +215,7 @@ module Savon
|
|
|
221
215
|
|
|
222
216
|
def builder
|
|
223
217
|
builder = ::Builder::XmlMarkup.new
|
|
224
|
-
builder.instruct!(:xml, :
|
|
218
|
+
builder.instruct!(:xml, encoding: @globals[:encoding])
|
|
225
219
|
builder
|
|
226
220
|
end
|
|
227
221
|
|
|
@@ -254,7 +248,7 @@ module Savon
|
|
|
254
248
|
|
|
255
249
|
# the mail.body.encoded algorithm reorders the parts, default order is [ "text/plain", "text/enriched", "text/html" ]
|
|
256
250
|
# should redefine the sort order, because the soap request xml should be the first
|
|
257
|
-
multipart_message.body.set_sort_order [
|
|
251
|
+
multipart_message.body.set_sort_order ["text/xml"]
|
|
258
252
|
|
|
259
253
|
multipart_message.body.encoded(multipart_message.content_transfer_encoding)
|
|
260
254
|
end
|
|
@@ -269,10 +263,10 @@ module Savon
|
|
|
269
263
|
end
|
|
270
264
|
multipart_message.add_part xml_part
|
|
271
265
|
|
|
272
|
-
#request.headers["Content-Type"] = "multipart/related; boundary=\"#{multipart_message.body.boundary}\"; type=\"text/xml\"; start=\"#{xml_part.content_id}\""
|
|
266
|
+
# request.headers["Content-Type"] = "multipart/related; boundary=\"#{multipart_message.body.boundary}\"; type=\"text/xml\"; start=\"#{xml_part.content_id}\""
|
|
273
267
|
@multipart = {
|
|
274
268
|
multipart_boundary: multipart_message.body.boundary,
|
|
275
|
-
start: xml_part.content_id
|
|
269
|
+
start: xml_part.content_id
|
|
276
270
|
}
|
|
277
271
|
|
|
278
272
|
multipart_message
|
data/lib/savon/client.rb
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "savon/operation"
|
|
3
|
-
require "savon/
|
|
4
|
+
require "savon/transport/httpi"
|
|
5
|
+
require "savon/transport/faraday"
|
|
4
6
|
require "savon/options"
|
|
5
7
|
require "savon/block_interface"
|
|
6
8
|
require "wasabi"
|
|
7
9
|
|
|
8
10
|
module Savon
|
|
11
|
+
# The main entry point for Savon.
|
|
12
|
+
#
|
|
13
|
+
# Holds global configuration, owns the WSDL document, and dispatches
|
|
14
|
+
# named operations. A single Client instance is typically shared across
|
|
15
|
+
# multiple calls to the same service.
|
|
9
16
|
class Client
|
|
10
|
-
|
|
11
17
|
def initialize(globals = {}, &block)
|
|
12
|
-
unless globals.
|
|
18
|
+
unless globals.is_a? Hash
|
|
13
19
|
raise_version1_initialize_error! globals
|
|
14
20
|
end
|
|
15
21
|
|
|
16
22
|
set_globals(globals, block)
|
|
23
|
+
@globals.validate_transport!
|
|
17
24
|
|
|
18
25
|
unless wsdl_or_endpoint_and_namespace_specified?
|
|
19
26
|
raise_initialization_error!
|
|
@@ -24,13 +31,25 @@ module Savon
|
|
|
24
31
|
|
|
25
32
|
attr_reader :globals, :wsdl
|
|
26
33
|
|
|
34
|
+
# Returns the memoized Faraday::Connection for this client.
|
|
35
|
+
# Callers use this to configure middleware, SSL, auth, timeouts, and any
|
|
36
|
+
# other transport-level concern before making calls.
|
|
37
|
+
# Raises ArgumentError if transport is not :faraday.
|
|
38
|
+
def faraday
|
|
39
|
+
unless @globals[:transport] == :faraday
|
|
40
|
+
raise ArgumentError, "client.faraday is only available when transport: :faraday is set"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@faraday ||= ::Faraday.new
|
|
44
|
+
end
|
|
45
|
+
|
|
27
46
|
def operations
|
|
28
47
|
raise_missing_wsdl_error! unless @wsdl.document?
|
|
29
48
|
@wsdl.soap_actions
|
|
30
49
|
end
|
|
31
50
|
|
|
32
51
|
def operation(operation_name)
|
|
33
|
-
Operation.create(operation_name, @wsdl, @globals)
|
|
52
|
+
Operation.create(operation_name, @wsdl, @globals, build_transport)
|
|
34
53
|
end
|
|
35
54
|
|
|
36
55
|
def call(operation_name, locals = {}, &block)
|
|
@@ -48,6 +67,15 @@ module Savon
|
|
|
48
67
|
|
|
49
68
|
private
|
|
50
69
|
|
|
70
|
+
# Builds the transport for a single operation.
|
|
71
|
+
def build_transport
|
|
72
|
+
if @globals[:transport] == :faraday
|
|
73
|
+
Transport::Faraday.new(faraday, @globals)
|
|
74
|
+
else
|
|
75
|
+
Transport::HTTPI.new(@globals)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
51
79
|
def set_globals(globals, block)
|
|
52
80
|
globals = GlobalOptions.new(globals)
|
|
53
81
|
BlockInterface.new(globals).evaluate(block) if block
|
|
@@ -58,12 +86,16 @@ module Savon
|
|
|
58
86
|
def build_wsdl_document
|
|
59
87
|
@wsdl = Wasabi::Document.new
|
|
60
88
|
|
|
61
|
-
@wsdl.document
|
|
62
|
-
@wsdl.endpoint
|
|
63
|
-
@wsdl.namespace
|
|
64
|
-
@wsdl.adapter = @globals[:adapter] if @globals.include? :adapter
|
|
89
|
+
@wsdl.document = @globals[:wsdl] if @globals.include? :wsdl
|
|
90
|
+
@wsdl.endpoint = @globals[:endpoint] if @globals.include? :endpoint
|
|
91
|
+
@wsdl.namespace = @globals[:namespace] if @globals.include? :namespace
|
|
65
92
|
|
|
66
|
-
@
|
|
93
|
+
if @globals[:transport] == :faraday
|
|
94
|
+
@wsdl.request = faraday
|
|
95
|
+
else
|
|
96
|
+
@wsdl.adapter = @globals[:adapter] if @globals.include? :adapter
|
|
97
|
+
@wsdl.request = Transport::HTTPI.new(@globals).wsdl_request
|
|
98
|
+
end
|
|
67
99
|
end
|
|
68
100
|
|
|
69
101
|
def wsdl_or_endpoint_and_namespace_specified?
|
|
@@ -72,9 +104,9 @@ module Savon
|
|
|
72
104
|
|
|
73
105
|
def raise_version1_initialize_error!(object)
|
|
74
106
|
raise InitializationError,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
107
|
+
"Some code tries to initialize Savon with the #{object.inspect} (#{object.class}) \n" \
|
|
108
|
+
"Savon 2 expects a Hash of options for creating a new client and executing requests.\n" \
|
|
109
|
+
"Please read the updated documentation for version 2: http://savonrb.com/version2.html"
|
|
78
110
|
end
|
|
79
111
|
|
|
80
112
|
def raise_initialization_error!
|
|
@@ -88,6 +120,5 @@ module Savon
|
|
|
88
120
|
def raise_missing_wsdl_error!
|
|
89
121
|
raise "Unable to inspect the service without a WSDL document."
|
|
90
122
|
end
|
|
91
|
-
|
|
92
123
|
end
|
|
93
124
|
end
|
data/lib/savon/header.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "akami"
|
|
3
4
|
require "gyoku"
|
|
4
5
|
require "securerandom"
|
|
5
6
|
|
|
6
7
|
module Savon
|
|
7
8
|
class Header
|
|
8
|
-
|
|
9
9
|
def initialize(globals, locals)
|
|
10
|
-
@gyoku_options = { :
|
|
10
|
+
@gyoku_options = { key_converter: globals[:convert_request_keys_to] }
|
|
11
11
|
|
|
12
12
|
@wsse_auth = locals[:wsse_auth].nil? ? globals[:wsse_auth] : locals[:wsse_auth]
|
|
13
13
|
@wsse_timestamp = locals[:wsse_timestamp].nil? ? globals[:wsse_timestamp] : locals[:wsse_timestamp]
|
|
@@ -41,7 +41,7 @@ module Savon
|
|
|
41
41
|
|
|
42
42
|
def build_header
|
|
43
43
|
header =
|
|
44
|
-
if global_header.
|
|
44
|
+
if global_header.is_a?(Hash) && local_header.is_a?(Hash)
|
|
45
45
|
global_header.merge(local_header)
|
|
46
46
|
elsif local_header
|
|
47
47
|
local_header
|
|
@@ -58,16 +58,17 @@ module Savon
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def build_wsa_header
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
return '' unless @globals[:use_wsa_headers]
|
|
62
|
+
|
|
63
|
+
convert_to_xml({
|
|
64
|
+
'wsa:Action' => @locals[:soap_action],
|
|
65
|
+
'wsa:To' => @globals[:endpoint],
|
|
66
|
+
'wsa:MessageID' => "urn:uuid:#{SecureRandom.uuid}"
|
|
67
|
+
})
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
def convert_to_xml(hash_or_string)
|
|
70
|
-
if hash_or_string.
|
|
71
|
+
if hash_or_string.is_a? Hash
|
|
71
72
|
Gyoku.xml(hash_or_string, gyoku_options)
|
|
72
73
|
else
|
|
73
74
|
hash_or_string.to_s
|
|
@@ -78,12 +79,11 @@ module Savon
|
|
|
78
79
|
wsse = Akami.wsse
|
|
79
80
|
wsse.credentials(*wsse_auth) if wsse_auth
|
|
80
81
|
wsse.timestamp = wsse_timestamp if wsse_timestamp
|
|
81
|
-
if wsse_signature
|
|
82
|
+
if wsse_signature&.have_document?
|
|
82
83
|
wsse.signature = wsse_signature
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
wsse
|
|
86
87
|
end
|
|
87
|
-
|
|
88
88
|
end
|
|
89
89
|
end
|
data/lib/savon/http_error.rb
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module Savon
|
|
4
4
|
class HTTPError < Error
|
|
5
|
-
|
|
6
5
|
def self.present?(http)
|
|
7
6
|
http.error?
|
|
8
7
|
end
|
|
@@ -20,8 +19,7 @@ module Savon
|
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
def to_hash
|
|
23
|
-
{ :
|
|
22
|
+
{ code: @http.code, headers: @http.headers, body: @http.body }
|
|
24
23
|
end
|
|
25
|
-
|
|
26
24
|
end
|
|
27
25
|
end
|
data/lib/savon/log_message.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "nokogiri"
|
|
3
4
|
|
|
4
5
|
module Savon
|
|
5
6
|
class LogMessage
|
|
6
|
-
|
|
7
7
|
def initialize(message, filters = [], pretty_print = false)
|
|
8
8
|
@message = message
|
|
9
9
|
@filters = filters
|
|
@@ -46,8 +46,7 @@ module Savon
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def nokogiri_options
|
|
49
|
-
@pretty_print ? { :
|
|
49
|
+
@pretty_print ? { indent: 2 } : { save_with: Nokogiri::XML::Node::SaveOptions::AS_XML }
|
|
50
50
|
end
|
|
51
|
-
|
|
52
51
|
end
|
|
53
52
|
end
|
data/lib/savon/message.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "savon/qualified_message"
|
|
3
4
|
require "gyoku"
|
|
4
5
|
|
|
5
6
|
module Savon
|
|
6
7
|
class Message
|
|
7
|
-
|
|
8
8
|
def initialize(message_tag, namespace_identifier, types, used_namespaces, message, element_form_default, key_converter, unwrap)
|
|
9
9
|
@message_tag = message_tag
|
|
10
10
|
@namespace_identifier = namespace_identifier
|
|
@@ -18,21 +18,20 @@ module Savon
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def to_s
|
|
21
|
-
return @message.to_s unless @message.
|
|
21
|
+
return @message.to_s unless @message.is_a? Hash
|
|
22
22
|
|
|
23
23
|
if @element_form_default == :qualified
|
|
24
24
|
@message = QualifiedMessage.new(@types, @used_namespaces, @key_converter).to_hash(@message, [@message_tag.to_s])
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
gyoku_options = {
|
|
28
|
-
:
|
|
29
|
-
:
|
|
30
|
-
:
|
|
31
|
-
:
|
|
28
|
+
element_form_default: @element_form_default,
|
|
29
|
+
namespace: @namespace_identifier,
|
|
30
|
+
key_converter: @key_converter,
|
|
31
|
+
unwrap: @unwrap
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
Gyoku.xml(@message, gyoku_options)
|
|
35
35
|
end
|
|
36
|
-
|
|
37
36
|
end
|
|
38
37
|
end
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "httpi"
|
|
4
|
+
require "savon/transport/response"
|
|
3
5
|
|
|
4
6
|
module Savon
|
|
7
|
+
# A single test expectation set up by Savon's mock interface.
|
|
8
|
+
# One expectation covers one operation call in one test.
|
|
9
|
+
#
|
|
10
|
+
# Records the expected operation name and message, captures what was
|
|
11
|
+
# actually called, and either returns a synthetic response or raises
|
|
12
|
+
# an error on mismatch.
|
|
5
13
|
class MockExpectation
|
|
6
|
-
|
|
7
14
|
def initialize(operation_name)
|
|
8
|
-
@expected = { :
|
|
15
|
+
@expected = { operation_name: operation_name }
|
|
9
16
|
@actual = nil
|
|
10
17
|
end
|
|
11
18
|
|
|
@@ -15,15 +22,15 @@ module Savon
|
|
|
15
22
|
end
|
|
16
23
|
|
|
17
24
|
def returns(response)
|
|
18
|
-
response = { :
|
|
25
|
+
response = { code: 200, headers: {}, body: response } if response.is_a?(String)
|
|
19
26
|
@response = response
|
|
20
27
|
self
|
|
21
28
|
end
|
|
22
29
|
|
|
23
|
-
def actual(operation_name,
|
|
30
|
+
def actual(operation_name, _builder, _globals, locals)
|
|
24
31
|
@actual = {
|
|
25
|
-
:
|
|
26
|
-
:
|
|
32
|
+
operation_name: operation_name,
|
|
33
|
+
message: locals[:message]
|
|
27
34
|
}
|
|
28
35
|
end
|
|
29
36
|
|
|
@@ -37,45 +44,52 @@ module Savon
|
|
|
37
44
|
verify_message!
|
|
38
45
|
end
|
|
39
46
|
|
|
47
|
+
# Builds and returns a Transport::Response from the configured response hash.
|
|
48
|
+
#
|
|
49
|
+
# @return [Transport::Response]
|
|
50
|
+
# @raise [ExpectationError] if no response was configured for this expectation
|
|
40
51
|
def response!
|
|
41
52
|
unless @response
|
|
42
53
|
raise ExpectationError, "This expectation was not set up with a response."
|
|
43
54
|
end
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
Transport::Response.new(@response[:code], @response[:headers], @response[:body])
|
|
46
57
|
end
|
|
47
58
|
|
|
48
59
|
private
|
|
49
60
|
|
|
50
61
|
def verify_operation_name!
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
return if @expected[:operation_name] == @actual[:operation_name]
|
|
63
|
+
|
|
64
|
+
raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation.\n" \
|
|
65
|
+
"Received a request to the #{@actual[:operation_name].inspect} operation instead."
|
|
55
66
|
end
|
|
56
67
|
|
|
57
68
|
def verify_message!
|
|
58
69
|
return if @expected[:message].eql? :any
|
|
59
|
-
unless equals_except_any(@expected[:message], @actual[:message])
|
|
60
|
-
expected_message = " with this message: #{@expected[:message].inspect}" if @expected[:message]
|
|
61
|
-
expected_message ||= " with no message."
|
|
62
70
|
|
|
63
|
-
|
|
64
|
-
actual_message ||= " with no message."
|
|
71
|
+
return if equals_except_any(@expected[:message], @actual[:message])
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
expected_message = " with this message: #{@expected[:message].inspect}" if @expected[:message]
|
|
74
|
+
expected_message ||= " with no message."
|
|
75
|
+
|
|
76
|
+
actual_message = " with this message: #{@actual[:message].inspect}" if @actual[:message]
|
|
77
|
+
actual_message ||= " with no message."
|
|
78
|
+
|
|
79
|
+
raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation\n#{expected_message}\n" \
|
|
80
|
+
"Received a request to the #{@actual[:operation_name].inspect} operation\n#{actual_message}"
|
|
69
81
|
end
|
|
70
82
|
|
|
71
83
|
def equals_except_any(msg_expected, msg_real)
|
|
72
|
-
|
|
73
|
-
return
|
|
84
|
+
# === allows RSpec matchers (e.g. include(:key)) to be used as expected values
|
|
85
|
+
return true if msg_expected === msg_real # rubocop:disable Style/CaseEquality
|
|
86
|
+
return false if msg_expected.nil? || msg_real.nil? # If both are nil has returned true
|
|
87
|
+
|
|
74
88
|
msg_expected.each do |key, expected_value|
|
|
75
|
-
next if
|
|
89
|
+
next if expected_value == :any && msg_real.include?(key)
|
|
76
90
|
return false if expected_value != msg_real[key]
|
|
77
91
|
end
|
|
78
|
-
|
|
92
|
+
true
|
|
79
93
|
end
|
|
80
94
|
end
|
|
81
95
|
end
|