johnreitano-savon 0.7.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/CHANGELOG +124 -0
  2. data/README.textile +75 -0
  3. data/Rakefile +45 -0
  4. data/lib/savon/client.rb +84 -0
  5. data/lib/savon/core_ext/datetime.rb +8 -0
  6. data/lib/savon/core_ext/hash.rb +78 -0
  7. data/lib/savon/core_ext/net_http.rb +20 -0
  8. data/lib/savon/core_ext/object.rb +21 -0
  9. data/lib/savon/core_ext/string.rb +47 -0
  10. data/lib/savon/core_ext/symbol.rb +8 -0
  11. data/lib/savon/core_ext/uri.rb +10 -0
  12. data/lib/savon/core_ext.rb +3 -0
  13. data/lib/savon/request.rb +149 -0
  14. data/lib/savon/response.rb +99 -0
  15. data/lib/savon/soap.rb +176 -0
  16. data/lib/savon/wsdl.rb +122 -0
  17. data/lib/savon/wsse.rb +136 -0
  18. data/lib/savon.rb +34 -0
  19. data/spec/basic_spec_helper.rb +12 -0
  20. data/spec/endpoint_helper.rb +22 -0
  21. data/spec/fixtures/response/response_fixture.rb +36 -0
  22. data/spec/fixtures/response/xml/authentication.xml +14 -0
  23. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  24. data/spec/fixtures/response/xml/soap_fault.xml +8 -0
  25. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  26. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  27. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  28. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  29. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  30. data/spec/http_stubs.rb +23 -0
  31. data/spec/integration/http_basic_auth_spec.rb +16 -0
  32. data/spec/integration/server.rb +51 -0
  33. data/spec/savon/client_spec.rb +77 -0
  34. data/spec/savon/core_ext/datetime_spec.rb +12 -0
  35. data/spec/savon/core_ext/hash_spec.rb +138 -0
  36. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  37. data/spec/savon/core_ext/object_spec.rb +40 -0
  38. data/spec/savon/core_ext/string_spec.rb +68 -0
  39. data/spec/savon/core_ext/symbol_spec.rb +11 -0
  40. data/spec/savon/core_ext/uri_spec.rb +15 -0
  41. data/spec/savon/request_spec.rb +89 -0
  42. data/spec/savon/response_spec.rb +137 -0
  43. data/spec/savon/savon_spec.rb +23 -0
  44. data/spec/savon/soap_spec.rb +171 -0
  45. data/spec/savon/wsdl_spec.rb +84 -0
  46. data/spec/savon/wsse_spec.rb +132 -0
  47. data/spec/spec_helper.rb +5 -0
  48. metadata +218 -0
@@ -0,0 +1,47 @@
1
+ class String
2
+
3
+ # Returns a random String of a given +length+.
4
+ def self.random(length = 100)
5
+ (0...length).map { ("a".."z").to_a[rand(26)] }.join
6
+ end
7
+
8
+ # Returns the String in snake_case.
9
+ def snakecase
10
+ str = dup
11
+ str.gsub! /::/, '/'
12
+ str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
13
+ str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
14
+ str.tr! ".", "_"
15
+ str.tr! "-", "_"
16
+ str.downcase!
17
+ str
18
+ end
19
+
20
+ # Returns the String in lowerCamelCase.
21
+ def lower_camelcase
22
+ str = dup
23
+ str.gsub!(/\/(.?)/) { "::#{$1.upcase}" }
24
+ str.gsub!(/(?:_+|-+)([a-z])/) { $1.upcase }
25
+ str.gsub!(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
26
+ str
27
+ end
28
+
29
+ # Returns the String without namespace.
30
+ def strip_namespace
31
+ gsub /(.+:)(.+)/, '\2'
32
+ end
33
+
34
+ # Translates SOAP response values to more convenient Ruby Objects.
35
+ def map_soap_response
36
+ return DateTime.parse( self ) if Savon::SOAPDateTimeRegexp === self
37
+ return true if self.strip.downcase == "true"
38
+ return false if self.strip.downcase == "false"
39
+ self
40
+ end
41
+
42
+ # Returns the String as a SOAP request compliant value.
43
+ def to_soap_value
44
+ to_s
45
+ end
46
+
47
+ end
@@ -0,0 +1,8 @@
1
+ class Symbol
2
+
3
+ # Returns the Symbol as a lowerCamelCase String.
4
+ def to_soap_key
5
+ to_s.lower_camelcase
6
+ end
7
+
8
+ end
@@ -0,0 +1,10 @@
1
+ module URI
2
+ class HTTP
3
+
4
+ # Returns whether the URI hints to SSL.
5
+ def ssl?
6
+ /^https/ === @scheme
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ %w(object string symbol datetime hash uri net_http).each do |file|
2
+ require File.dirname(__FILE__) + "/core_ext/#{file}"
3
+ end
@@ -0,0 +1,149 @@
1
+ module Savon
2
+
3
+ # == Savon::Request
4
+ #
5
+ # Handles both WSDL and SOAP HTTP requests.
6
+ class Request
7
+
8
+ # Content-Types by SOAP version.
9
+ ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
10
+
11
+ # Whether to log HTTP requests.
12
+ @@log = true
13
+
14
+ # The default logger.
15
+ @@logger = Logger.new STDOUT
16
+
17
+ # The default log level.
18
+ @@log_level = :debug
19
+
20
+ # Sets whether to log HTTP requests.
21
+ def self.log=(log)
22
+ @@log = log
23
+ end
24
+
25
+ # Returns whether to log HTTP requests.
26
+ def self.log?
27
+ @@log
28
+ end
29
+
30
+ # Sets the logger.
31
+ def self.logger=(logger)
32
+ @@logger = logger
33
+ end
34
+
35
+ # Returns the logger.
36
+ def self.logger
37
+ @@logger
38
+ end
39
+
40
+ # Sets the log level.
41
+ def self.log_level=(log_level)
42
+ @@log_level = log_level
43
+ end
44
+
45
+ # Returns the log level.
46
+ def self.log_level
47
+ @@log_level
48
+ end
49
+
50
+ # Expects a SOAP +endpoint+ String. Also accepts an optional Hash
51
+ # of +options+ for specifying a proxy server.
52
+ def initialize(endpoint, options = {})
53
+ @endpoint = URI endpoint
54
+ @proxy = options[:proxy] ? URI(options[:proxy]) : URI("")
55
+ end
56
+
57
+ # Returns the endpoint URI.
58
+ attr_reader :endpoint
59
+
60
+ # Returns the proxy URI.
61
+ attr_reader :proxy
62
+
63
+ # Returns the HTTP headers for a SOAP request.
64
+ def headers
65
+ @headers ||= {}
66
+ end
67
+
68
+ # Sets the HTTP headers for a SOAP request.
69
+ def headers=(headers)
70
+ @headers = headers if headers.kind_of? Hash
71
+ end
72
+
73
+ # Sets the +username+ and +password+ for HTTP basic authentication.
74
+ def basic_auth(username, password)
75
+ @basic_auth = [username, password]
76
+ end
77
+
78
+ # Retrieves WSDL document and returns the Net::HTTP response.
79
+ def wsdl
80
+ log "Retrieving WSDL from: #{@endpoint}"
81
+ http.endpoint @endpoint.host, @endpoint.port
82
+ http.use_ssl = @endpoint.ssl?
83
+ http.start { |h| h.request request(:wsdl) }
84
+ end
85
+
86
+ # Executes a SOAP request using a given Savon::SOAP instance and
87
+ # returns the Net::HTTP response.
88
+ def soap(soap)
89
+ @soap = soap
90
+ http.endpoint @soap.endpoint.host, @soap.endpoint.port
91
+ http.use_ssl = @soap.endpoint.ssl?
92
+
93
+ log_request
94
+ @response = http.start do |h|
95
+ h.request request(:soap) { |request| request.body = @soap.to_xml }
96
+ end
97
+ log_response
98
+ @response
99
+ end
100
+
101
+ # Returns the Net::HTTP object.
102
+ def http
103
+ @http ||= Net::HTTP::Proxy(@proxy.host, @proxy.port).new @endpoint.host, @endpoint.port
104
+ end
105
+
106
+ private
107
+
108
+ # Logs the SOAP request.
109
+ def log_request
110
+ log "SOAP request: #{@soap.endpoint}"
111
+ log headers.merge(soap_headers).map { |key, value| "#{key}: #{value}" }.join(", ")
112
+ log @soap.to_xml
113
+ end
114
+
115
+ # Logs the SOAP response.
116
+ def log_response
117
+ log "SOAP response (status #{@response.code}):"
118
+ log @response.body
119
+ end
120
+
121
+ # Returns a Net::HTTP request for a given +type+. Yields the request
122
+ # to an optional block.
123
+ def request(type)
124
+ request = case type
125
+ when :wsdl then Net::HTTP::Get.new @endpoint.to_s
126
+ when :soap then Net::HTTP::Post.new @soap.endpoint.to_s, headers.merge(soap_headers)
127
+ end
128
+ request.basic_auth *@basic_auth if @basic_auth
129
+ yield request if block_given?
130
+ request
131
+ end
132
+
133
+ # Returns a Hash containing the SOAP headers for an HTTP request.
134
+ def soap_headers
135
+ { "Content-Type" => ContentType[@soap.version], "SOAPAction" => @soap.action }
136
+ end
137
+
138
+ # Logs a given +message+.
139
+ def log(message)
140
+ self.class.logger.send self.class.log_level, message if log?
141
+ end
142
+
143
+ # Returns whether to log.
144
+ def log?
145
+ self.class.log? && self.class.logger.respond_to?(self.class.log_level)
146
+ end
147
+
148
+ end
149
+ end
@@ -0,0 +1,99 @@
1
+ module Savon
2
+
3
+ # == Savon::Response
4
+ #
5
+ # Represents the HTTP and SOAP response.
6
+ class Response
7
+
8
+ # The global setting of whether to raise errors.
9
+ @@raise_errors = true
10
+
11
+ # Sets the global setting of whether to raise errors.
12
+ def self.raise_errors=(raise_errors)
13
+ @@raise_errors = raise_errors
14
+ end
15
+
16
+ # Returns the global setting of whether to raise errors.
17
+ def self.raise_errors?
18
+ @@raise_errors
19
+ end
20
+
21
+ # Expects a Net::HTTPResponse and handles errors.
22
+ def initialize(http)
23
+ @http = http
24
+
25
+ handle_soap_fault
26
+ handle_http_error
27
+ end
28
+
29
+ # Returns whether there was a SOAP fault.
30
+ def soap_fault?
31
+ @soap_fault ? true : false
32
+ end
33
+
34
+ # Returns the SOAP fault message.
35
+ attr_reader :soap_fault
36
+
37
+ # Returns whether there was an HTTP error.
38
+ def http_error?
39
+ @http_error ? true : false
40
+ end
41
+
42
+ # Returns the HTTP error message.
43
+ attr_reader :http_error
44
+
45
+ # Returns the SOAP response body as a Hash.
46
+ def to_hash
47
+ @body ||= (Crack::XML.parse(@http.body) rescue {}).find_soap_body
48
+ end
49
+
50
+ # Returns the SOAP response XML.
51
+ def to_xml
52
+ @http.body
53
+ end
54
+
55
+ # Returns the HTTP response object.
56
+ attr_reader :http
57
+
58
+ alias :to_s :to_xml
59
+
60
+ private
61
+
62
+ # Handles SOAP faults. Raises a Savon::SOAPFault unless the default
63
+ # behavior of raising errors was turned off.
64
+ def handle_soap_fault
65
+ if soap_fault_message
66
+ @soap_fault = soap_fault_message
67
+ raise Savon::SOAPFault, @soap_fault if self.class.raise_errors?
68
+ end
69
+ end
70
+
71
+ # Returns a SOAP fault message in case a SOAP fault was found.
72
+ def soap_fault_message
73
+ @soap_fault_message ||= soap_fault_message_by_version to_hash[:fault]
74
+ end
75
+
76
+ # Expects a Hash that might contain information about a SOAP fault.
77
+ # Returns the SOAP fault message in case one was found.
78
+ def soap_fault_message_by_version(soap_fault)
79
+ return unless soap_fault
80
+
81
+ if soap_fault.keys.include? :faultcode
82
+ "(#{soap_fault[:faultcode]}) #{soap_fault[:faultstring]}"
83
+ elsif soap_fault.keys.include? :code
84
+ "(#{soap_fault[:code][:value]}) #{soap_fault[:reason][:text]}"
85
+ end
86
+ end
87
+
88
+ # Handles HTTP errors. Raises a Savon::HTTPError unless the default
89
+ # behavior of raising errors was turned off.
90
+ def handle_http_error
91
+ if @http.code.to_i >= 300
92
+ @http_error = "#{@http.message} (#{@http.code})"
93
+ @http_error << ": #{@http.body}" unless @http.body.empty?
94
+ raise Savon::HTTPError, http_error if self.class.raise_errors?
95
+ end
96
+ end
97
+
98
+ end
99
+ end
data/lib/savon/soap.rb ADDED
@@ -0,0 +1,176 @@
1
+ module Savon
2
+
3
+ # == Savon::SOAP
4
+ #
5
+ # Represents the SOAP parameters and envelope.
6
+ class SOAP
7
+
8
+ # SOAP namespaces by SOAP version.
9
+ SOAPNamespace = {
10
+ 1 => "http://schemas.xmlsoap.org/soap/envelope/",
11
+ 2 => "http://www.w3.org/2003/05/soap-envelope"
12
+ }
13
+
14
+ # Content-Types by SOAP version.
15
+ ContentType = { 1 => "text/xml", 2 => "application/soap+xml" }
16
+
17
+ # The global SOAP version.
18
+ @@version = 1
19
+
20
+ # Returns the global SOAP version.
21
+ def self.version
22
+ @@version
23
+ end
24
+
25
+ # Sets the global SOAP version.
26
+ def self.version=(version)
27
+ @@version = version if Savon::SOAPVersions.include? version
28
+ end
29
+
30
+ # Sets the global SOAP header. Expected to be a Hash that can be translated
31
+ # to XML via Hash.to_soap_xml or any other Object responding to to_s.
32
+ def self.header=(header)
33
+ @@header = header
34
+ end
35
+
36
+ # Returns the global SOAP header. Defaults to an empty Hash.
37
+ def self.header
38
+ @@header ||= {}
39
+ end
40
+
41
+ # Sets the global namespaces. Expected to be a Hash containing the
42
+ # namespaces (keys) and the corresponding URI's (values).
43
+ def self.namespaces=(namespaces)
44
+ @@namespaces = namespaces if namespaces.kind_of? Hash
45
+ end
46
+
47
+ # Returns the global namespaces. A Hash containing the namespaces (keys)
48
+ # and the corresponding URI's (values).
49
+ def self.namespaces
50
+ @@namespaces ||= {}
51
+ end
52
+
53
+ # Initialzes the SOAP object.
54
+ def initialize
55
+ @builder = Builder::XmlMarkup.new
56
+ end
57
+
58
+ # Sets the WSSE options.
59
+ attr_writer :wsse
60
+
61
+ # Sets the SOAP action.
62
+ attr_writer :action
63
+
64
+ # Returns the SOAP action.
65
+ def action
66
+ @action ||= ""
67
+ end
68
+
69
+ # Sets the SOAP input.
70
+ attr_writer :input
71
+
72
+ # Returns the SOAP input.
73
+ def input
74
+ @input ||= ""
75
+ end
76
+
77
+ # Accessor for the SOAP endpoint.
78
+ attr_accessor :endpoint
79
+
80
+ # Sets the SOAP header. Expected to be a Hash that can be translated
81
+ # to XML via Hash.to_soap_xml or any other Object responding to to_s.
82
+ attr_writer :header
83
+
84
+ # Returns the SOAP header. Defaults to an empty Hash.
85
+ def header
86
+ @header ||= {}
87
+ end
88
+
89
+ # Sets the SOAP body. Expected to be a Hash that can be translated to
90
+ # XML via Hash.to_soap_xml or any other Object responding to to_s.
91
+ attr_writer :body
92
+
93
+ # Sets the namespaces. Expected to be a Hash containing the namespaces
94
+ # (keys) and the corresponding URI's (values).
95
+ attr_writer :namespaces
96
+
97
+ # Returns the namespaces. A Hash containing the namespaces (keys)
98
+ # and the corresponding URI's (values).
99
+ def namespaces
100
+ @namespaces ||= { "xmlns:env" => SOAPNamespace[version] }
101
+ end
102
+
103
+ # Convenience method for setting the "xmlns:wsdl" namespace.
104
+ def namespace=(namespace)
105
+ namespaces["xmlns:wsdl"] = namespace
106
+ end
107
+
108
+ # Sets the SOAP version.
109
+ def version=(version)
110
+ @version = version if Savon::SOAPVersions.include? version
111
+ end
112
+
113
+ # Returns the SOAP version. Defaults to the global default.
114
+ def version
115
+ @version ||= self.class.version
116
+ end
117
+
118
+ # Returns the SOAP envelope XML.
119
+ def to_xml
120
+ unless @xml_body
121
+ @xml_body = @builder.env :Envelope, all_namespaces do |xml|
122
+ xml_header xml
123
+ xml_body xml
124
+ end
125
+ end
126
+ @xml_body
127
+ end
128
+
129
+ private
130
+
131
+ # Adds a SOAP XML header to a given +xml+ Object.
132
+ def xml_header(xml)
133
+ xml.env(:Header) do
134
+ xml << all_header + wsse_header
135
+ end
136
+ end
137
+
138
+ # Returns a String containing the global and per request header.
139
+ def all_header
140
+ if self.class.header.kind_of?(Hash) && header.kind_of?(Hash)
141
+ self.class.header.merge(header).to_soap_xml
142
+ else
143
+ self.class.header.to_s + header.to_s
144
+ end
145
+ end
146
+
147
+ # Adds a SOAP XML body to a given +xml+ Object.
148
+ def xml_body(xml)
149
+ xml.env(:Body) do
150
+ xml.tag!(:wsdl, *input_array) do
151
+ xml << (@body.to_soap_xml rescue @body.to_s)
152
+ end
153
+ end
154
+ end
155
+
156
+ # Returns a Hash containing the global and per request namespaces.
157
+ def all_namespaces
158
+ self.class.namespaces.merge namespaces
159
+ end
160
+
161
+ # Returns an Array of SOAP input names to append to the :wsdl namespace.
162
+ # Defaults to use the name of the SOAP action and may be an empty Array
163
+ # in case the specified SOAP input seems invalid.
164
+ def input_array
165
+ return [input.to_sym] unless input.blank?
166
+ return [action.to_sym] unless action.blank?
167
+ []
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?(:header) ? @wsse.header : ""
173
+ end
174
+
175
+ end
176
+ end