savon 0.7.9 → 0.8.0.beta.1
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/.gitignore +9 -0
- data/.rspec +1 -0
- data/.yardopts +2 -0
- data/CHANGELOG.md +332 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +37 -0
- data/Rakefile +28 -39
- data/autotest/discover.rb +1 -0
- data/lib/savon.rb +10 -31
- data/lib/savon/client.rb +116 -98
- data/lib/savon/core_ext/array.rb +36 -22
- data/lib/savon/core_ext/datetime.rb +15 -6
- data/lib/savon/core_ext/hash.rb +122 -94
- data/lib/savon/core_ext/object.rb +19 -11
- data/lib/savon/core_ext/string.rb +62 -57
- data/lib/savon/core_ext/symbol.rb +13 -5
- data/lib/savon/error.rb +6 -0
- data/lib/savon/global.rb +75 -0
- data/lib/savon/http/error.rb +42 -0
- data/lib/savon/soap.rb +8 -283
- data/lib/savon/soap/fault.rb +48 -0
- data/lib/savon/soap/request.rb +61 -0
- data/lib/savon/soap/response.rb +65 -0
- data/lib/savon/soap/xml.rb +132 -0
- data/lib/savon/version.rb +2 -2
- data/lib/savon/wsdl/document.rb +107 -0
- data/lib/savon/wsdl/parser.rb +90 -0
- data/lib/savon/wsdl/request.rb +35 -0
- data/lib/savon/wsse.rb +42 -104
- data/savon.gemspec +26 -0
- data/spec/fixtures/response/response_fixture.rb +26 -26
- data/spec/fixtures/response/xml/list.xml +18 -0
- data/spec/fixtures/wsdl/wsdl_fixture.rb +6 -0
- data/spec/fixtures/wsdl/wsdl_fixture.yml +4 -4
- data/spec/savon/client_spec.rb +274 -51
- data/spec/savon/core_ext/datetime_spec.rb +1 -1
- data/spec/savon/core_ext/hash_spec.rb +40 -4
- data/spec/savon/core_ext/object_spec.rb +1 -1
- data/spec/savon/core_ext/string_spec.rb +0 -12
- data/spec/savon/http/error_spec.rb +52 -0
- data/spec/savon/savon_spec.rb +90 -0
- data/spec/savon/soap/fault_spec.rb +80 -0
- data/spec/savon/soap/request_spec.rb +45 -0
- data/spec/savon/soap/response_spec.rb +153 -0
- data/spec/savon/soap/xml_spec.rb +249 -0
- data/spec/savon/soap_spec.rb +4 -177
- data/spec/savon/{wsdl_spec.rb → wsdl/document_spec.rb} +54 -17
- data/spec/savon/wsdl/request_spec.rb +15 -0
- data/spec/savon/wsse_spec.rb +123 -92
- data/spec/spec_helper.rb +19 -4
- data/spec/support/endpoint.rb +25 -0
- metadata +97 -97
- data/.autotest +0 -5
- data/CHANGELOG +0 -176
- data/README.rdoc +0 -64
- data/lib/savon/core_ext.rb +0 -8
- data/lib/savon/core_ext/net_http.rb +0 -19
- data/lib/savon/core_ext/uri.rb +0 -10
- data/lib/savon/logger.rb +0 -56
- data/lib/savon/request.rb +0 -138
- data/lib/savon/response.rb +0 -174
- data/lib/savon/wsdl.rb +0 -137
- data/lib/savon/wsdl_stream.rb +0 -85
- data/spec/basic_spec_helper.rb +0 -11
- data/spec/endpoint_helper.rb +0 -23
- data/spec/http_stubs.rb +0 -26
- data/spec/integration/http_basic_auth_spec.rb +0 -16
- data/spec/integration/server.rb +0 -51
- data/spec/savon/core_ext/net_http_spec.rb +0 -38
- data/spec/savon/core_ext/uri_spec.rb +0 -19
- data/spec/savon/request_spec.rb +0 -117
- data/spec/savon/response_spec.rb +0 -179
- data/spec/spec.opts +0 -4
@@ -0,0 +1 @@
|
|
1
|
+
Autotest.add_discovery { "rspec2" }
|
data/lib/savon.rb
CHANGED
@@ -1,35 +1,14 @@
|
|
1
|
-
|
1
|
+
require "savon/version"
|
2
|
+
require "savon/global"
|
3
|
+
require "savon/client"
|
2
4
|
|
3
|
-
|
4
|
-
|
5
|
+
module Savon
|
6
|
+
extend Global
|
5
7
|
|
6
|
-
#
|
7
|
-
|
8
|
+
# Yields this module to a given +block+. Please refer to the
|
9
|
+
# <tt>Savon::Global</tt> module for configuration options.
|
10
|
+
def self.configure
|
11
|
+
yield self if block_given?
|
12
|
+
end
|
8
13
|
|
9
14
|
end
|
10
|
-
|
11
|
-
# standard libs
|
12
|
-
require "logger"
|
13
|
-
require "net/https"
|
14
|
-
require "base64"
|
15
|
-
require "digest/sha1"
|
16
|
-
require "rexml/document"
|
17
|
-
require "stringio"
|
18
|
-
require "zlib"
|
19
|
-
require "cgi"
|
20
|
-
|
21
|
-
# gem dependencies
|
22
|
-
require "builder"
|
23
|
-
require "crack/xml"
|
24
|
-
|
25
|
-
# core files
|
26
|
-
require "savon/core_ext"
|
27
|
-
require "savon/wsse"
|
28
|
-
require "savon/soap"
|
29
|
-
require "savon/logger"
|
30
|
-
require "savon/request"
|
31
|
-
require "savon/response"
|
32
|
-
require "savon/wsdl_stream"
|
33
|
-
require "savon/wsdl"
|
34
|
-
require "savon/client"
|
35
|
-
require "savon/version"
|
data/lib/savon/client.rb
CHANGED
@@ -1,130 +1,148 @@
|
|
1
|
+
require "httpi/request"
|
2
|
+
require "savon/soap/xml"
|
3
|
+
require "savon/soap/request"
|
4
|
+
require "savon/soap/response"
|
5
|
+
require "savon/wsdl/document"
|
6
|
+
require "savon/wsse"
|
7
|
+
|
1
8
|
module Savon
|
2
9
|
|
3
10
|
# = Savon::Client
|
4
11
|
#
|
5
12
|
# Savon::Client is the main object for connecting to a SOAP service. It includes methods to access
|
6
|
-
# both the Savon::WSDL and
|
7
|
-
#
|
8
|
-
# == Instantiation
|
9
|
-
#
|
10
|
-
# Depending on whether you aim to use Savon with or without Savon::WSDL, you need to instantiate
|
11
|
-
# Savon::Client by passing in the WSDL or SOAP endpoint.
|
12
|
-
#
|
13
|
-
# Client instance with a WSDL endpoint:
|
14
|
-
#
|
15
|
-
# client = Savon::Client.new "http://example.com/UserService?wsdl"
|
16
|
-
#
|
17
|
-
# Client instance with a SOAP endpoint (for using Savon without a WSDL):
|
18
|
-
#
|
19
|
-
# client = Savon::Client.new "http://example.com/UserService"
|
20
|
-
#
|
21
|
-
# It is recommended to not use Savon::WSDL for production. Please take a look at the Documentation
|
22
|
-
# for Savon::WSDL for more information about how to disable it.
|
23
|
-
#
|
24
|
-
# == Using a proxy server
|
25
|
-
#
|
26
|
-
# You can specify the URI to a proxy server via optional hash arguments.
|
27
|
-
#
|
28
|
-
# client = Savon::Client.new "http://example.com/UserService?wsdl", :proxy => "http://proxy.example.com"
|
29
|
-
#
|
30
|
-
# == Forcing a particular SOAP endpoint
|
31
|
-
#
|
32
|
-
# In case you don't want to use the SOAP endpoint specified in the WSDL, you can tell Savon to use
|
33
|
-
# another SOAP endpoint.
|
34
|
-
#
|
35
|
-
# client = Savon::Client.new "http://example.com/UserService?wsdl", :soap_endpoint => "http://localhost/UserService"
|
36
|
-
#
|
37
|
-
# == Gzipped SOAP requests
|
38
|
-
#
|
39
|
-
# Sending gzipped SOAP requests can be specified per client instance.
|
40
|
-
#
|
41
|
-
# client = Savon::Client.new "http://example.com/UserService?wsdl", :gzip => true
|
42
|
-
#
|
43
|
-
# == Savon::WSDL
|
44
|
-
#
|
45
|
-
# You can access Savon::WSDL via:
|
46
|
-
#
|
47
|
-
# client.wsdl
|
48
|
-
#
|
49
|
-
# == Savon::Request
|
50
|
-
#
|
51
|
-
# You can also access Savon::Request via:
|
52
|
-
#
|
53
|
-
# client.request
|
13
|
+
# both the Savon::WSDL::Document and HTTPI::Request object.
|
54
14
|
class Client
|
55
15
|
|
56
|
-
#
|
16
|
+
# Initializes the Savon::Client for a SOAP service. Accepts a +block+ which is evaluated in the
|
17
|
+
# context of this object to let you access the +wsdl+, +http+, and +wsse+ methods.
|
18
|
+
#
|
19
|
+
# == Examples
|
57
20
|
#
|
58
|
-
#
|
21
|
+
# # Using a remote WSDL
|
22
|
+
# client = Savon::Client.new { wsdl.document = "http://example.com/UserService?wsdl" }
|
59
23
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
24
|
+
# # Using a local WSDL
|
25
|
+
# client = Savon::Client.new { wsdl.document = "../wsdl/user_service.xml" }
|
26
|
+
#
|
27
|
+
# # Directly accessing a SOAP endpoint
|
28
|
+
# client = Savon::Client.new do
|
29
|
+
# wsdl.endpoint = "http://example.com/UserService"
|
30
|
+
# wsdl.namespace = "http://users.example.com"
|
31
|
+
# end
|
32
|
+
def initialize(&block)
|
33
|
+
process 1, &block if block
|
34
|
+
wsdl.request = http
|
67
35
|
end
|
68
36
|
|
69
|
-
# Returns the Savon::WSDL
|
70
|
-
|
37
|
+
# Returns the <tt>Savon::WSDL::Document</tt>.
|
38
|
+
def wsdl
|
39
|
+
@wsdl ||= WSDL::Document.new
|
40
|
+
end
|
71
41
|
|
72
|
-
# Returns the
|
73
|
-
|
42
|
+
# Returns the <tt>HTTPI::Request</tt>.
|
43
|
+
def http
|
44
|
+
@http ||= HTTPI::Request.new
|
45
|
+
end
|
74
46
|
|
75
|
-
# Returns
|
76
|
-
def
|
77
|
-
|
78
|
-
super
|
47
|
+
# Returns the <tt>Savon::WSSE</tt> object.
|
48
|
+
def wsse
|
49
|
+
@wsse ||= WSSE.new
|
79
50
|
end
|
80
51
|
|
81
|
-
#
|
82
|
-
#
|
83
|
-
|
84
|
-
|
52
|
+
# Returns the <tt>Savon::SOAP::XML</tt> object. Please notice, that this object is only available
|
53
|
+
# in a block given to <tt>Savon::Client#request</tt>. A new instance of this object is created
|
54
|
+
# per SOAP request.
|
55
|
+
attr_reader :soap
|
56
|
+
|
57
|
+
# Executes a SOAP request for a given SOAP action. Accepts a +block+ which is evaluated in the
|
58
|
+
# context of this object to let you access the +soap+, +wsdl+, +http+ and +wsse+ methods.
|
59
|
+
#
|
60
|
+
# == Examples
|
61
|
+
#
|
62
|
+
# # Calls a "getUser" SOAP action with the payload of "<userId>123</userId>"
|
63
|
+
# client.request(:get_user) { soap.body = { :user_id => 123 } }
|
64
|
+
#
|
65
|
+
# # Prefixes the SOAP input tag with a given namespace: "<wsdl:GetUser>...</wsdl:GetUser>"
|
66
|
+
# client.request(:wsdl, "GetUser") { soap.body = { :user_id => 123 } }
|
67
|
+
#
|
68
|
+
# # SOAP input tag with attributes: <getUser xmlns:wsdl="http://example.com">...</getUser>"
|
69
|
+
# client.request(:get_user, "xmlns:wsdl" => "http://example.com")
|
70
|
+
def request(*args, &block)
|
71
|
+
raise ArgumentError, "Savon::Client#request requires at least one argument" if args.empty?
|
72
|
+
|
73
|
+
self.soap = SOAP::XML.new
|
74
|
+
preconfigure extract_options(args)
|
75
|
+
process &block if block
|
76
|
+
soap.wsse = wsse
|
77
|
+
|
78
|
+
SOAP::Request.new(http, soap).response
|
85
79
|
end
|
86
80
|
|
87
81
|
private
|
88
82
|
|
89
|
-
#
|
90
|
-
|
91
|
-
soap_action = soap_action_from method.to_s
|
92
|
-
super unless @wsdl.respond_to? soap_action
|
83
|
+
# Writer for the <tt>Savon::SOAP::XML</tt> object.
|
84
|
+
attr_writer :soap
|
93
85
|
|
94
|
-
|
95
|
-
|
86
|
+
# Accessor for the original self of a given block.
|
87
|
+
attr_accessor :original_self
|
88
|
+
|
89
|
+
# Expects an Array of +args+ and returns an Array containing the namespace (might be +nil+),
|
90
|
+
# the SOAP input and a Hash of attributes for the input tag (which might be empty).
|
91
|
+
def extract_options(args)
|
92
|
+
attributes = Hash === args.last ? args.pop : {}
|
93
|
+
namespace = args.size > 1 ? args.shift.to_sym : nil
|
94
|
+
input = args.first
|
95
|
+
|
96
|
+
[namespace, input, attributes]
|
96
97
|
end
|
97
98
|
|
98
|
-
#
|
99
|
-
|
100
|
-
|
101
|
-
|
99
|
+
# Expects and Array of +options+ and preconfigures the system.
|
100
|
+
def preconfigure(options)
|
101
|
+
soap.endpoint = wsdl.endpoint
|
102
|
+
soap.namespace_identifier = options[0]
|
103
|
+
soap.namespace = wsdl.namespace
|
104
|
+
soap.body = options[2].delete :body
|
105
|
+
|
106
|
+
set_soap_action options[1]
|
107
|
+
set_soap_input *options
|
108
|
+
end
|
109
|
+
|
110
|
+
# Expects an +input+ and sets the +SOAPAction+ HTTP headers.
|
111
|
+
def set_soap_action(input)
|
112
|
+
soap_action = wsdl.soap_action input.to_sym
|
113
|
+
soap_action ||= input.kind_of?(String) ? input : input.to_s.lower_camelcase
|
114
|
+
http.headers["SOAPAction"] = %{"#{soap_action}"}
|
115
|
+
end
|
116
|
+
|
117
|
+
# Expects a +namespace+, +input+ and +attributes+ and sets the SOAP input.
|
118
|
+
def set_soap_input(namespace, input, attributes)
|
119
|
+
new_input = wsdl.soap_input input.to_sym
|
120
|
+
new_input ||= input.kind_of?(String) ? input.to_sym : input.to_s.lower_camelcase.to_sym
|
121
|
+
soap.input = [namespace, new_input, attributes].compact
|
122
|
+
end
|
102
123
|
|
103
|
-
|
104
|
-
|
124
|
+
# Processes a given +block+. Yields objects if the block expects any arguments.
|
125
|
+
# Otherwise evaluates the block in the context of this object.
|
126
|
+
def process(offset = 0, &block)
|
127
|
+
block.arity > 0 ? yield_objects(offset, &block) : evaluate(&block)
|
105
128
|
end
|
106
129
|
|
107
|
-
#
|
108
|
-
|
109
|
-
|
130
|
+
# Yields a number of objects to a given +block+ depending on how many arguments
|
131
|
+
# the block is expecting.
|
132
|
+
def yield_objects(offset, &block)
|
133
|
+
yield *[soap, wsdl, http, wsse][offset, block.arity]
|
110
134
|
end
|
111
135
|
|
112
|
-
#
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
yield_objects &block if block
|
117
|
-
@soap.namespaces["xmlns:wsdl"] ||= @wsdl.namespace_uri if @wsdl.enabled?
|
118
|
-
@soap.wsse = @wsse
|
136
|
+
# Evaluates a given +block+ inside this object. Stores the original block binding.
|
137
|
+
def evaluate(&block)
|
138
|
+
self.original_self = eval "self", block.binding
|
139
|
+
instance_eval &block
|
119
140
|
end
|
120
141
|
|
121
|
-
#
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
when 1 then yield @soap
|
126
|
-
when 2 then yield @soap, @wsse
|
127
|
-
end
|
142
|
+
# Handles calls to undefined methods by delegating to the original block binding.
|
143
|
+
def method_missing(method, *args, &block)
|
144
|
+
super unless original_self
|
145
|
+
original_self.send method, *args, &block
|
128
146
|
end
|
129
147
|
|
130
148
|
end
|
data/lib/savon/core_ext/array.rb
CHANGED
@@ -1,31 +1,45 @@
|
|
1
|
-
|
1
|
+
require "builder"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
require "savon/core_ext/object"
|
4
|
+
require "savon/core_ext/string"
|
5
|
+
require "savon/core_ext/hash"
|
6
|
+
require "savon/core_ext/datetime"
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
when Hash then xml.tag!(key, attrs) { xml << item.to_soap_xml }
|
11
|
-
else xml.tag!(key, attrs) { xml << (escape_xml ? item.to_soap_value : item.to_soap_value!) }
|
12
|
-
end
|
13
|
-
end
|
8
|
+
module Savon
|
9
|
+
module CoreExt
|
10
|
+
module Array
|
14
11
|
|
15
|
-
|
16
|
-
|
12
|
+
# Translates the Array into SOAP compatible XML. See: Hash.to_soap_xml.
|
13
|
+
def to_soap_xml(key, escape_xml = true, attributes = {})
|
14
|
+
xml = Builder::XmlMarkup.new
|
15
|
+
|
16
|
+
each_with_index do |item, index|
|
17
|
+
attrs = tag_attributes attributes, index
|
18
|
+
case item
|
19
|
+
when ::Hash then xml.tag!(key, attrs) { xml << item.to_soap_xml }
|
20
|
+
when NilClass then xml.tag!(key, "xsi:nil" => "true")
|
21
|
+
else xml.tag!(key, attrs) { xml << (escape_xml ? item.to_soap_value : item.to_soap_value!) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
xml.target!
|
26
|
+
end
|
17
27
|
|
18
|
-
private
|
28
|
+
private
|
19
29
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
30
|
+
# Takes a Hash of +attributes+ and the +index+ for which to return attributes
|
31
|
+
# for duplicate tags.
|
32
|
+
def tag_attributes(attributes, index)
|
33
|
+
return {} if attributes.empty?
|
34
|
+
|
35
|
+
attributes.inject({}) do |hash, (key, value)|
|
36
|
+
value = value[index] if value.kind_of? ::Array
|
37
|
+
hash.merge key => value
|
38
|
+
end
|
39
|
+
end
|
24
40
|
|
25
|
-
attributes.inject({}) do |hash, (key, value)|
|
26
|
-
value = value[index] if value.kind_of? Array
|
27
|
-
hash.merge key => value
|
28
41
|
end
|
29
42
|
end
|
43
|
+
end
|
30
44
|
|
31
|
-
|
45
|
+
Array.send :include, Savon::CoreExt::Array
|
@@ -1,10 +1,19 @@
|
|
1
|
-
|
1
|
+
require "date"
|
2
|
+
require "savon/soap"
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
module Savon
|
5
|
+
module CoreExt
|
6
|
+
module DateTime
|
7
|
+
|
8
|
+
# Returns the DateTime as an xs:dateTime formatted String.
|
9
|
+
def to_soap_value
|
10
|
+
strftime Savon::SOAP::DateTimeFormat
|
11
|
+
end
|
7
12
|
|
8
|
-
|
13
|
+
alias_method :to_soap_value!, :to_soap_value
|
9
14
|
|
15
|
+
end
|
16
|
+
end
|
10
17
|
end
|
18
|
+
|
19
|
+
DateTime.send :include, Savon::CoreExt::DateTime
|
data/lib/savon/core_ext/hash.rb
CHANGED
@@ -1,107 +1,135 @@
|
|
1
|
-
|
1
|
+
require "builder"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
|
-
# Translates the Hash into SOAP request compatible XML.
|
12
|
-
#
|
13
|
-
# { :find_user => { :id => 123, "wsdl:Key" => "api" } }.to_soap_xml
|
14
|
-
# # => "<findUser><id>123</id><wsdl:Key>api</wsdl:Key></findUser>"
|
15
|
-
#
|
16
|
-
# ==== Mapping
|
17
|
-
#
|
18
|
-
# * Hash keys specified as Symbols are converted to lowerCamelCase Strings
|
19
|
-
# * Hash keys specified as Strings are not converted and may contain namespaces
|
20
|
-
# * DateTime values are converted to xs:dateTime Strings
|
21
|
-
# * Objects responding to to_datetime (except Strings) are converted to xs:dateTime Strings
|
22
|
-
# * TrueClass and FalseClass objects are converted to "true" and "false" Strings
|
23
|
-
# * All other objects are expected to be converted to Strings using to_s
|
24
|
-
#
|
25
|
-
# An example:
|
26
|
-
#
|
27
|
-
# { :magic_request => {
|
28
|
-
# :perform_move => true,
|
29
|
-
# "perform_at" => DateTime.new(2010, 11, 22, 11, 22, 33)
|
30
|
-
# }
|
31
|
-
# }.to_soap_xml
|
32
|
-
#
|
33
|
-
# <magicRequest>
|
34
|
-
# <performMove>true</performMove>
|
35
|
-
# <perform_at>2012-06-11T10:42:21</perform_at>
|
36
|
-
# </magicRequest>
|
37
|
-
#
|
38
|
-
# ==== Escaped XML values
|
39
|
-
#
|
40
|
-
# By default, special characters in XML String values are escaped.
|
41
|
-
#
|
42
|
-
# ==== Fixed order of XML tags
|
43
|
-
#
|
44
|
-
# In case your service requires the tags to be in a specific order (parameterOrder), you have two
|
45
|
-
# options. The first is to specify your body as an XML string. The second is to specify the order
|
46
|
-
# through an additional array stored under the +:order!+ key.
|
47
|
-
#
|
48
|
-
# { :name => "Eve", :id => 123, :order! => [:id, :name] }.to_soap_xml
|
49
|
-
# # => "<id>123</id><name>Eve</name>"
|
50
|
-
#
|
51
|
-
# ==== XML attributes
|
52
|
-
#
|
53
|
-
# If you need attributes, you could either go with an XML string or add another hash under the
|
54
|
-
# +:attributes!+ key.
|
55
|
-
#
|
56
|
-
# { :person => "Eve", :attributes! => { :person => { :id => 666 } } }.to_soap_xml
|
57
|
-
# # => '<person id="666">Eve</person>'
|
58
|
-
def to_soap_xml
|
59
|
-
xml = Builder::XmlMarkup.new
|
60
|
-
attributes = delete(:attributes!) || {}
|
3
|
+
require "savon"
|
4
|
+
require "savon/core_ext/object"
|
5
|
+
require "savon/core_ext/string"
|
6
|
+
require "savon/core_ext/symbol"
|
7
|
+
require "savon/core_ext/array"
|
8
|
+
require "savon/core_ext/datetime"
|
61
9
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
escape_xml = key.to_s[-1, 1] != "!"
|
66
|
-
key = key.to_soap_key
|
10
|
+
module Savon
|
11
|
+
module CoreExt
|
12
|
+
module Hash
|
67
13
|
|
68
|
-
case
|
69
|
-
|
70
|
-
|
71
|
-
|
14
|
+
# Returns the values from the soap:Body element or an empty Hash in case the soap:Body tag could
|
15
|
+
# not be found.
|
16
|
+
def find_soap_body
|
17
|
+
envelope = self[keys.first] || {}
|
18
|
+
body_key = envelope.keys.find { |key| /.+:Body/ =~ key } rescue nil
|
19
|
+
body_key ? envelope[body_key].map_soap_response : {}
|
72
20
|
end
|
73
|
-
end
|
74
21
|
|
75
|
-
|
76
|
-
|
22
|
+
# Translates the Hash into SOAP request compatible XML.
|
23
|
+
#
|
24
|
+
# { :find_user => { :id => 123, "wsdl:Key" => "api" } }.to_soap_xml
|
25
|
+
# # => "<findUser><id>123</id><wsdl:Key>api</wsdl:Key></findUser>"
|
26
|
+
#
|
27
|
+
# ==== Mapping
|
28
|
+
#
|
29
|
+
# * Hash keys specified as Symbols are converted to lowerCamelCase Strings
|
30
|
+
# * Hash keys specified as Strings are not converted and may contain namespaces
|
31
|
+
# * DateTime values are converted to xs:dateTime Strings
|
32
|
+
# * Objects responding to to_datetime (except Strings) are converted to xs:dateTime Strings
|
33
|
+
# * TrueClass and FalseClass objects are converted to "true" and "false" Strings
|
34
|
+
# * All other objects are expected to be converted to Strings using to_s
|
35
|
+
#
|
36
|
+
# An example:
|
37
|
+
#
|
38
|
+
# { :magic_request => {
|
39
|
+
# :perform_move => true,
|
40
|
+
# "perform_at" => DateTime.new(2010, 11, 22, 11, 22, 33)
|
41
|
+
# }
|
42
|
+
# }.to_soap_xml
|
43
|
+
#
|
44
|
+
# <magicRequest>
|
45
|
+
# <performMove>true</performMove>
|
46
|
+
# <perform_at>2012-06-11T10:42:21</perform_at>
|
47
|
+
# </magicRequest>
|
48
|
+
#
|
49
|
+
# ==== Escaped XML values
|
50
|
+
#
|
51
|
+
# By default, special characters in XML String values are escaped.
|
52
|
+
#
|
53
|
+
# ==== Fixed order of XML tags
|
54
|
+
#
|
55
|
+
# In case your service requires the tags to be in a specific order (parameterOrder), you have two
|
56
|
+
# options. The first is to specify your body as an XML string. The second is to specify the order
|
57
|
+
# through an additional array stored under the +:order!+ key.
|
58
|
+
#
|
59
|
+
# { :name => "Eve", :id => 123, :order! => [:id, :name] }.to_soap_xml
|
60
|
+
# # => "<id>123</id><name>Eve</name>"
|
61
|
+
#
|
62
|
+
# ==== XML attributes
|
63
|
+
#
|
64
|
+
# If you need attributes, you could either go with an XML string or add another hash under the
|
65
|
+
# +:attributes!+ key.
|
66
|
+
#
|
67
|
+
# { :person => "Eve", :attributes! => { :person => { :id => 666 } } }.to_soap_xml
|
68
|
+
# # => '<person id="666">Eve</person>'
|
69
|
+
def to_soap_xml
|
70
|
+
xml = Builder::XmlMarkup.new
|
71
|
+
attributes = delete(:attributes!) || {}
|
72
|
+
|
73
|
+
order.each do |key|
|
74
|
+
attrs = attributes[key] || {}
|
75
|
+
value = self[key]
|
76
|
+
escape_xml = key.to_s[-1, 1] != "!"
|
77
|
+
key = key.to_soap_key
|
78
|
+
|
79
|
+
case value
|
80
|
+
when ::Array then xml << value.to_soap_xml(key, escape_xml, attrs)
|
81
|
+
when ::Hash then xml.tag!(key, attrs) { xml << value.to_soap_xml }
|
82
|
+
when NilClass then xml.tag!(key, "xsi:nil" => "true")
|
83
|
+
else xml.tag!(key, attrs) { xml << (escape_xml ? value.to_soap_value : value.to_soap_value!) }
|
84
|
+
end
|
85
|
+
end
|
77
86
|
|
78
|
-
|
79
|
-
def map_soap_response
|
80
|
-
inject({}) do |hash, (key, value)|
|
81
|
-
value = case value
|
82
|
-
when Hash then value["xsi:nil"] ? nil : value.map_soap_response
|
83
|
-
when Array then value.map { |val| val.map_soap_response rescue val }
|
84
|
-
when String then value.map_soap_response
|
87
|
+
xml.target!
|
85
88
|
end
|
86
89
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
90
|
+
# Maps keys and values of a Hash created from SOAP response XML to more convenient Ruby Objects.
|
91
|
+
def map_soap_response
|
92
|
+
inject({}) do |hash, (key, value)|
|
93
|
+
value = case value
|
94
|
+
when ::Hash then value["xsi:nil"] ? nil : value.map_soap_response
|
95
|
+
when ::Array then value.map { |val| val.map_soap_response rescue val }
|
96
|
+
when ::String then value.map_soap_response
|
97
|
+
end
|
98
|
+
|
99
|
+
new_key = if Savon.strip_namespaces?
|
100
|
+
key.strip_namespace.snakecase.to_sym
|
101
|
+
else
|
102
|
+
key.snakecase
|
103
|
+
end
|
104
|
+
|
105
|
+
if hash[new_key] # key already exists, value should be added as an Array
|
106
|
+
hash[new_key] = [hash[new_key], value].flatten
|
107
|
+
result = hash
|
108
|
+
else
|
109
|
+
result = hash.merge new_key => value
|
110
|
+
end
|
111
|
+
result
|
112
|
+
end
|
113
|
+
end
|
92
114
|
|
93
|
-
|
94
|
-
# keys of this Hash if no :order! key could be found. Raises an ArgumentError in case the :order!
|
95
|
-
# Array does not match the Hash keys.
|
96
|
-
def order
|
97
|
-
order = delete :order!
|
98
|
-
order = keys unless order.kind_of? Array
|
115
|
+
private
|
99
116
|
|
100
|
-
|
101
|
-
|
102
|
-
|
117
|
+
# Deletes and returns an Array of keys stored under the :order! key. Defaults to return the actual
|
118
|
+
# keys of this Hash if no :order! key could be found. Raises an ArgumentError in case the :order!
|
119
|
+
# Array does not match the Hash keys.
|
120
|
+
def order
|
121
|
+
order = delete :order!
|
122
|
+
order = keys unless order.kind_of? ::Array
|
123
|
+
|
124
|
+
missing, spurious = keys - order, order - keys
|
125
|
+
raise ArgumentError, "Missing elements in :order! #{missing.inspect}" unless missing.empty?
|
126
|
+
raise ArgumentError, "Spurious elements in :order! #{spurious.inspect}" unless spurious.empty?
|
127
|
+
|
128
|
+
order
|
129
|
+
end
|
103
130
|
|
104
|
-
|
131
|
+
end
|
105
132
|
end
|
133
|
+
end
|
106
134
|
|
107
|
-
|
135
|
+
Hash.send :include, Savon::CoreExt::Hash
|