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.
- data/CHANGELOG.md +119 -104
- data/README.md +12 -11
- data/Rakefile +0 -6
- data/lib/savon.rb +16 -14
- data/lib/savon/block_interface.rb +26 -0
- data/lib/savon/builder.rb +142 -0
- data/lib/savon/client.rb +36 -135
- data/lib/savon/header.rb +42 -0
- data/lib/savon/http_error.rb +27 -0
- data/lib/savon/log_message.rb +23 -25
- data/lib/savon/message.rb +35 -0
- data/lib/savon/mock.rb +5 -0
- data/lib/savon/mock/expectation.rb +70 -0
- data/lib/savon/mock/spec_helper.rb +62 -0
- data/lib/savon/model.rb +39 -61
- data/lib/savon/operation.rb +62 -0
- data/lib/savon/options.rb +265 -0
- data/lib/savon/qualified_message.rb +49 -0
- data/lib/savon/request.rb +92 -0
- data/lib/savon/response.rb +97 -0
- data/lib/savon/soap_fault.rb +40 -0
- data/lib/savon/version.rb +1 -1
- data/savon.gemspec +10 -8
- data/spec/integration/options_spec.rb +536 -0
- data/spec/integration/request_spec.rb +31 -16
- data/spec/integration/support/application.rb +80 -0
- data/spec/integration/support/server.rb +84 -0
- data/spec/savon/builder_spec.rb +81 -0
- data/spec/savon/client_spec.rb +90 -488
- data/spec/savon/http_error_spec.rb +49 -0
- data/spec/savon/log_message_spec.rb +33 -0
- data/spec/savon/mock_spec.rb +127 -0
- data/spec/savon/model_spec.rb +110 -99
- data/spec/savon/observers_spec.rb +92 -0
- data/spec/savon/operation_spec.rb +49 -0
- data/spec/savon/request_spec.rb +145 -0
- data/spec/savon/{soap/response_spec.rb → response_spec.rb} +22 -59
- data/spec/savon/soap_fault_spec.rb +94 -0
- data/spec/spec_helper.rb +5 -3
- data/spec/support/fixture.rb +5 -1
- metadata +202 -197
- data/lib/savon/config.rb +0 -46
- data/lib/savon/error.rb +0 -6
- data/lib/savon/hooks/group.rb +0 -68
- data/lib/savon/hooks/hook.rb +0 -61
- data/lib/savon/http/error.rb +0 -42
- data/lib/savon/logger.rb +0 -39
- data/lib/savon/null_logger.rb +0 -10
- data/lib/savon/soap.rb +0 -21
- data/lib/savon/soap/fault.rb +0 -59
- data/lib/savon/soap/invalid_response_error.rb +0 -13
- data/lib/savon/soap/request.rb +0 -86
- data/lib/savon/soap/request_builder.rb +0 -205
- data/lib/savon/soap/response.rb +0 -117
- data/lib/savon/soap/xml.rb +0 -257
- data/spec/savon/config_spec.rb +0 -38
- data/spec/savon/hooks/group_spec.rb +0 -71
- data/spec/savon/hooks/hook_spec.rb +0 -16
- data/spec/savon/http/error_spec.rb +0 -52
- data/spec/savon/logger_spec.rb +0 -51
- data/spec/savon/savon_spec.rb +0 -33
- data/spec/savon/soap/fault_spec.rb +0 -89
- data/spec/savon/soap/request_builder_spec.rb +0 -207
- data/spec/savon/soap/request_spec.rb +0 -112
- data/spec/savon/soap/xml_spec.rb +0 -357
- data/spec/savon/soap_spec.rb +0 -16
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Savon [](http://travis-ci.org/savonrb/savon)
|
2
2
|
=====
|
3
3
|
|
4
4
|
Heavy metal SOAP client
|
@@ -6,13 +6,14 @@ Heavy metal SOAP client
|
|
6
6
|
[Documentation](http://savonrb.com) | [RDoc](http://rubydoc.info/gems/savon) |
|
7
7
|
[Mailing list](https://groups.google.com/forum/#!forum/savonrb) | [Twitter](http://twitter.com/savonrb)
|
8
8
|
|
9
|
-
|
10
|
-
|
9
|
+
Version 2
|
10
|
+
---------
|
11
11
|
|
12
|
-
Savon is
|
12
|
+
Savon 2.0 is almost feature-complete and I would really appreciate your feedback!
|
13
|
+
To get started, add the following line to your Gemfile:
|
13
14
|
|
14
|
-
```
|
15
|
-
|
15
|
+
``` ruby
|
16
|
+
gem "savon", github: "savonrb/savon", branch: "version2"
|
16
17
|
```
|
17
18
|
|
18
19
|
Introduction
|
@@ -22,14 +23,14 @@ Introduction
|
|
22
23
|
require "savon"
|
23
24
|
|
24
25
|
# create a client for your SOAP service
|
25
|
-
client = Savon.client("http://service.example.com?wsdl")
|
26
|
+
client = Savon.client(wsdl: "http://service.example.com?wsdl")
|
26
27
|
|
27
|
-
client.
|
28
|
+
client.operations
|
28
29
|
# => [:create_user, :get_user, :get_all_users]
|
29
30
|
|
30
31
|
# execute a SOAP request to call the "getUser" action
|
31
|
-
response = client.
|
32
|
-
|
32
|
+
response = client.call(:get_user) do
|
33
|
+
message(user_id: 1)
|
33
34
|
end
|
34
35
|
|
35
36
|
response.body
|
@@ -39,4 +40,4 @@ response.body
|
|
39
40
|
Documentation
|
40
41
|
-------------
|
41
42
|
|
42
|
-
Continue reading at [savonrb.com](http://savonrb.com)
|
43
|
+
Continue reading at [savonrb.com](http://savonrb.com/version2.html)
|
data/Rakefile
CHANGED
@@ -10,11 +10,5 @@ RSpec::Core::RakeTask.new "spec:integration" do |t|
|
|
10
10
|
t.pattern = "spec/integration/**/*_spec.rb"
|
11
11
|
end
|
12
12
|
|
13
|
-
# http://stackoverflow.com/q/5771758/279024
|
14
|
-
task :require_ruby_18 do
|
15
|
-
raise "This must be run on Ruby 1.8" unless RUBY_VERSION =~ /^1\.8/
|
16
|
-
end
|
17
|
-
task :release => [:require_ruby_18]
|
18
|
-
|
19
13
|
task :default => :spec
|
20
14
|
task :test => :spec
|
data/lib/savon.rb
CHANGED
@@ -1,23 +1,25 @@
|
|
1
|
-
require "savon/version"
|
2
|
-
require "savon/config"
|
3
|
-
require "savon/client"
|
4
|
-
require "savon/model"
|
5
|
-
|
6
1
|
module Savon
|
7
|
-
extend self
|
8
2
|
|
9
|
-
|
10
|
-
|
11
|
-
end
|
3
|
+
class Error < RuntimeError; end
|
4
|
+
class InitializationError < Error; end
|
5
|
+
class InvalidResponseError < Error; end
|
12
6
|
|
13
|
-
def
|
14
|
-
|
7
|
+
def self.client(globals = {}, &block)
|
8
|
+
Client.new(globals, &block)
|
15
9
|
end
|
16
10
|
|
17
|
-
def
|
18
|
-
@
|
11
|
+
def self.observers
|
12
|
+
@observers ||= []
|
19
13
|
end
|
20
14
|
|
21
|
-
|
15
|
+
def self.notify_observers(operation_name, builder, globals, locals)
|
16
|
+
observers.inject(nil) do |response, observer|
|
17
|
+
observer.notify(operation_name, builder, globals, locals)
|
18
|
+
end
|
19
|
+
end
|
22
20
|
|
23
21
|
end
|
22
|
+
|
23
|
+
require "savon/version"
|
24
|
+
require "savon/client"
|
25
|
+
require "savon/model"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Savon
|
2
|
+
class BlockInterface
|
3
|
+
|
4
|
+
def initialize(target)
|
5
|
+
@target = target
|
6
|
+
end
|
7
|
+
|
8
|
+
def evaluate(block)
|
9
|
+
if block.arity > 0
|
10
|
+
block.call(@target)
|
11
|
+
else
|
12
|
+
@original = eval("self", block.binding)
|
13
|
+
instance_eval(&block)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def method_missing(method, *args, &block)
|
20
|
+
@target.send(method, *args, &block)
|
21
|
+
rescue NoMethodError
|
22
|
+
@original.send(method, *args, &block)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "savon/header"
|
2
|
+
require "savon/message"
|
3
|
+
require "builder"
|
4
|
+
require "gyoku"
|
5
|
+
|
6
|
+
module Savon
|
7
|
+
class Builder
|
8
|
+
|
9
|
+
SCHEMA_TYPES = {
|
10
|
+
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
11
|
+
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
|
12
|
+
}
|
13
|
+
|
14
|
+
SOAP_NAMESPACE = {
|
15
|
+
1 => "http://schemas.xmlsoap.org/soap/envelope/",
|
16
|
+
2 => "http://www.w3.org/2003/05/soap-envelope"
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize(operation_name, wsdl, globals, locals)
|
20
|
+
@operation_name = operation_name
|
21
|
+
|
22
|
+
@wsdl = wsdl
|
23
|
+
@globals = globals
|
24
|
+
@locals = locals
|
25
|
+
|
26
|
+
@types = convert_type_definitions_to_hash
|
27
|
+
@used_namespaces = convert_type_namespaces_to_hash
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_s
|
31
|
+
return @locals[:xml] if @locals.include? :xml
|
32
|
+
|
33
|
+
tag(builder, :Envelope, namespaces) do |xml|
|
34
|
+
tag(xml, :Header) { xml << header.to_s } unless header.empty?
|
35
|
+
tag(xml, :Body) { xml.tag!(*namespaced_message_tag) { xml << message.to_s } }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def convert_type_definitions_to_hash
|
42
|
+
@wsdl.type_definitions.inject({}) do |memo, (path, type)|
|
43
|
+
memo[path] = type
|
44
|
+
memo
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def convert_type_namespaces_to_hash
|
49
|
+
@wsdl.type_namespaces.inject({}) do |memo, (path, uri)|
|
50
|
+
key, value = use_namespace(path, uri)
|
51
|
+
memo[key] = value
|
52
|
+
memo
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def use_namespace(path, uri)
|
57
|
+
@internal_namespace_count ||= 0
|
58
|
+
|
59
|
+
unless identifier = namespace_by_uri(uri)
|
60
|
+
identifier = "ins#{@internal_namespace_count}"
|
61
|
+
namespaces["xmlns:#{identifier}"] = uri
|
62
|
+
@internal_namespace_count += 1
|
63
|
+
end
|
64
|
+
|
65
|
+
[path, identifier]
|
66
|
+
end
|
67
|
+
|
68
|
+
def namespaces
|
69
|
+
@namespaces ||= begin
|
70
|
+
namespaces = SCHEMA_TYPES.dup
|
71
|
+
namespaces["xmlns:#{namespace_identifier}"] = @globals[:namespace] || @wsdl.namespace
|
72
|
+
|
73
|
+
key = ["xmlns"]
|
74
|
+
key << env_namespace if env_namespace && env_namespace != ""
|
75
|
+
namespaces[key.join(":")] = SOAP_NAMESPACE[@globals[:soap_version]]
|
76
|
+
|
77
|
+
namespaces
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def env_namespace
|
82
|
+
@env_namespace ||= @globals[:env_namespace] || :env
|
83
|
+
end
|
84
|
+
|
85
|
+
def header
|
86
|
+
@header ||= Header.new(@globals, @locals)
|
87
|
+
end
|
88
|
+
|
89
|
+
def namespaced_message_tag
|
90
|
+
return [namespace_identifier, message_tag] unless @used_namespaces[[@operation_name.to_s]]
|
91
|
+
[@used_namespaces[[@operation_name.to_s]], message_tag]
|
92
|
+
end
|
93
|
+
|
94
|
+
def message_tag
|
95
|
+
message_tag = @locals[:message_tag]
|
96
|
+
message_tag ||= @wsdl.soap_input(@operation_name.to_sym) if @wsdl.document?
|
97
|
+
message_tag ||= Gyoku.xml_tag(@operation_name, :key_converter => @globals[:convert_request_keys_to])
|
98
|
+
|
99
|
+
@message_tag = message_tag.to_sym
|
100
|
+
end
|
101
|
+
|
102
|
+
def message
|
103
|
+
element_form_default = @globals[:element_form_default] || @wsdl.element_form_default
|
104
|
+
# TODO: clean this up! [dh, 2012-12-17]
|
105
|
+
Message.new(@operation_name, namespace_identifier, @types, @used_namespaces, @locals[:message],
|
106
|
+
element_form_default, @globals[:convert_request_keys_to])
|
107
|
+
end
|
108
|
+
|
109
|
+
def namespace_identifier
|
110
|
+
return @globals[:namespace_identifier] if @globals.include? :namespace_identifier
|
111
|
+
return @namespace_identifier if @namespace_identifier
|
112
|
+
|
113
|
+
operation = @wsdl.operations[@operation_name] if @wsdl.document?
|
114
|
+
namespace_identifier = operation[:namespace_identifier] if operation
|
115
|
+
namespace_identifier ||= "wsdl"
|
116
|
+
|
117
|
+
@namespace_identifier = namespace_identifier.to_sym
|
118
|
+
end
|
119
|
+
|
120
|
+
def namespace_by_uri(uri)
|
121
|
+
namespaces.each do |candidate_identifier, candidate_uri|
|
122
|
+
return candidate_identifier.gsub(/^xmlns:/, '') if candidate_uri == uri
|
123
|
+
end
|
124
|
+
nil
|
125
|
+
end
|
126
|
+
|
127
|
+
def builder
|
128
|
+
builder = ::Builder::XmlMarkup.new
|
129
|
+
builder.instruct!(:xml, :encoding => @globals[:encoding])
|
130
|
+
builder
|
131
|
+
end
|
132
|
+
|
133
|
+
def tag(xml, name, namespaces = {}, &block)
|
134
|
+
if env_namespace && env_namespace != ""
|
135
|
+
xml.tag! env_namespace, name, namespaces, &block
|
136
|
+
else
|
137
|
+
xml.tag! name, namespaces, &block
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
data/lib/savon/client.rb
CHANGED
@@ -1,162 +1,63 @@
|
|
1
|
-
require "
|
2
|
-
require "
|
3
|
-
require "
|
4
|
-
|
5
|
-
require "savon/soap/xml"
|
6
|
-
require "savon/soap/request"
|
7
|
-
require "savon/soap/response"
|
8
|
-
require "savon/soap/request_builder"
|
1
|
+
require "savon/operation"
|
2
|
+
require "savon/options"
|
3
|
+
require "savon/block_interface"
|
4
|
+
require "wasabi"
|
9
5
|
|
10
6
|
module Savon
|
11
|
-
|
12
|
-
# = Savon::Client
|
13
|
-
#
|
14
|
-
# Savon::Client is the main object for connecting to a SOAP service.
|
15
7
|
class Client
|
16
8
|
|
17
|
-
|
18
|
-
|
19
|
-
#
|
20
|
-
# == Examples
|
21
|
-
#
|
22
|
-
# # Using a remote WSDL
|
23
|
-
# client = Savon::Client.new("http://example.com/UserService?wsdl")
|
24
|
-
#
|
25
|
-
# # Using a local WSDL
|
26
|
-
# client = Savon::Client.new File.expand_path("../wsdl/service.xml", __FILE__)
|
27
|
-
#
|
28
|
-
# # Directly accessing a SOAP endpoint
|
29
|
-
# client = Savon::Client.new do
|
30
|
-
# wsdl.endpoint = "http://example.com/UserService"
|
31
|
-
# wsdl.namespace = "http://users.example.com"
|
32
|
-
# end
|
33
|
-
def initialize(wsdl_document = nil, &block)
|
34
|
-
self.config = Savon.config.clone
|
35
|
-
wsdl.document = wsdl_document if wsdl_document
|
9
|
+
def initialize(globals = {}, &block)
|
10
|
+
@globals = GlobalOptions.new(globals)
|
36
11
|
|
37
|
-
|
38
|
-
wsdl.request = http
|
39
|
-
end
|
12
|
+
BlockInterface.new(@globals).evaluate(block) if block
|
40
13
|
|
41
|
-
|
42
|
-
|
14
|
+
unless wsdl_or_endpoint_and_namespace_specified?
|
15
|
+
raise_initialization_error!
|
16
|
+
end
|
43
17
|
|
44
|
-
|
45
|
-
|
46
|
-
@wsdl
|
18
|
+
@wsdl = Wasabi::Document.new
|
19
|
+
@wsdl.document = @globals[:wsdl] if @globals.include? :wsdl
|
20
|
+
@wsdl.endpoint = @globals[:endpoint] if @globals.include? :endpoint
|
21
|
+
@wsdl.namespace = @globals[:namespace] if @globals.include? :namespace
|
47
22
|
end
|
48
23
|
|
49
|
-
|
50
|
-
def http
|
51
|
-
@http ||= HTTPI::Request.new
|
52
|
-
end
|
24
|
+
attr_reader :globals
|
53
25
|
|
54
|
-
|
55
|
-
|
56
|
-
@
|
26
|
+
def operations
|
27
|
+
raise_missing_wsdl_error! unless @wsdl.document?
|
28
|
+
@wsdl.soap_actions
|
57
29
|
end
|
58
30
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
# == Examples
|
64
|
-
#
|
65
|
-
# # Calls a "getUser" SOAP action with the payload of "<userId>123</userId>"
|
66
|
-
# client.request(:get_user) { soap.body = { :user_id => 123 } }
|
67
|
-
#
|
68
|
-
# # Prefixes the SOAP input tag with a given namespace: "<wsdl:GetUser>...</wsdl:GetUser>"
|
69
|
-
# client.request(:wsdl, "GetUser") { soap.body = { :user_id => 123 } }
|
70
|
-
#
|
71
|
-
# # SOAP input tag with attributes: <getUser xmlns:wsdl="http://example.com">...</getUser>"
|
72
|
-
# client.request(:get_user, "xmlns:wsdl" => "http://example.com")
|
73
|
-
def request(*args, &block)
|
74
|
-
raise ArgumentError, "Savon::Client#request requires at least one argument" if args.empty?
|
75
|
-
|
76
|
-
options = extract_options(args)
|
77
|
-
|
78
|
-
request_builder = SOAP::RequestBuilder.new(options.delete(:input), options)
|
79
|
-
request_builder.wsdl = wsdl
|
80
|
-
request_builder.http = http.dup
|
81
|
-
request_builder.wsse = wsse.dup
|
82
|
-
request_builder.config = config.dup
|
83
|
-
|
84
|
-
post_configuration = lambda { process(0, request_builder, &block) if block }
|
85
|
-
|
86
|
-
response = request_builder.request(&post_configuration).response
|
87
|
-
http.set_cookies(response.http)
|
88
|
-
|
89
|
-
if wsse.verify_response
|
90
|
-
WSSE::VerifySignature.new(response.http.body).verify!
|
91
|
-
end
|
31
|
+
def operation(operation_name)
|
32
|
+
Operation.create(operation_name, @wsdl, @globals)
|
33
|
+
end
|
92
34
|
|
35
|
+
def call(operation_name, locals = {}, &block)
|
36
|
+
response = operation(operation_name).call(locals, &block)
|
37
|
+
persist_last_response(response)
|
93
38
|
response
|
94
39
|
end
|
95
40
|
|
96
41
|
private
|
97
42
|
|
98
|
-
|
99
|
-
|
100
|
-
# the SOAP body (might be +nil+), and a Hash of attributes for the input
|
101
|
-
# tag (which might be empty).
|
102
|
-
def extract_options(args)
|
103
|
-
attributes = Hash === args.last ? args.pop : {}
|
104
|
-
body = attributes.delete(:body)
|
105
|
-
soap_action = attributes.delete(:soap_action)
|
106
|
-
|
107
|
-
namespace_identifier = args.size > 1 ? args.shift.to_sym : nil
|
108
|
-
input = args.first
|
109
|
-
|
110
|
-
remove_blank_values(
|
111
|
-
:namespace_identifier => namespace_identifier,
|
112
|
-
:input => input,
|
113
|
-
:attributes => attributes,
|
114
|
-
:body => body,
|
115
|
-
:soap_action => soap_action
|
116
|
-
)
|
117
|
-
end
|
118
|
-
|
119
|
-
# Processes a given +block+. Yields objects if the block expects any arguments.
|
120
|
-
# Otherwise evaluates the block in the context of +instance+.
|
121
|
-
def process(offset = 0, instance = self, &block)
|
122
|
-
block.arity > 0 ? yield_objects(offset, instance, &block) : evaluate(instance, &block)
|
43
|
+
def persist_last_response(response)
|
44
|
+
@globals[:last_response] = response.http
|
123
45
|
end
|
124
46
|
|
125
|
-
|
126
|
-
|
127
|
-
def yield_objects(offset, instance, &block)
|
128
|
-
to_yield = [:soap, :wsdl, :http, :wsse]
|
129
|
-
yield *(to_yield[offset, block.arity].map { |obj_name| instance.send(obj_name) })
|
47
|
+
def wsdl_or_endpoint_and_namespace_specified?
|
48
|
+
@globals.include?(:wsdl) || (@globals.include?(:endpoint) && @globals.include?(:namespace))
|
130
49
|
end
|
131
50
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
proxy = Object.new
|
139
|
-
proxy.instance_eval do
|
140
|
-
class << self
|
141
|
-
attr_accessor :original_self, :instance
|
142
|
-
end
|
143
|
-
|
144
|
-
def method_missing(method, *args, &block)
|
145
|
-
instance.send(method, *args, &block)
|
146
|
-
rescue NoMethodError
|
147
|
-
original_self.send(method, *args, &block)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
|
-
proxy.instance = instance
|
152
|
-
proxy.original_self = original_self
|
153
|
-
|
154
|
-
proxy.instance_eval &block
|
51
|
+
def raise_initialization_error!
|
52
|
+
raise InitializationError,
|
53
|
+
"Expected either a WSDL document or the SOAP endpoint and target namespace options.\n\n" \
|
54
|
+
"Savon.client(wsdl: '/Users/me/project/service.wsdl') # to use a local WSDL document\n" \
|
55
|
+
"Savon.client(wsdl: 'http://example.com?wsdl') # to use a remote WSDL document\n" \
|
56
|
+
"Savon.client(endpoint: 'http://example.com', namespace: 'http://v1.example.com') # if you don't have a WSDL document"
|
155
57
|
end
|
156
58
|
|
157
|
-
|
158
|
-
|
159
|
-
hash.delete_if { |_, value| value.respond_to?(:empty?) ? value.empty? : !value }
|
59
|
+
def raise_missing_wsdl_error!
|
60
|
+
raise "Unable to inspect the service without a WSDL document."
|
160
61
|
end
|
161
62
|
|
162
63
|
end
|