savon 2.16.0 → 2.17.4
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 +110 -7
- data/README.md +54 -48
- data/Rakefile +6 -2
- data/lib/savon/addressing.rb +60 -0
- data/lib/savon/block_interface.rb +3 -4
- data/lib/savon/builder.rb +54 -23
- data/lib/savon/client.rb +44 -13
- data/lib/savon/effective_options.rb +124 -0
- data/lib/savon/faraday_migration_hint.rb +186 -0
- data/lib/savon/header.rb +87 -40
- data/lib/savon/http_error.rb +8 -8
- 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 +25 -25
- data/lib/savon/operation.rb +110 -74
- data/lib/savon/options.rb +274 -157
- data/lib/savon/qualified_message.rb +2 -1
- data/lib/savon/response.rb +40 -31
- data/lib/savon/soap_fault.rb +2 -3
- data/lib/savon/string_utils.rb +3 -4
- data/lib/savon/transport/faraday.rb +101 -0
- data/lib/savon/transport/httpi.rb +138 -0
- data/lib/savon/transport/logging.rb +60 -0
- data/lib/savon/transport/response.rb +73 -0
- data/lib/savon/version.rb +2 -1
- data/lib/savon.rb +2 -3
- metadata +85 -83
- data/lib/savon/request.rb +0 -113
- data/lib/savon/request_logger.rb +0 -54
|
@@ -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
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "savon/mock"
|
|
3
4
|
|
|
4
5
|
module Savon
|
|
5
6
|
module SpecHelper
|
|
6
|
-
|
|
7
7
|
class Interface
|
|
8
|
-
|
|
9
8
|
def mock!
|
|
10
9
|
Savon.observers << self
|
|
11
10
|
end
|
|
@@ -27,14 +26,12 @@ module Savon
|
|
|
27
26
|
def notify(operation_name, builder, globals, locals)
|
|
28
27
|
expectation = expectations.shift
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
raise ExpectationError, "Unexpected request to the #{operation_name.inspect} operation." unless expectation
|
|
30
|
+
|
|
31
|
+
expectation.actual(operation_name, builder, globals, locals)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
else
|
|
36
|
-
raise ExpectationError, "Unexpected request to the #{operation_name.inspect} operation."
|
|
37
|
-
end
|
|
33
|
+
expectation.verify!
|
|
34
|
+
expectation.response!
|
|
38
35
|
rescue ExpectationError
|
|
39
36
|
@expectations.clear
|
|
40
37
|
raise
|
|
@@ -42,12 +39,12 @@ module Savon
|
|
|
42
39
|
|
|
43
40
|
def verify!
|
|
44
41
|
return if expectations.empty?
|
|
42
|
+
|
|
45
43
|
expectations.each(&:verify!)
|
|
46
44
|
rescue ExpectationError
|
|
47
45
|
@expectations.clear
|
|
48
46
|
raise
|
|
49
47
|
end
|
|
50
|
-
|
|
51
48
|
end
|
|
52
49
|
|
|
53
50
|
def savon
|
|
@@ -58,6 +55,5 @@ module Savon
|
|
|
58
55
|
super if defined? super
|
|
59
56
|
savon.verify!
|
|
60
57
|
end
|
|
61
|
-
|
|
62
58
|
end
|
|
63
59
|
end
|
data/lib/savon/mock.rb
CHANGED
data/lib/savon/model.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
module Savon
|
|
3
4
|
module Model
|
|
4
|
-
|
|
5
5
|
def self.extended(base)
|
|
6
6
|
base.setup
|
|
7
7
|
end
|
|
@@ -28,26 +28,30 @@ module Savon
|
|
|
28
28
|
|
|
29
29
|
# Defines a class-level SOAP operation.
|
|
30
30
|
def define_class_operation(operation)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
method_name = operation_method_name(operation)
|
|
32
|
+
|
|
33
|
+
class_operation_module.define_method(method_name) do |locals = {}|
|
|
34
|
+
client.call operation, locals
|
|
35
|
+
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
# Defines an instance-level SOAP operation.
|
|
39
39
|
def define_instance_operation(operation)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
method_name = operation_method_name(operation)
|
|
41
|
+
|
|
42
|
+
instance_operation_module.define_method(method_name) do |locals = {}|
|
|
43
|
+
self.class.public_send(method_name, locals)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns the generated Ruby method name for a SOAP operation.
|
|
48
|
+
def operation_method_name(operation)
|
|
49
|
+
StringUtils.snakecase(operation.to_s).to_sym
|
|
45
50
|
end
|
|
46
51
|
|
|
47
52
|
# Class methods.
|
|
48
53
|
def class_operation_module
|
|
49
|
-
@class_operation_module ||= Module.new
|
|
50
|
-
|
|
54
|
+
@class_operation_module ||= Module.new do
|
|
51
55
|
def client(globals = {})
|
|
52
56
|
@client ||= Savon::Client.new(globals)
|
|
53
57
|
rescue InitializationError
|
|
@@ -60,26 +64,22 @@ module Savon
|
|
|
60
64
|
|
|
61
65
|
def raise_initialization_error!
|
|
62
66
|
raise InitializationError,
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
"Expected the model to be initialized with either a WSDL document or the SOAP endpoint and target namespace options.\n" \
|
|
68
|
+
"Make sure to setup the model by calling the .client class method before calling the .global method.\n\n" \
|
|
69
|
+
"client(wsdl: '/Users/me/project/service.wsdl') # to use a local WSDL document\n" \
|
|
70
|
+
"client(wsdl: 'http://example.com?wsdl') # to use a remote WSDL document\n" \
|
|
71
|
+
"client(endpoint: 'http://example.com', namespace: 'http://v1.example.com') # if you don't have a WSDL document"
|
|
68
72
|
end
|
|
69
|
-
|
|
70
|
-
}.tap { |mod| extend(mod) }
|
|
73
|
+
end.tap { |mod| extend(mod) }
|
|
71
74
|
end
|
|
72
75
|
|
|
73
76
|
# Instance methods.
|
|
74
77
|
def instance_operation_module
|
|
75
|
-
@instance_operation_module ||= Module.new
|
|
76
|
-
|
|
78
|
+
@instance_operation_module ||= Module.new do
|
|
77
79
|
def client
|
|
78
80
|
self.class.client
|
|
79
81
|
end
|
|
80
|
-
|
|
81
|
-
}.tap { |mod| include(mod) }
|
|
82
|
+
end.tap { |mod| include(mod) }
|
|
82
83
|
end
|
|
83
|
-
|
|
84
84
|
end
|
|
85
85
|
end
|
data/lib/savon/operation.rb
CHANGED
|
@@ -1,52 +1,67 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
|
|
2
3
|
require "savon/options"
|
|
4
|
+
require "savon/effective_options"
|
|
3
5
|
require "savon/block_interface"
|
|
4
|
-
require "savon/request"
|
|
5
6
|
require "savon/builder"
|
|
6
7
|
require "savon/response"
|
|
7
|
-
require "savon/request_logger"
|
|
8
8
|
require "savon/http_error"
|
|
9
|
+
require "savon/transport/httpi"
|
|
10
|
+
require "savon/transport/faraday"
|
|
9
11
|
require "mail"
|
|
10
12
|
|
|
11
13
|
module Savon
|
|
14
|
+
# Represents a single named SOAP operation.
|
|
15
|
+
#
|
|
16
|
+
# Bridges the SOAP layer (envelope building, action headers, multipart) and the
|
|
17
|
+
# transport layer (execution, logging). Knows nothing about transport internals
|
|
18
|
+
# such as proxy, SSL, or auth.
|
|
12
19
|
class Operation
|
|
13
|
-
|
|
20
|
+
# SOAP Content-Type values indexed by SOAP version.
|
|
21
|
+
# SOAP 1.1 §6 (HTTP binding), SOAP 1.2 Part 2 §7.1.4 (HTTP media type)
|
|
22
|
+
CONTENT_TYPE = {
|
|
23
|
+
1 => "text/xml;charset=%s",
|
|
24
|
+
2 => "application/soap+xml;charset=%s"
|
|
25
|
+
}.freeze
|
|
26
|
+
|
|
27
|
+
# Maps SOAP version to the base MIME type used in multipart requests.
|
|
28
|
+
# RFC 2387 §3.1 (multipart/related Content-Type parameter)
|
|
14
29
|
SOAP_REQUEST_TYPE = {
|
|
15
30
|
1 => "text/xml",
|
|
16
31
|
2 => "application/soap+xml"
|
|
17
|
-
}
|
|
32
|
+
}.freeze
|
|
18
33
|
|
|
19
|
-
def self.create(operation_name, wsdl, globals)
|
|
34
|
+
def self.create(operation_name, wsdl, globals, transport)
|
|
20
35
|
if wsdl.document?
|
|
21
36
|
ensure_name_is_symbol! operation_name
|
|
22
|
-
ensure_exists! operation_name, wsdl
|
|
37
|
+
ensure_exists! operation_name, wsdl, transport
|
|
23
38
|
end
|
|
24
39
|
|
|
25
|
-
new(operation_name, wsdl, globals)
|
|
40
|
+
new(operation_name, wsdl, globals, transport)
|
|
26
41
|
end
|
|
27
42
|
|
|
28
|
-
|
|
43
|
+
# Verifies the operation is provided by the WSDL.
|
|
44
|
+
def self.ensure_exists!(operation_name, wsdl, transport)
|
|
29
45
|
unless wsdl.soap_actions.include? operation_name
|
|
30
46
|
raise UnknownOperationError, "Unable to find SOAP operation: #{operation_name.inspect}\n" \
|
|
31
47
|
"Operations provided by your service: #{wsdl.soap_actions.inspect}"
|
|
32
48
|
end
|
|
33
49
|
rescue Wasabi::Resolver::HTTPError => e
|
|
34
|
-
raise HTTPError.
|
|
50
|
+
raise HTTPError, transport.normalize_response(e.response)
|
|
35
51
|
end
|
|
36
52
|
|
|
37
53
|
def self.ensure_name_is_symbol!(operation_name)
|
|
38
|
-
|
|
39
|
-
raise ArgumentError, "Expected the first parameter (the name of the operation to call) to be a symbol\n" \
|
|
40
|
-
"Actual: #{operation_name.inspect} (#{operation_name.class})"
|
|
41
|
-
end
|
|
42
|
-
end
|
|
54
|
+
return if operation_name.is_a? Symbol
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@globals = globals
|
|
56
|
+
raise ArgumentError, "Expected the first parameter (the name of the operation to call) to be a symbol\n" \
|
|
57
|
+
"Actual: #{operation_name.inspect} (#{operation_name.class})"
|
|
58
|
+
end
|
|
48
59
|
|
|
49
|
-
|
|
60
|
+
def initialize(name, wsdl, globals, transport)
|
|
61
|
+
@name = name
|
|
62
|
+
@wsdl = wsdl
|
|
63
|
+
@globals = globals
|
|
64
|
+
@transport = transport
|
|
50
65
|
end
|
|
51
66
|
|
|
52
67
|
def build(locals = {}, &block)
|
|
@@ -54,28 +69,45 @@ module Savon
|
|
|
54
69
|
Builder.new(@name, @wsdl, @globals, @locals)
|
|
55
70
|
end
|
|
56
71
|
|
|
72
|
+
# Executes the SOAP operation and returns a Savon::Response.
|
|
73
|
+
#
|
|
74
|
+
# Observer short-circuit: if any registered observer returns a
|
|
75
|
+
# Transport::Response (or legacy HTTPI::Response), the HTTP call
|
|
76
|
+
# is skipped and that response is used directly.
|
|
57
77
|
def call(locals = {}, &block)
|
|
58
78
|
builder = build(locals, &block)
|
|
79
|
+
observer_response = Savon.notify_observers(@name, builder, @globals, @locals)
|
|
80
|
+
|
|
81
|
+
transport_response =
|
|
82
|
+
if observer_response.nil?
|
|
83
|
+
body = builder.to_s
|
|
84
|
+
headers = soap_headers(builder)
|
|
85
|
+
@transport.post(endpoint.to_s, headers, body, @locals)
|
|
86
|
+
else
|
|
87
|
+
normalize_observer_response(observer_response)
|
|
88
|
+
end
|
|
59
89
|
|
|
60
|
-
|
|
61
|
-
response ||= call_with_logging build_request(builder)
|
|
62
|
-
|
|
63
|
-
raise_expected_httpi_response! unless response.kind_of?(HTTPI::Response)
|
|
64
|
-
|
|
65
|
-
create_response(response)
|
|
90
|
+
Response.new(transport_response, @globals, @locals)
|
|
66
91
|
end
|
|
67
92
|
|
|
93
|
+
# Builds and returns the HTTPI::Request that would be sent for this
|
|
94
|
+
# operation, without executing it. Useful for inspection and debugging.
|
|
95
|
+
# Not supported with transport: :faraday.
|
|
68
96
|
def request(locals = {}, &block)
|
|
97
|
+
if @globals[:transport] == :faraday
|
|
98
|
+
raise ArgumentError, "#request returns an HTTPI::Request and is not supported " \
|
|
99
|
+
"with transport: :faraday. Use client.faraday to configure " \
|
|
100
|
+
"the connection"
|
|
101
|
+
end
|
|
102
|
+
|
|
69
103
|
builder = build(locals, &block)
|
|
70
|
-
|
|
104
|
+
# Build the body before soap_headers - see #call.
|
|
105
|
+
body = builder.to_s
|
|
106
|
+
@transport.to_httpi_request(endpoint.to_s, soap_headers(builder), body, @locals)
|
|
71
107
|
end
|
|
72
108
|
|
|
73
109
|
private
|
|
74
110
|
|
|
75
|
-
def create_response(response)
|
|
76
|
-
Response.new(response, @globals, @locals)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
111
|
def set_locals(locals, block)
|
|
80
112
|
locals = LocalOptions.new(locals)
|
|
81
113
|
BlockInterface.new(locals).evaluate(block) if block
|
|
@@ -83,65 +115,69 @@ module Savon
|
|
|
83
115
|
@locals = locals
|
|
84
116
|
end
|
|
85
117
|
|
|
86
|
-
|
|
87
|
-
|
|
118
|
+
# The effective options view for the current request. Built on demand
|
|
119
|
+
# instead of being cached, so it always reflects the current per-request
|
|
120
|
+
# options.
|
|
121
|
+
def effective_options
|
|
122
|
+
EffectiveOptions.new(@name, @wsdl, @globals, @locals)
|
|
88
123
|
end
|
|
89
124
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
request.url = endpoint
|
|
101
|
-
request.body = builder.to_s
|
|
125
|
+
# Assembles the SOAP-level request headers for the given builder.
|
|
126
|
+
#
|
|
127
|
+
# Our responsibility regardless of transport:
|
|
128
|
+
# * Content-Type (SOAP 1.1 §6 / SOAP 1.2 Part 2 §7.1.4)
|
|
129
|
+
# * SOAPAction (SOAP 1.1 §6.1.1)
|
|
130
|
+
# * Multipart Content-Type (RFC 2387), MIME-Version (RFC 2045 §4), Accept-Encoding (RFC 9110 §12.5.3)
|
|
131
|
+
def soap_headers(builder)
|
|
132
|
+
headers = {}
|
|
102
133
|
|
|
103
134
|
if builder.multipart
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
135
|
+
# RFC 2387 §3 (multipart/related) - SOAP envelope is the root body part
|
|
136
|
+
headers["Content-Type"] = [
|
|
137
|
+
"multipart/related",
|
|
138
|
+
"type=\"#{SOAP_REQUEST_TYPE[@globals[:soap_version]]}\"",
|
|
139
|
+
"start=\"#{builder.multipart[:start]}\"",
|
|
140
|
+
"boundary=\"#{builder.multipart[:multipart_boundary]}\""
|
|
141
|
+
].join("; ")
|
|
142
|
+
headers["MIME-Version"] = "1.0"
|
|
143
|
+
headers["Accept-Encoding"] = "gzip,deflate"
|
|
144
|
+
else
|
|
145
|
+
headers["Content-Type"] = CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding]
|
|
110
146
|
end
|
|
111
147
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
request.headers["Content-Length"] = request.body.bytesize.to_s
|
|
148
|
+
action = soap_action
|
|
149
|
+
headers["SOAPAction"] = %("#{action}") if action
|
|
115
150
|
|
|
116
|
-
|
|
151
|
+
headers
|
|
117
152
|
end
|
|
118
153
|
|
|
154
|
+
# The resolved SOAPAction of the request, used for the HTTP header.
|
|
155
|
+
# {Savon::EffectiveOptions#soap_action} owns the precedence rules.
|
|
119
156
|
def soap_action
|
|
120
|
-
|
|
121
|
-
return if @locals.include?(:soap_action) && !@locals[:soap_action]
|
|
122
|
-
|
|
123
|
-
# get the soap_action from local options
|
|
124
|
-
@locals[:soap_action] ||
|
|
125
|
-
# with no local option, but a wsdl, ask it for the soap_action
|
|
126
|
-
@wsdl.document? && @wsdl.soap_action(@name.to_sym) ||
|
|
127
|
-
# if there is no soap_action up to this point, fallback to a simple default
|
|
128
|
-
Gyoku.xml_tag(@name, :key_converter => @globals[:convert_request_keys_to])
|
|
157
|
+
effective_options.soap_action
|
|
129
158
|
end
|
|
130
159
|
|
|
160
|
+
# The resolved endpoint URL the request is sent to.
|
|
161
|
+
# {Savon::EffectiveOptions#endpoint} owns the precedence rules.
|
|
131
162
|
def endpoint
|
|
132
|
-
|
|
133
|
-
if @globals[:host]
|
|
134
|
-
host_url = URI.parse(@globals[:host])
|
|
135
|
-
url.host = host_url.host
|
|
136
|
-
url.port = host_url.port
|
|
137
|
-
end
|
|
138
|
-
end
|
|
163
|
+
effective_options.endpoint
|
|
139
164
|
end
|
|
140
165
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
166
|
+
# Normalizes an observer return value into a Transport::Response.
|
|
167
|
+
#
|
|
168
|
+
# Accepts Transport::Response directly (current contract), wraps
|
|
169
|
+
# HTTPI::Response with a deprecation warning (legacy observer support),
|
|
170
|
+
# and raises on anything else.
|
|
171
|
+
def normalize_observer_response(observer_response)
|
|
172
|
+
return observer_response if observer_response.is_a?(Transport::Response)
|
|
173
|
+
|
|
174
|
+
if observer_response.is_a?(HTTPI::Response)
|
|
175
|
+
warn "Observers returning HTTPI::Response is deprecated - return Savon::Transport::Response instead.", uplevel: 1
|
|
176
|
+
return Transport::Response.from_httpi(observer_response)
|
|
177
|
+
end
|
|
145
178
|
|
|
179
|
+
raise Error, "Observers need to return a Savon::Transport::Response " \
|
|
180
|
+
"to mock the request or nil to execute the request."
|
|
181
|
+
end
|
|
146
182
|
end
|
|
147
183
|
end
|