s-savon 0.8.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|