tictoc-savon 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.autotest +5 -0
  2. data/CHANGELOG +176 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +64 -0
  5. data/Rakefile +50 -0
  6. data/lib/savon.rb +35 -0
  7. data/lib/savon/client.rb +131 -0
  8. data/lib/savon/core_ext.rb +8 -0
  9. data/lib/savon/core_ext/array.rb +31 -0
  10. data/lib/savon/core_ext/datetime.rb +10 -0
  11. data/lib/savon/core_ext/hash.rb +107 -0
  12. data/lib/savon/core_ext/net_http.rb +19 -0
  13. data/lib/savon/core_ext/object.rb +16 -0
  14. data/lib/savon/core_ext/string.rb +69 -0
  15. data/lib/savon/core_ext/symbol.rb +8 -0
  16. data/lib/savon/core_ext/uri.rb +10 -0
  17. data/lib/savon/logger.rb +56 -0
  18. data/lib/savon/request.rb +138 -0
  19. data/lib/savon/response.rb +174 -0
  20. data/lib/savon/soap.rb +302 -0
  21. data/lib/savon/version.rb +5 -0
  22. data/lib/savon/wsdl.rb +137 -0
  23. data/lib/savon/wsdl_stream.rb +85 -0
  24. data/lib/savon/wsse.rb +163 -0
  25. data/spec/basic_spec_helper.rb +11 -0
  26. data/spec/endpoint_helper.rb +23 -0
  27. data/spec/fixtures/gzip/gzip_response_fixture.rb +7 -0
  28. data/spec/fixtures/gzip/message.gz +0 -0
  29. data/spec/fixtures/response/response_fixture.rb +36 -0
  30. data/spec/fixtures/response/xml/authentication.xml +14 -0
  31. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  32. data/spec/fixtures/response/xml/soap_fault.xml +8 -0
  33. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  34. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  35. data/spec/fixtures/wsdl/wsdl_fixture.yml +42 -0
  36. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  37. data/spec/fixtures/wsdl/xml/geotrust.xml +156 -0
  38. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  39. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  40. data/spec/http_stubs.rb +26 -0
  41. data/spec/integration/http_basic_auth_spec.rb +16 -0
  42. data/spec/integration/server.rb +51 -0
  43. data/spec/savon/client_spec.rb +86 -0
  44. data/spec/savon/core_ext/array_spec.rb +49 -0
  45. data/spec/savon/core_ext/datetime_spec.rb +21 -0
  46. data/spec/savon/core_ext/hash_spec.rb +190 -0
  47. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  48. data/spec/savon/core_ext/object_spec.rb +34 -0
  49. data/spec/savon/core_ext/string_spec.rb +99 -0
  50. data/spec/savon/core_ext/symbol_spec.rb +12 -0
  51. data/spec/savon/core_ext/uri_spec.rb +19 -0
  52. data/spec/savon/request_spec.rb +117 -0
  53. data/spec/savon/response_spec.rb +179 -0
  54. data/spec/savon/soap_spec.rb +202 -0
  55. data/spec/savon/wsdl_spec.rb +107 -0
  56. data/spec/savon/wsse_spec.rb +132 -0
  57. data/spec/spec.opts +4 -0
  58. data/spec/spec_helper.rb +5 -0
  59. metadata +229 -0
@@ -0,0 +1,8 @@
1
+ require "savon/core_ext/object"
2
+ require "savon/core_ext/string"
3
+ require "savon/core_ext/symbol"
4
+ require "savon/core_ext/datetime"
5
+ require "savon/core_ext/array"
6
+ require "savon/core_ext/hash"
7
+ require "savon/core_ext/uri"
8
+ require "savon/core_ext/net_http"
@@ -0,0 +1,31 @@
1
+ class Array
2
+
3
+ # Translates the Array into SOAP compatible XML. See: Hash.to_soap_xml.
4
+ def to_soap_xml(key, escape_xml = true, attributes = {})
5
+ xml = Builder::XmlMarkup.new
6
+
7
+ each_with_index do |item, index|
8
+ attrs = tag_attributes attributes, index
9
+ case item
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
14
+
15
+ xml.target!
16
+ end
17
+
18
+ private
19
+
20
+ # Takes a Hash of +attributes+ and the +index+ for which to return attributes
21
+ # for duplicate tags.
22
+ def tag_attributes(attributes, index)
23
+ return {} if attributes.empty?
24
+
25
+ attributes.inject({}) do |hash, (key, value)|
26
+ value = value[index] if value.kind_of? Array
27
+ hash.merge key => value
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,10 @@
1
+ class DateTime
2
+
3
+ # Returns the DateTime as an xs:dateTime formatted String.
4
+ def to_soap_value
5
+ strftime Savon::SOAP::DateTimeFormat
6
+ end
7
+
8
+ alias_method :to_soap_value!, :to_soap_value
9
+
10
+ end
@@ -0,0 +1,107 @@
1
+ class Hash
2
+
3
+ # Returns the values from the soap:Body element or an empty Hash in case the soap:Body tag could
4
+ # not be found.
5
+ def find_soap_body
6
+ envelope = self[keys.first] || {}
7
+ body_key = envelope.keys.find { |key| /.+:Body/ =~ key } rescue nil
8
+ body_key ? envelope[body_key].map_soap_response : {}
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!) || {}
61
+
62
+ order.each do |key|
63
+ attrs = attributes[key] || {}
64
+ value = self[key]
65
+ escape_xml = key.to_s[-1, 1] != "!"
66
+ key = key.to_soap_key
67
+
68
+ case value
69
+ when Array then xml << value.to_soap_xml(key, escape_xml, attrs)
70
+ when Hash then xml.tag!(key, attrs) { xml << value.to_soap_xml }
71
+ else xml.tag!(key, attrs) { xml << (escape_xml ? value.to_soap_value : value.to_soap_value!) }
72
+ end
73
+ end
74
+
75
+ xml.target!
76
+ end
77
+
78
+ # Maps keys and values of a Hash created from SOAP response XML to more convenient Ruby Objects.
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
85
+ end
86
+
87
+ hash.merge key.strip_namespace.snakecase.to_sym => value
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ # Deletes and returns an Array of keys stored under the :order! key. Defaults to return the actual
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
99
+
100
+ missing, spurious = keys - order, order - keys
101
+ raise ArgumentError, "Missing elements in :order! #{missing.inspect}" unless missing.empty?
102
+ raise ArgumentError, "Spurious elements in :order! #{spurious.inspect}" unless spurious.empty?
103
+
104
+ order
105
+ end
106
+
107
+ end
@@ -0,0 +1,19 @@
1
+ module Net
2
+ class HTTP
3
+
4
+ # Sets the endpoint +address+ and +port+.
5
+ def endpoint(address, port)
6
+ @address, @port = address, port
7
+ end
8
+
9
+ # Convenience method for setting SSL client authentication through a Hash of +options+.
10
+ def ssl_client_auth(options)
11
+ self.use_ssl = true
12
+ self.cert = options[:cert] if options[:cert]
13
+ self.key = options[:key] if options[:key]
14
+ self.ca_file = options[:ca_file] if options[:ca_file]
15
+ self.verify_mode = options[:verify_mode] if options[:verify_mode]
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ class Object
2
+
3
+ # Returns +true+ if the Object is nil, false or empty. Implementation from ActiveSupport.
4
+ def blank?
5
+ respond_to?(:empty?) ? empty? : !self
6
+ end unless defined? blank?
7
+
8
+ # Returns the Object as a SOAP request compliant value.
9
+ def to_soap_value
10
+ return to_s unless respond_to? :to_datetime
11
+ to_datetime.to_soap_value
12
+ end
13
+
14
+ alias_method :to_soap_value!, :to_soap_value
15
+
16
+ end
@@ -0,0 +1,69 @@
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 whether the String starts with a given +prefix+.
30
+ def starts_with?(prefix)
31
+ prefix = prefix.to_s
32
+ self[0, prefix.length] == prefix
33
+ end unless defined? starts_with?
34
+
35
+ # Returns whether the String ends with a given +suffix+.
36
+ def ends_with?(suffix)
37
+ suffix = suffix.to_s
38
+ self[-suffix.length, suffix.length] == suffix
39
+ end unless defined? ends_with?
40
+
41
+ # Returns the String without namespace.
42
+ def strip_namespace
43
+ split(":").last
44
+ end
45
+
46
+ # Translates SOAP response values to Ruby Objects.
47
+ def map_soap_response
48
+ return DateTime.parse(self) if Savon::SOAP::DateTimeRegexp === self
49
+ return true if self.strip.downcase == "true"
50
+ return false if self.strip.downcase == "false"
51
+ self
52
+ end
53
+
54
+ # Returns the Object as a SOAP request compliant key.
55
+ def to_soap_key
56
+ self[-1, 1] == "!" ? chop : self
57
+ end
58
+
59
+ # Returns the String as a SOAP value. Escapes special characters for XML.
60
+ def to_soap_value
61
+ CGI.escapeHTML self
62
+ end
63
+
64
+ # Convert the String into a SOAP value without escaping special characters.
65
+ def to_soap_value!
66
+ self
67
+ end
68
+
69
+ 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.to_soap_key.lower_camelcase
6
+ end
7
+
8
+ end
@@ -0,0 +1,10 @@
1
+ module URI
2
+ class Generic
3
+
4
+ # Returns whether the URI hints to SSL.
5
+ def ssl?
6
+ !@scheme ? nil : @scheme.starts_with?("https")
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,56 @@
1
+ module Savon
2
+
3
+ # = Savon::Logger
4
+ #
5
+ # Savon::Logger can be mixed into classes to provide logging behavior.
6
+ #
7
+ # By default, the Logger mixin uses {Ruby's Logger}[http://ruby-doc.org/stdlib/libdoc/logger/rdoc/]
8
+ # from the standard library, a log level of :debug and is pointing to STDOUT.
9
+ module Logger
10
+
11
+ module ClassMethods
12
+
13
+ # Sets whether to log.
14
+ def log=(log)
15
+ @log = log
16
+ end
17
+
18
+ # Returns whether to log. Defaults to +true+.
19
+ def log?
20
+ @log != false
21
+ end
22
+
23
+ # Sets the logger.
24
+ def logger=(logger)
25
+ @logger = logger
26
+ end
27
+
28
+ # Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
29
+ def logger
30
+ @logger ||= ActiveRecord::Base.logger rescue ::Logger.new STDOUT
31
+ end
32
+
33
+ # Sets the log level.
34
+ def log_level=(log_level)
35
+ @log_level = log_level
36
+ end
37
+
38
+ # Returns the log level. Defaults to +debug+.
39
+ def log_level
40
+ @log_level ||= :debug
41
+ end
42
+
43
+ end
44
+
45
+ # Extends the class including this module with its ClassMethods.
46
+ def self.included(base)
47
+ base.extend ClassMethods
48
+ end
49
+
50
+ # Logs a given +message+.
51
+ def log(message)
52
+ self.class.logger.send self.class.log_level, message if self.class.log?
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,138 @@
1
+ module Savon
2
+
3
+ # = Savon::Request
4
+ #
5
+ # Savon::Request handles both WSDL and SOAP requests.
6
+ #
7
+ # == The Net::HTTP object
8
+ #
9
+ # You can access the Net::HTTP object used for both WSDL and SOAP requests via:
10
+ #
11
+ # client.request.http
12
+ #
13
+ # Here's an example of how to set open and read timeouts on the Net::HTTP object.
14
+ #
15
+ # client.request.http.open_timeout = 30
16
+ # client.request.http.read_timeout = 30
17
+ #
18
+ # Please refer to the {Net::HTTP documentation}[http://ruby-doc.org/stdlib/libdoc/net/http/rdoc/]
19
+ # for more information.
20
+ #
21
+ # == HTTP basic authentication
22
+ #
23
+ # Setting credentials for HTTP basic authentication:
24
+ #
25
+ # client.request.basic_auth "username", "password"
26
+ #
27
+ # == SSL client authentication
28
+ #
29
+ # You can use the methods provided by Net::HTTP to set SSL client authentication or use a shortcut:
30
+ #
31
+ # client.request.http.ssl_client_auth(
32
+ # :cert => OpenSSL::X509::Certificate.new(File.read("client_cert.pem")),
33
+ # :key => OpenSSL::PKey::RSA.new(File.read("client_key.pem"), "password if one exists"),
34
+ # :ca_file => "cacert.pem",
35
+ # :verify_mode => OpenSSL::SSL::VERIFY_PEER
36
+ # )
37
+ #
38
+ # == HTTP headers
39
+ #
40
+ # There's an accessor for the Hash of HTTP headers sent with any SOAP call:
41
+ #
42
+ # client.request.headers["custom"] = "header"
43
+ class Request
44
+ include Logger
45
+
46
+ # Content-Types by SOAP version.
47
+ ContentType = { 1 => "text/xml;charset=UTF-8", 2 => "application/soap+xml;charset=UTF-8" }
48
+
49
+ # Expects a WSDL or SOAP +endpoint+ and accepts a custom +proxy+ address.
50
+ def initialize(endpoint, options = {})
51
+ @endpoint = URI endpoint
52
+ @proxy = URI options[:proxy] || ""
53
+ headers["Accept-encoding"] = "gzip,deflate" if options[:gzip]
54
+ end
55
+
56
+ # Returns the endpoint URI.
57
+ attr_reader :endpoint
58
+
59
+ # Returns the proxy URI.
60
+ attr_reader :proxy
61
+
62
+ # Returns the HTTP headers for a SOAP request.
63
+ def headers
64
+ @headers ||= {}
65
+ end
66
+
67
+ # Sets the HTTP headers for a SOAP request.
68
+ def headers=(headers)
69
+ @headers = headers if headers.kind_of? Hash
70
+ end
71
+
72
+ # Sets the +username+ and +password+ for HTTP basic authentication.
73
+ def basic_auth(username, password)
74
+ @basic_auth = [username, password]
75
+ end
76
+
77
+ # Retrieves WSDL document and returns the Net::HTTP response.
78
+ def wsdl
79
+ log "Retrieving WSDL from: #{@endpoint}"
80
+ http.endpoint @endpoint.host, @endpoint.port
81
+ http.use_ssl = @endpoint.ssl?
82
+ http.start { |h| h.request request(:wsdl) }
83
+ end
84
+
85
+ # Executes a SOAP request using a given Savon::SOAP instance and returns the Net::HTTP response.
86
+ def soap(soap)
87
+ @soap = soap
88
+ http.endpoint @soap.endpoint.host, @soap.endpoint.port
89
+ http.use_ssl = @soap.endpoint.ssl?
90
+
91
+ log_request
92
+ @response = http.start do |h|
93
+ h.request request(:soap) { |request| request.body = @soap.to_xml }
94
+ end
95
+ log_response
96
+ @response
97
+ end
98
+
99
+ # Returns the Net::HTTP object.
100
+ def http
101
+ @http ||= Net::HTTP::Proxy(@proxy.host, @proxy.port).new @endpoint.host, @endpoint.port
102
+ end
103
+
104
+ private
105
+
106
+ # Logs the SOAP request.
107
+ def log_request
108
+ log "SOAP request: #{@soap.endpoint}"
109
+ log soap_headers.merge(headers).map { |key, value| "#{key}: #{value}" }.join(", ")
110
+ log @soap.to_xml
111
+ end
112
+
113
+ # Logs the SOAP response.
114
+ def log_response
115
+ log "SOAP response (status #{@response.code}):"
116
+ log @response.body
117
+ end
118
+
119
+ # Returns a Net::HTTP request for a given +type+. Yields the request to an optional block.
120
+ def request(type)
121
+ request = case type
122
+ when :wsdl then Net::HTTP::Get.new @endpoint.request_uri
123
+ when :soap then Net::HTTP::Post.new @soap.endpoint.request_uri, soap_headers.merge(headers)
124
+ end
125
+
126
+ request.basic_auth(*@basic_auth) if @basic_auth
127
+ yield request if block_given?
128
+ request
129
+ end
130
+
131
+ # Returns a Hash containing the SOAP headers for an HTTP request.
132
+ def soap_headers
133
+ { "Content-Type" => ContentType[@soap.version], "SOAPAction" => @soap.action }
134
+ end
135
+
136
+ end
137
+ end
138
+