savon 2.17.3 → 3.0.0.rc1
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 +17 -99
- data/README.md +52 -52
- data/Rakefile +2 -6
- data/lib/savon/block_interface.rb +4 -3
- data/lib/savon/builder.rb +33 -34
- data/lib/savon/client.rb +13 -44
- data/lib/savon/header.rb +12 -12
- data/lib/savon/http_error.rb +9 -9
- data/lib/savon/log_message.rb +3 -2
- data/lib/savon/message.rb +7 -6
- data/lib/savon/mock/expectation.rb +24 -38
- data/lib/savon/mock/spec_helper.rb +11 -7
- data/lib/savon/mock.rb +0 -1
- data/lib/savon/model.rb +25 -25
- data/lib/savon/operation.rb +82 -104
- data/lib/savon/options.rb +175 -276
- data/lib/savon/qualified_message.rb +1 -2
- data/lib/savon/request.rb +147 -0
- data/lib/savon/request_logger.rb +57 -0
- data/lib/savon/response.rb +31 -45
- data/lib/savon/soap_fault.rb +6 -6
- data/lib/savon/string_utils.rb +4 -3
- data/lib/savon/version.rb +1 -2
- data/lib/savon.rb +11 -2
- metadata +130 -69
- data/lib/savon/faraday_migration_hint.rb +0 -186
- data/lib/savon/transport/faraday.rb +0 -101
- data/lib/savon/transport/httpi.rb +0 -138
- data/lib/savon/transport/logging.rb +0 -60
- data/lib/savon/transport/response.rb +0 -73
data/lib/savon/header.rb
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "akami"
|
|
4
3
|
require "gyoku"
|
|
5
4
|
require "securerandom"
|
|
6
5
|
|
|
7
6
|
module Savon
|
|
8
7
|
class Header
|
|
8
|
+
|
|
9
9
|
def initialize(globals, locals)
|
|
10
|
-
@gyoku_options = { key_converter
|
|
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.kind_of?(Hash) && local_header.kind_of?(Hash)
|
|
45
45
|
global_header.merge(local_header)
|
|
46
46
|
elsif local_header
|
|
47
47
|
local_header
|
|
@@ -58,17 +58,16 @@ module Savon
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def build_wsa_header
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
})
|
|
61
|
+
return '' unless @globals[:use_wsa_headers]
|
|
62
|
+
convert_to_xml({
|
|
63
|
+
'wsa:Action' => @locals[:soap_action],
|
|
64
|
+
'wsa:To' => @globals[:endpoint],
|
|
65
|
+
'wsa:MessageID' => "urn:uuid:#{SecureRandom.uuid}"
|
|
66
|
+
})
|
|
68
67
|
end
|
|
69
68
|
|
|
70
69
|
def convert_to_xml(hash_or_string)
|
|
71
|
-
if hash_or_string.
|
|
70
|
+
if hash_or_string.kind_of? Hash
|
|
72
71
|
Gyoku.xml(hash_or_string, gyoku_options)
|
|
73
72
|
else
|
|
74
73
|
hash_or_string.to_s
|
|
@@ -79,11 +78,12 @@ module Savon
|
|
|
79
78
|
wsse = Akami.wsse
|
|
80
79
|
wsse.credentials(*wsse_auth) if wsse_auth
|
|
81
80
|
wsse.timestamp = wsse_timestamp if wsse_timestamp
|
|
82
|
-
if wsse_signature
|
|
81
|
+
if wsse_signature && wsse_signature.have_document?
|
|
83
82
|
wsse.signature = wsse_signature
|
|
84
83
|
end
|
|
85
84
|
|
|
86
85
|
wsse
|
|
87
86
|
end
|
|
87
|
+
|
|
88
88
|
end
|
|
89
89
|
end
|
data/lib/savon/http_error.rb
CHANGED
|
@@ -2,26 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module Savon
|
|
4
4
|
class HTTPError < Error
|
|
5
|
+
|
|
5
6
|
def self.present?(http)
|
|
6
|
-
http.
|
|
7
|
+
!http.success?
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
def initialize(
|
|
10
|
-
@
|
|
10
|
+
def initialize(http)
|
|
11
|
+
@http = http
|
|
11
12
|
end
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
@transport_response
|
|
15
|
-
end
|
|
14
|
+
attr_reader :http
|
|
16
15
|
|
|
17
16
|
def to_s
|
|
18
|
-
String.new("HTTP error (#{http.
|
|
19
|
-
str_error << ": #{http.body}" unless http.body.empty?
|
|
17
|
+
String.new("HTTP error (#{@http.status})").tap do |str_error|
|
|
18
|
+
str_error << ": #{@http.body}" unless @http.body.empty?
|
|
20
19
|
end
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def to_hash
|
|
24
|
-
{ code
|
|
23
|
+
{ :code => @http.status, :headers => @http.headers, :body => @http.body }
|
|
25
24
|
end
|
|
25
|
+
|
|
26
26
|
end
|
|
27
27
|
end
|
data/lib/savon/log_message.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "nokogiri"
|
|
4
3
|
|
|
5
4
|
module Savon
|
|
6
5
|
class LogMessage
|
|
6
|
+
|
|
7
7
|
def initialize(message, filters = [], pretty_print = false)
|
|
8
8
|
@message = message
|
|
9
9
|
@filters = filters
|
|
@@ -46,7 +46,8 @@ module Savon
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
def nokogiri_options
|
|
49
|
-
@pretty_print ? { indent
|
|
49
|
+
@pretty_print ? { :indent => 2 } : { :save_with => Nokogiri::XML::Node::SaveOptions::AS_XML }
|
|
50
50
|
end
|
|
51
|
+
|
|
51
52
|
end
|
|
52
53
|
end
|
data/lib/savon/message.rb
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "savon/qualified_message"
|
|
4
3
|
require "gyoku"
|
|
5
4
|
|
|
6
5
|
module Savon
|
|
7
6
|
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,20 +18,21 @@ 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.kind_of? 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
|
-
element_form_default
|
|
29
|
-
namespace
|
|
30
|
-
key_converter
|
|
31
|
-
unwrap
|
|
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
|
+
|
|
36
37
|
end
|
|
37
38
|
end
|
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "httpi"
|
|
4
|
-
require "savon/transport/response"
|
|
2
|
+
require "faraday"
|
|
5
3
|
|
|
6
4
|
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.
|
|
13
5
|
class MockExpectation
|
|
6
|
+
|
|
14
7
|
def initialize(operation_name)
|
|
15
|
-
@expected = { operation_name
|
|
8
|
+
@expected = { :operation_name => operation_name }
|
|
16
9
|
@actual = nil
|
|
17
10
|
end
|
|
18
11
|
|
|
@@ -22,15 +15,15 @@ module Savon
|
|
|
22
15
|
end
|
|
23
16
|
|
|
24
17
|
def returns(response)
|
|
25
|
-
response = { code
|
|
18
|
+
response = { :code => 200, :headers => {}, :body => response } if response.kind_of?(String)
|
|
26
19
|
@response = response
|
|
27
20
|
self
|
|
28
21
|
end
|
|
29
22
|
|
|
30
|
-
def actual(operation_name,
|
|
23
|
+
def actual(operation_name, builder, globals, locals)
|
|
31
24
|
@actual = {
|
|
32
|
-
operation_name
|
|
33
|
-
message
|
|
25
|
+
:operation_name => operation_name,
|
|
26
|
+
:message => locals[:message]
|
|
34
27
|
}
|
|
35
28
|
end
|
|
36
29
|
|
|
@@ -44,49 +37,42 @@ module Savon
|
|
|
44
37
|
verify_message!
|
|
45
38
|
end
|
|
46
39
|
|
|
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
|
|
51
40
|
def response!
|
|
52
41
|
unless @response
|
|
53
42
|
raise ExpectationError, "This expectation was not set up with a response."
|
|
54
43
|
end
|
|
55
|
-
|
|
56
|
-
|
|
44
|
+
env = Faraday::Env.from(status: @response[:code], response_headers: @response[:headers], response_body: @response[:body])
|
|
45
|
+
Faraday::Response.new(env)
|
|
57
46
|
end
|
|
58
47
|
|
|
59
48
|
private
|
|
60
49
|
|
|
61
50
|
def verify_operation_name!
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
unless @expected[:operation_name] == @actual[:operation_name]
|
|
52
|
+
raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation.\n" \
|
|
53
|
+
"Received a request to the #{@actual[:operation_name].inspect} operation instead."
|
|
54
|
+
end
|
|
66
55
|
end
|
|
67
56
|
|
|
68
57
|
def verify_message!
|
|
69
58
|
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."
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
actual_message = " with this message: #{@actual[:message].inspect}" if @actual[:message]
|
|
64
|
+
actual_message ||= " with no message."
|
|
72
65
|
|
|
73
|
-
|
|
74
|
-
|
|
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}"
|
|
66
|
+
raise ExpectationError, "Expected a request to the #{@expected[:operation_name].inspect} operation\n#{expected_message}\n" \
|
|
67
|
+
"Received a request to the #{@actual[:operation_name].inspect} operation\n#{actual_message}"
|
|
68
|
+
end
|
|
81
69
|
end
|
|
82
70
|
|
|
83
71
|
def equals_except_any(msg_expected, msg_real)
|
|
84
|
-
|
|
85
|
-
return
|
|
86
|
-
return false if msg_expected.nil? || msg_real.nil? # If both are nil has returned true
|
|
87
|
-
|
|
72
|
+
return true if msg_expected === msg_real
|
|
73
|
+
return false if (msg_expected.nil? || msg_real.nil?) # If both are nil has returned true
|
|
88
74
|
msg_expected.each do |key, expected_value|
|
|
89
|
-
next if expected_value == :any &&
|
|
75
|
+
next if (expected_value == :any && msg_real.include?(key))
|
|
90
76
|
return false if expected_value != msg_real[key]
|
|
91
77
|
end
|
|
92
78
|
true
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
require "savon/mock"
|
|
4
3
|
|
|
5
4
|
module Savon
|
|
6
5
|
module SpecHelper
|
|
6
|
+
|
|
7
7
|
class Interface
|
|
8
|
+
|
|
8
9
|
def mock!
|
|
9
10
|
Savon.observers << self
|
|
10
11
|
end
|
|
@@ -26,12 +27,14 @@ module Savon
|
|
|
26
27
|
def notify(operation_name, builder, globals, locals)
|
|
27
28
|
expectation = expectations.shift
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
expectation.actual(operation_name, builder, globals, locals)
|
|
30
|
+
if expectation
|
|
31
|
+
expectation.actual(operation_name, builder, globals, locals)
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
expectation.verify!
|
|
34
|
+
expectation.response!
|
|
35
|
+
else
|
|
36
|
+
raise ExpectationError, "Unexpected request to the #{operation_name.inspect} operation."
|
|
37
|
+
end
|
|
35
38
|
rescue ExpectationError
|
|
36
39
|
@expectations.clear
|
|
37
40
|
raise
|
|
@@ -39,12 +42,12 @@ module Savon
|
|
|
39
42
|
|
|
40
43
|
def verify!
|
|
41
44
|
return if expectations.empty?
|
|
42
|
-
|
|
43
45
|
expectations.each(&:verify!)
|
|
44
46
|
rescue ExpectationError
|
|
45
47
|
@expectations.clear
|
|
46
48
|
raise
|
|
47
49
|
end
|
|
50
|
+
|
|
48
51
|
end
|
|
49
52
|
|
|
50
53
|
def savon
|
|
@@ -55,5 +58,6 @@ module Savon
|
|
|
55
58
|
super if defined? super
|
|
56
59
|
savon.verify!
|
|
57
60
|
end
|
|
61
|
+
|
|
58
62
|
end
|
|
59
63
|
end
|
data/lib/savon/mock.rb
CHANGED
data/lib/savon/model.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
-
|
|
3
2
|
module Savon
|
|
4
3
|
module Model
|
|
4
|
+
|
|
5
5
|
def self.extended(base)
|
|
6
6
|
base.setup
|
|
7
7
|
end
|
|
@@ -28,30 +28,26 @@ 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
|
+
class_operation_module.module_eval %{
|
|
32
|
+
def #{StringUtils.snakecase(operation.to_s)}(locals = {})
|
|
33
|
+
client.call #{operation.inspect}, locals
|
|
34
|
+
end
|
|
35
|
+
}
|
|
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
|
-
|
|
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
|
|
40
|
+
instance_operation_module.module_eval %{
|
|
41
|
+
def #{StringUtils.snakecase(operation.to_s)}(locals = {})
|
|
42
|
+
self.class.#{StringUtils.snakecase(operation.to_s)} locals
|
|
43
|
+
end
|
|
44
|
+
}
|
|
50
45
|
end
|
|
51
46
|
|
|
52
47
|
# Class methods.
|
|
53
48
|
def class_operation_module
|
|
54
|
-
@class_operation_module ||= Module.new
|
|
49
|
+
@class_operation_module ||= Module.new {
|
|
50
|
+
|
|
55
51
|
def client(globals = {})
|
|
56
52
|
@client ||= Savon::Client.new(globals)
|
|
57
53
|
rescue InitializationError
|
|
@@ -64,22 +60,26 @@ module Savon
|
|
|
64
60
|
|
|
65
61
|
def raise_initialization_error!
|
|
66
62
|
raise InitializationError,
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
"Expected the model to be initialized with either a WSDL document or the SOAP endpoint and target namespace options.\n" \
|
|
64
|
+
"Make sure to setup the model by calling the .client class method before calling the .global method.\n\n" \
|
|
65
|
+
"client(wsdl: '/Users/me/project/service.wsdl') # to use a local WSDL document\n" \
|
|
66
|
+
"client(wsdl: 'http://example.com?wsdl') # to use a remote WSDL document\n" \
|
|
67
|
+
"client(endpoint: 'http://example.com', namespace: 'http://v1.example.com') # if you don't have a WSDL document"
|
|
72
68
|
end
|
|
73
|
-
|
|
69
|
+
|
|
70
|
+
}.tap { |mod| extend(mod) }
|
|
74
71
|
end
|
|
75
72
|
|
|
76
73
|
# Instance methods.
|
|
77
74
|
def instance_operation_module
|
|
78
|
-
@instance_operation_module ||= Module.new
|
|
75
|
+
@instance_operation_module ||= Module.new {
|
|
76
|
+
|
|
79
77
|
def client
|
|
80
78
|
self.class.client
|
|
81
79
|
end
|
|
82
|
-
|
|
80
|
+
|
|
81
|
+
}.tap { |mod| include(mod) }
|
|
83
82
|
end
|
|
83
|
+
|
|
84
84
|
end
|
|
85
85
|
end
|