s-savon 0.8.6
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 +461 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +37 -0
- data/Rakefile +40 -0
- data/lib/savon.rb +14 -0
- data/lib/savon/client.rb +157 -0
- data/lib/savon/core_ext/hash.rb +70 -0
- data/lib/savon/core_ext/object.rb +14 -0
- data/lib/savon/core_ext/string.rb +51 -0
- data/lib/savon/core_ext/time.rb +14 -0
- 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 +24 -0
- data/lib/savon/soap/fault.rb +59 -0
- data/lib/savon/soap/request.rb +61 -0
- data/lib/savon/soap/response.rb +80 -0
- data/lib/savon/soap/xml.rb +187 -0
- data/lib/savon/version.rb +5 -0
- data/lib/savon/wsdl/document.rb +112 -0
- data/lib/savon/wsdl/parser.rb +102 -0
- data/lib/savon/wsdl/request.rb +35 -0
- data/lib/savon/wsse.rb +150 -0
- data/savon.gemspec +29 -0
- data/spec/fixtures/gzip/message.gz +0 -0
- data/spec/fixtures/response/another_soap_fault.xml +14 -0
- data/spec/fixtures/response/authentication.xml +14 -0
- data/spec/fixtures/response/header.xml +13 -0
- data/spec/fixtures/response/list.xml +18 -0
- data/spec/fixtures/response/multi_ref.xml +39 -0
- data/spec/fixtures/response/soap_fault.xml +8 -0
- data/spec/fixtures/response/soap_fault12.xml +18 -0
- data/spec/fixtures/wsdl/authentication.xml +63 -0
- data/spec/fixtures/wsdl/geotrust.xml +156 -0
- data/spec/fixtures/wsdl/namespaced_actions.xml +307 -0
- data/spec/fixtures/wsdl/no_namespace.xml +115 -0
- data/spec/fixtures/wsdl/two_bindings.xml +25 -0
- data/spec/savon/client_spec.rb +346 -0
- data/spec/savon/core_ext/hash_spec.rb +121 -0
- data/spec/savon/core_ext/object_spec.rb +19 -0
- data/spec/savon/core_ext/string_spec.rb +57 -0
- data/spec/savon/core_ext/time_spec.rb +13 -0
- data/spec/savon/http/error_spec.rb +52 -0
- data/spec/savon/savon_spec.rb +85 -0
- data/spec/savon/soap/fault_spec.rb +89 -0
- data/spec/savon/soap/request_spec.rb +45 -0
- data/spec/savon/soap/response_spec.rb +174 -0
- data/spec/savon/soap/xml_spec.rb +335 -0
- data/spec/savon/soap_spec.rb +21 -0
- data/spec/savon/wsdl/document_spec.rb +132 -0
- data/spec/savon/wsdl/parser_spec.rb +99 -0
- data/spec/savon/wsdl/request_spec.rb +15 -0
- data/spec/savon/wsse_spec.rb +213 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/endpoint.rb +25 -0
- data/spec/support/fixture.rb +37 -0
- metadata +251 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
require "savon/error"
|
2
|
+
require "savon/soap/xml"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module HTTP
|
6
|
+
|
7
|
+
# = Savon::HTTP::Error
|
8
|
+
#
|
9
|
+
# Represents an HTTP error. Contains the original <tt>HTTPI::Response</tt>.
|
10
|
+
class Error < Error
|
11
|
+
|
12
|
+
# Expects an <tt>HTTPI::Response</tt>.
|
13
|
+
def initialize(http)
|
14
|
+
self.http = http
|
15
|
+
end
|
16
|
+
|
17
|
+
# Accessor for the <tt>HTTPI::Response</tt>.
|
18
|
+
attr_accessor :http
|
19
|
+
|
20
|
+
# Returns whether an HTTP error is present.
|
21
|
+
def present?
|
22
|
+
http.error?
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the HTTP error message.
|
26
|
+
def to_s
|
27
|
+
return "" unless present?
|
28
|
+
|
29
|
+
@message ||= begin
|
30
|
+
message = "HTTP error (#{http.code})"
|
31
|
+
message << ": #{http.body}" unless http.body.empty?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Returns the HTTP response as a Hash.
|
36
|
+
def to_hash
|
37
|
+
@hash = { :code => http.code, :headers => http.headers, :body => http.body }
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/savon/soap.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Savon
|
2
|
+
|
3
|
+
# = Savon::SOAP
|
4
|
+
#
|
5
|
+
# Contains various SOAP details.
|
6
|
+
module SOAP
|
7
|
+
|
8
|
+
# Default SOAP version.
|
9
|
+
DefaultVersion = 1
|
10
|
+
|
11
|
+
# Supported SOAP versions.
|
12
|
+
Versions = 1..2
|
13
|
+
|
14
|
+
# SOAP namespaces by SOAP version.
|
15
|
+
Namespace = {
|
16
|
+
1 => "http://schemas.xmlsoap.org/soap/envelope/",
|
17
|
+
2 => "http://www.w3.org/2003/05/soap-envelope"
|
18
|
+
}
|
19
|
+
|
20
|
+
# SOAP xs:dateTime Regexp.
|
21
|
+
DateTimeRegexp = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "savon/error"
|
2
|
+
require "savon/soap/xml"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module SOAP
|
6
|
+
|
7
|
+
# = Savon::SOAP::Fault
|
8
|
+
#
|
9
|
+
# Represents a SOAP fault. Contains the original <tt>HTTPI::Response</tt>.
|
10
|
+
class Fault < Error
|
11
|
+
|
12
|
+
# Expects an <tt>HTTPI::Response</tt>.
|
13
|
+
def initialize(http)
|
14
|
+
self.http = http
|
15
|
+
end
|
16
|
+
|
17
|
+
# Accessor for the <tt>HTTPI::Response</tt>.
|
18
|
+
attr_accessor :http
|
19
|
+
|
20
|
+
# Returns whether a SOAP fault is present.
|
21
|
+
def present?
|
22
|
+
@present ||= http.body.include?("Fault>") && (soap1_fault? || soap2_fault?)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the SOAP fault message.
|
26
|
+
def to_s
|
27
|
+
return "" unless present?
|
28
|
+
@message ||= message_by_version to_hash[:fault]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the SOAP response body as a Hash.
|
32
|
+
def to_hash
|
33
|
+
@hash ||= Savon::SOAP::XML.to_hash http.body
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# Returns whether the response contains a SOAP 1.1 fault.
|
39
|
+
def soap1_fault?
|
40
|
+
http.body.include?("faultcode>") && http.body.include?("faultstring>")
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns whether the response contains a SOAP 1.2 fault.
|
44
|
+
def soap2_fault?
|
45
|
+
http.body.include?("Code>") && http.body.include?("Reason>")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the SOAP fault message by version.
|
49
|
+
def message_by_version(fault)
|
50
|
+
if fault[:faultcode]
|
51
|
+
"(#{fault[:faultcode]}) #{fault[:faultstring]}"
|
52
|
+
elsif fault[:code]
|
53
|
+
"(#{fault[:code][:value]}) #{fault[:reason][:text]}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "httpi"
|
2
|
+
require "savon/soap/response"
|
3
|
+
|
4
|
+
module Savon
|
5
|
+
module SOAP
|
6
|
+
|
7
|
+
# = Savon::SOAP::Request
|
8
|
+
#
|
9
|
+
# Executes SOAP requests.
|
10
|
+
class Request
|
11
|
+
|
12
|
+
# Content-Types by SOAP version.
|
13
|
+
ContentType = { 1 => "text/xml;charset=UTF-8", 2 => "application/soap+xml;charset=UTF-8" }
|
14
|
+
|
15
|
+
# Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object.
|
16
|
+
def initialize(request, soap)
|
17
|
+
self.request = setup(request, soap)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Accessor for the <tt>HTTPI::Request</tt>.
|
21
|
+
attr_accessor :request
|
22
|
+
|
23
|
+
# Executes the request and returns the response.
|
24
|
+
def response
|
25
|
+
@response ||= with_logging { HTTPI.post request }
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Sets up the +request+ using a given +soap+ object.
|
31
|
+
def setup(request, soap)
|
32
|
+
request.url = soap.endpoint
|
33
|
+
request.headers["Content-Type"] ||= ContentType[soap.version]
|
34
|
+
request.body = soap.to_xml
|
35
|
+
request
|
36
|
+
end
|
37
|
+
|
38
|
+
# Logs the HTTP request, yields to a given +block+ and returns a <tt>Savon::SOAP::Response</tt>.
|
39
|
+
def with_logging
|
40
|
+
log_request request.url, request.headers, request.body
|
41
|
+
response = yield
|
42
|
+
log_response response.code, response.body
|
43
|
+
SOAP::Response.new response
|
44
|
+
end
|
45
|
+
|
46
|
+
# Logs the SOAP request +url+, +headers+ and +body+.
|
47
|
+
def log_request(url, headers, body)
|
48
|
+
Savon.log "SOAP request: #{url}"
|
49
|
+
Savon.log headers.map { |key, value| "#{key}: #{value}" }.join(", ")
|
50
|
+
Savon.log body
|
51
|
+
end
|
52
|
+
|
53
|
+
# Logs the SOAP response +code+ and +body+.
|
54
|
+
def log_response(code, body)
|
55
|
+
Savon.log "SOAP response (status #{code}):"
|
56
|
+
Savon.log body
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "savon/soap/xml"
|
2
|
+
require "savon/soap/fault"
|
3
|
+
require "savon/http/error"
|
4
|
+
|
5
|
+
module Savon
|
6
|
+
module SOAP
|
7
|
+
|
8
|
+
# = Savon::SOAP::Response
|
9
|
+
#
|
10
|
+
# Represents the SOAP response and contains the HTTP response.
|
11
|
+
class Response
|
12
|
+
|
13
|
+
# Expects an <tt>HTTPI::Response</tt> and handles errors.
|
14
|
+
def initialize(response)
|
15
|
+
self.http = response
|
16
|
+
raise_errors if Savon.raise_errors?
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_accessor :http
|
20
|
+
|
21
|
+
# Returns whether the request was successful.
|
22
|
+
def success?
|
23
|
+
!soap_fault? && !http_error?
|
24
|
+
end
|
25
|
+
|
26
|
+
# Returns whether there was a SOAP fault.
|
27
|
+
def soap_fault?
|
28
|
+
soap_fault.present?
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the <tt>Savon::SOAP::Fault</tt>.
|
32
|
+
def soap_fault
|
33
|
+
@soap_fault ||= Fault.new http
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns whether there was an HTTP error.
|
37
|
+
def http_error?
|
38
|
+
http_error.present?
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the <tt>Savon::HTTP::Error</tt>.
|
42
|
+
def http_error
|
43
|
+
@http_error ||= HTTP::Error.new http
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns the SOAP response header as a Hash.
|
47
|
+
def header
|
48
|
+
@header_hash ||= basic_hash.find_soap_header
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns the SOAP response body as a Hash.
|
52
|
+
def to_hash
|
53
|
+
@hash ||= Savon::SOAP::XML.to_hash basic_hash
|
54
|
+
end
|
55
|
+
|
56
|
+
# Returns the SOAP response body as an Array.
|
57
|
+
def to_array(*path)
|
58
|
+
Savon::SOAP::XML.to_array to_hash, *path
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the complete SOAP response XML without normalization.
|
62
|
+
def basic_hash
|
63
|
+
@basic_hash ||= Savon::SOAP::XML.parse http.body
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the SOAP response XML.
|
67
|
+
def to_xml
|
68
|
+
http.body
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def raise_errors
|
74
|
+
raise soap_fault if soap_fault?
|
75
|
+
raise http_error if http_error?
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require "builder"
|
2
|
+
require "crack/xml"
|
3
|
+
require "gyoku"
|
4
|
+
|
5
|
+
require "savon/soap"
|
6
|
+
require "savon/core_ext/hash"
|
7
|
+
|
8
|
+
module Savon
|
9
|
+
module SOAP
|
10
|
+
|
11
|
+
# = Savon::SOAP::XML
|
12
|
+
#
|
13
|
+
# Represents the SOAP request XML. Contains various global and per request/instance settings
|
14
|
+
# like the SOAP version, header, body and namespaces.
|
15
|
+
class XML
|
16
|
+
|
17
|
+
# XML Schema Type namespaces.
|
18
|
+
SchemaTypes = {
|
19
|
+
"xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
|
20
|
+
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
|
21
|
+
}
|
22
|
+
|
23
|
+
# Converts the given SOAP response +value+ (XML or Hash) into a normalized Hash.
|
24
|
+
def self.to_hash(value)
|
25
|
+
value = parse value unless value.kind_of? Hash
|
26
|
+
value.find_soap_body
|
27
|
+
end
|
28
|
+
|
29
|
+
# Converts a given SOAP response +xml+ to a Hash.
|
30
|
+
def self.parse(xml)
|
31
|
+
Crack::XML.parse(xml) rescue {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Expects a SOAP response XML or Hash, traverses it for a given +path+ of Hash keys
|
35
|
+
# and returns the value as an Array. Defaults to return an empty Array in case the
|
36
|
+
# path does not exist or returns nil.
|
37
|
+
def self.to_array(object, *path)
|
38
|
+
hash = object.kind_of?(Hash) ? object : to_hash(object)
|
39
|
+
|
40
|
+
result = path.inject hash do |memo, key|
|
41
|
+
return [] unless memo[key]
|
42
|
+
memo[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
result.kind_of?(Array) ? result.compact : [result].compact
|
46
|
+
end
|
47
|
+
|
48
|
+
# Accepts an +endpoint+, an +input+ tag and a SOAP +body+.
|
49
|
+
def initialize(endpoint = nil, input = nil, body = nil)
|
50
|
+
self.endpoint = endpoint if endpoint
|
51
|
+
self.input = input if input
|
52
|
+
self.body = body if body
|
53
|
+
end
|
54
|
+
|
55
|
+
# Accessor for the SOAP +input+ tag.
|
56
|
+
attr_accessor :input
|
57
|
+
|
58
|
+
# Accessor for the SOAP +endpoint+.
|
59
|
+
attr_accessor :endpoint
|
60
|
+
|
61
|
+
# Sets the SOAP +version+.
|
62
|
+
def version=(version)
|
63
|
+
raise ArgumentError, "Invalid SOAP version: #{version}" unless SOAP::Versions.include? version
|
64
|
+
@version = version
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns the SOAP +version+. Defaults to <tt>Savon.soap_version</tt>.
|
68
|
+
def version
|
69
|
+
@version ||= Savon.soap_version
|
70
|
+
end
|
71
|
+
|
72
|
+
# Sets the SOAP +header+ Hash.
|
73
|
+
attr_writer :header
|
74
|
+
|
75
|
+
# Returns the SOAP +header+. Defaults to an empty Hash.
|
76
|
+
def header
|
77
|
+
@header ||= {}
|
78
|
+
end
|
79
|
+
|
80
|
+
# Sets the SOAP envelope namespace.
|
81
|
+
attr_writer :env_namespace
|
82
|
+
|
83
|
+
# Returns the SOAP envelope namespace. Defaults to :env.
|
84
|
+
def env_namespace
|
85
|
+
@env_namespace ||= :soap
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sets the +namespaces+ Hash.
|
89
|
+
attr_writer :namespaces
|
90
|
+
|
91
|
+
# Returns the +namespaces+. Defaults to a Hash containing the SOAP envelope namespace.
|
92
|
+
def namespaces
|
93
|
+
@namespaces ||= begin
|
94
|
+
key = env_namespace.blank? ? "xmlns" : "xmlns:#{env_namespace}"
|
95
|
+
{ key => SOAP::Namespace[version] }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Sets the default namespace identifier.
|
100
|
+
attr_writer :namespace_identifier
|
101
|
+
|
102
|
+
# Returns the default namespace identifier.
|
103
|
+
def namespace_identifier
|
104
|
+
@namespace_identifier ||= :wsdl
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns whether all local elements should be namespaced. Might be set to :qualified,
|
108
|
+
# but defaults to :unqualified.
|
109
|
+
def element_form_default
|
110
|
+
@element_form_default ||= :unqualified
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sets whether all local elements should be namespaced.
|
114
|
+
attr_writer :element_form_default
|
115
|
+
|
116
|
+
# Accessor for the default namespace URI.
|
117
|
+
attr_accessor :namespace
|
118
|
+
|
119
|
+
# Accessor for the <tt>Savon::WSSE</tt> object.
|
120
|
+
attr_accessor :wsse
|
121
|
+
|
122
|
+
# Accessor for the SOAP +body+. Expected to be a Hash that can be translated to XML via Gyoku.xml
|
123
|
+
# or any other Object responding to to_s.
|
124
|
+
attr_accessor :body
|
125
|
+
|
126
|
+
# Accepts a +block+ and yields a <tt>Builder::XmlMarkup</tt> object to let you create custom XML.
|
127
|
+
def xml
|
128
|
+
@xml = yield builder if block_given?
|
129
|
+
end
|
130
|
+
|
131
|
+
# Accepts an XML String and lets you specify a completely custom request body.
|
132
|
+
attr_writer :xml
|
133
|
+
|
134
|
+
# Returns the XML for a SOAP request.
|
135
|
+
def to_xml
|
136
|
+
@xml ||= tag(builder, :Envelope, complete_namespaces) do |xml|
|
137
|
+
tag(xml, :Header) { xml << header_for_xml } unless header_for_xml.empty?
|
138
|
+
input.nil? ? tag(xml, :Body) : tag(xml, :Body) { xml.tag!(*input) { xml << body_to_xml } }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Returns a new <tt>Builder::XmlMarkup</tt> object.
|
145
|
+
def builder
|
146
|
+
builder = Builder::XmlMarkup.new
|
147
|
+
builder.instruct!
|
148
|
+
builder
|
149
|
+
end
|
150
|
+
|
151
|
+
# Expects a builder +xml+ instance, a tag +name+ and accepts optional +namespaces+
|
152
|
+
# and a block to create an XML tag.
|
153
|
+
def tag(xml, name, namespaces = {}, &block)
|
154
|
+
return xml.tag! name, namespaces, &block if env_namespace.blank?
|
155
|
+
xml.tag! env_namespace, name, namespaces, &block
|
156
|
+
end
|
157
|
+
|
158
|
+
# Returns the complete Hash of namespaces.
|
159
|
+
def complete_namespaces
|
160
|
+
defaults = SchemaTypes.dup
|
161
|
+
defaults["xmlns:#{namespace_identifier}"] = namespace if namespace
|
162
|
+
defaults.merge namespaces
|
163
|
+
end
|
164
|
+
|
165
|
+
# Returns the SOAP header as an XML String.
|
166
|
+
def header_for_xml
|
167
|
+
@header_for_xml ||= Gyoku.xml(header) + wsse_header
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns the WSSE header or an empty String in case WSSE was not set.
|
171
|
+
def wsse_header
|
172
|
+
wsse.respond_to?(:to_xml) ? wsse.to_xml : ""
|
173
|
+
end
|
174
|
+
|
175
|
+
# Returns the SOAP body as an XML String.
|
176
|
+
def body_to_xml
|
177
|
+
return body.to_s unless body.kind_of? Hash
|
178
|
+
unless namespace_identifier == :wsdl
|
179
|
+
Gyoku.xml body, :element_form_default => element_form_default
|
180
|
+
else
|
181
|
+
Gyoku.xml body, :element_form_default => element_form_default, :namespace => namespace_identifier
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|