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