hoopla-savon 0.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/CHANGELOG +156 -0
  2. data/README.rdoc +64 -0
  3. data/Rakefile +51 -0
  4. data/lib/savon/client.rb +112 -0
  5. data/lib/savon/core_ext/array.rb +31 -0
  6. data/lib/savon/core_ext/datetime.rb +8 -0
  7. data/lib/savon/core_ext/hash.rb +102 -0
  8. data/lib/savon/core_ext/net_http.rb +19 -0
  9. data/lib/savon/core_ext/object.rb +19 -0
  10. data/lib/savon/core_ext/string.rb +66 -0
  11. data/lib/savon/core_ext/symbol.rb +8 -0
  12. data/lib/savon/core_ext/uri.rb +10 -0
  13. data/lib/savon/core_ext.rb +8 -0
  14. data/lib/savon/logger.rb +56 -0
  15. data/lib/savon/request.rb +135 -0
  16. data/lib/savon/response.rb +155 -0
  17. data/lib/savon/soap.rb +302 -0
  18. data/lib/savon/wsdl.rb +142 -0
  19. data/lib/savon/wsdl_stream.rb +85 -0
  20. data/lib/savon/wsse.rb +163 -0
  21. data/lib/savon.rb +32 -0
  22. data/spec/basic_spec_helper.rb +11 -0
  23. data/spec/endpoint_helper.rb +23 -0
  24. data/spec/fixtures/response/response_fixture.rb +36 -0
  25. data/spec/fixtures/response/xml/authentication.xml +14 -0
  26. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  27. data/spec/fixtures/response/xml/soap_fault.xml +8 -0
  28. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  29. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  30. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  31. data/spec/fixtures/wsdl/xml/geotrust.xml +156 -0
  32. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  33. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  34. data/spec/http_stubs.rb +27 -0
  35. data/spec/integration/http_basic_auth_spec.rb +16 -0
  36. data/spec/integration/server.rb +51 -0
  37. data/spec/savon/client_spec.rb +81 -0
  38. data/spec/savon/core_ext/array_spec.rb +19 -0
  39. data/spec/savon/core_ext/datetime_spec.rb +12 -0
  40. data/spec/savon/core_ext/hash_spec.rb +178 -0
  41. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  42. data/spec/savon/core_ext/object_spec.rb +40 -0
  43. data/spec/savon/core_ext/string_spec.rb +87 -0
  44. data/spec/savon/core_ext/symbol_spec.rb +11 -0
  45. data/spec/savon/core_ext/uri_spec.rb +19 -0
  46. data/spec/savon/request_spec.rb +93 -0
  47. data/spec/savon/response_spec.rb +137 -0
  48. data/spec/savon/soap_spec.rb +204 -0
  49. data/spec/savon/wsdl_spec.rb +110 -0
  50. data/spec/savon/wsse_spec.rb +132 -0
  51. data/spec/spec_helper.rb +11 -0
  52. metadata +206 -0
data/CHANGELOG ADDED
@@ -0,0 +1,156 @@
1
+ == 0.7.6 (2010-03-21)
2
+ * Renamed
3
+ * Moved documentation from the Github Wiki to the actual class files and established a much nicer
4
+ documentation combining examples and implementation (using Hanna) at: http://savon.rubiii.com
5
+ * Added Savon::Client#call as a workaround for dispatching calls to SOAP actions named after
6
+ existing methods. Fix for issue #48.
7
+ * Add support for specifying attributes for duplicate tags (via Hash values as Arrays). Fix for issue #45.
8
+ * Fix for issue #41 (Escape special characters (e.g. &) for XML requests).
9
+ * Fix for issue #39 and #49. Added Savon::SOAP#xml which let's you specify completely custom SOAP request XML.
10
+
11
+ == 0.7.5 (2010-02-19)
12
+ * Fix for issue #34 (soap_actions returns empty for wsdl12).
13
+ * Fix for issue #36 (Custom WSDL actions broken).
14
+ * Added feature requested in issue #35 (Setting an attribute on an element?).
15
+ * Changed the key for specifying the order of tags from :@inorder to :order!
16
+
17
+ == 0.7.4 (2010-02-02)
18
+ * Fix for issue #33 (undefined method 'start_with?').
19
+
20
+ == 0.7.3 (2010-01-31)
21
+ * Added support for Geotrust-style WSDL documents (Julian Kornberger <github.corny@digineo.de>).
22
+ * Make HTTP requests include path and query only. This was breaking requests via proxy as scheme and host
23
+ were repeated (Adrian Mugnolo <adrian@mugnolo.com>)
24
+ * Avoid warning on 1.8.7 and 1.9.1 (Adrian Mugnolo <adrian@mugnolo.com>).
25
+ * Fix for issue #29 (WSSE Created Bug?). Default to UTC to xs:dateTime value for WSSE authentication.
26
+ * Fix for issue #28 (Undefined Method ssl? on URI::Generic).
27
+ * Fix for issue #27 (http content-type defaults to utf-8). The Content-Type now defaults to UTF-8.
28
+ * Modification to allow assignment of an Array with an input name and an optional Hash of values to soap.input.
29
+ Patches issue #30 (stanleydrew <andrewmbenton@gmail.com>).
30
+ * Fix for issue #25 (header-tag should not be sent if not set).
31
+
32
+ == 0.7.2 (2010-01-17)
33
+ * Exposed the Net::HTTP response (added by Kevin Ingolfsland). Use the "http" accessor (response.http) on your
34
+ Savon::Response to access the Net::HTTP response object.
35
+ * Fix for issue #21 (savon is stripping ?SOAP off the end of WSDL locations).
36
+ * Fix for issue #22 (REXML::ParseException parsing 401 Unauthorized response body).
37
+ * Fix for issue #19 (Unable to set attribute in name-spaced WSSE password element).
38
+ * Added support for global header and namespaces. See issue #9 (Setting headers and namespaces).
39
+
40
+ == 0.7.1 (2010-01-10)
41
+ * The Hash of HTTP headers for SOAP calls is now public via Savon::Request#headers.
42
+ Patch for: http://github.com/rubiii/savon/issues/#issue/8
43
+
44
+ == 0.7.0 (2010-01-09)
45
+ This version comes with several changes to the public API!
46
+ Pay attention to the following list and read the updated Wiki: http://wiki.github.com/rubiii/savon
47
+
48
+ * Changed how Savon::WSDL can be disabled. Instead of disabling the WSDL globally/per request via two
49
+ different methods, you now simply append an exclamation mark (!) to your SOAP call: client.get_all_users!
50
+ Make sure you know what you're doing because when the WSDL is disabled, Savon does not know about which
51
+ SOAP actions are valid and just dispatches everything.
52
+ * The Net::HTTP object used by Savon::Request to retrieve WSDL documents and execute SOAP calls is now public.
53
+ While this makes the library even more flexible, it also comes with two major changes:
54
+ * SSL client authentication needs to be defined directly on the Net::HTTP object:
55
+ client.request.http.client_cert = ...
56
+ I added a shortcut method for setting all options through a Hash similar to the previous implementation:
57
+ client.request.http.ssl_client_auth :client_cert => ...
58
+ * Open and read timeouts also need to be set on the Net::HTTP object:
59
+ client.request.http.open_timeout = 30
60
+ client.request.http.read_timeout = 30
61
+ * Please refer to the Net::HTTP documentation for more details:
62
+ http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/index.html
63
+ * Thanks to JulianMorrison, Savon now supports HTTP basic authentication:
64
+ client.request.http.basic_auth "username", "password"
65
+ * Julian also added a way to explicitly specify the order of Hash keys and values, so you should now be able
66
+ to work with services requiring a specific order of input parameters while still using Hash input.
67
+ For example: client.find_user { |soap| soap.body = { :name => "Lucy", :id => 666, :@inorder => [:id, :name] } }
68
+ * Savon::Response#to_hash now returns the content inside of "soap:Body" instead of trying to go one level
69
+ deeper and return it's content. The previous implementation only worked when the "soap:Body" element
70
+ contained a single child. See: http://github.com/rubiii/savon/issues#issue/17
71
+ * Added Savon::SOAP#namespace as a shortcut for setting the "xmlns:wsdl" namespace.
72
+ Usage example: soap.namespace = "http://example.com"
73
+
74
+ == 0.6.8 (2010-01-01)
75
+ * Improved specifications for various kinds of WSDL documents.
76
+ * Added support for SOAP endpoints which are different than the WSDL endpoint of a service.
77
+ * Changed how SOAP actions and inputs are retrieved from the WSDL documents. This might break a few existing
78
+ implementations, but makes Savon work well with even more services. If this change breaks your implementation,
79
+ please take a look at the +action+ and +input+ methods of the Savon::SOAP object.
80
+ One specific problem I know of is working with the createsend WSDL and its namespaced actions.
81
+ To make it work, call the SOAP action without namespace and specify the input manually:
82
+ client.get_api_key { |soap| soap.input = "User.GetApiKey" }
83
+
84
+ == 0.6.7 (2009-12-18)
85
+ * Implemented support for a proxy server. The proxy URI can be set through an optional Hash of options passed
86
+ to instantiating Savon::Client (Dave Woodward <dave@futuremint.com>)
87
+ * Implemented support for SSL client authentication. Settings can be set through an optional Hash of arguments
88
+ passed to instantiating Savon::Client (colonhyphenp)
89
+ * Patch for issue #10 (Problem with operation tags without a namespace).
90
+
91
+ == 0.6.6 (2009-12-14)
92
+ * Default to use the name of the SOAP action (the method called in a client) in lowerCamelCase for SOAP action
93
+ and input when Savon::WSDL is disabled. You still need to specify soap.action and maybe soap.input in case
94
+ your SOAP actions are named any different.
95
+
96
+ == 0.6.5 (2009-12-13)
97
+ * Added an open_timeout method to Savon::Request.
98
+
99
+ == 0.6.4 (2009-12-13)
100
+ * Refactored specs to be less unit-like.
101
+ * Added a getter for the Savon::Request to Savon::Client and a read_timeout setter for HTTP requests.
102
+ * wsdl.soap_actions now returns an Array of SOAP actions. For the previous "mapping" please use wsdl.operations.
103
+ * Replaced WSDL document with stream parsing.
104
+
105
+ Benchmarks (1000 SOAP calls):
106
+
107
+ user system total real
108
+ 0.6.4 72.180000 8.280000 80.460000 (750.799011)
109
+ 0.6.3 192.900000 19.630000 212.530000 (914.031865)
110
+
111
+ == 0.6.3 (2009-12-11)
112
+ * Removing 2 ruby deprecation warnings for parenthesized arguments. (Dave Woodward <dave@futuremint.com>)
113
+ * Added global and per request options for disabling Savon::WSDL.
114
+
115
+ Benchmarks (1000 SOAP calls):
116
+
117
+ user system total real
118
+ WSDL 192.900000 19.630000 212.530000 (914.031865)
119
+ disabled WSDL 5.680000 1.340000 7.020000 (298.265318)
120
+
121
+ * Improved XPath expressions for parsing the WSDL document.
122
+
123
+ Benchmarks (1000 SOAP calls):
124
+
125
+ user system total real
126
+ 0.6.3 192.900000 19.630000 212.530000 (914.031865)
127
+ 0.6.2 574.720000 78.380000 653.100000 (1387.778539)
128
+
129
+ == 0.6.2 (2009-12-06)
130
+ * Added support for changing the name of the SOAP input node.
131
+ * Added a CHANGELOG.
132
+
133
+ == 0.6.1 (2009-12-06)
134
+ * Fixed a problem with WSSE credentials, where every request contained a WSSE authentication header.
135
+
136
+ == 0.6.0 (2009-12-06)
137
+ * method_missing now yields the SOAP and WSSE objects to a given block.
138
+ * The response_process (which previously was a block passed to method_missing) was replaced by Savon::Response.
139
+ * Improved SOAP action handling (another problem that came up with issue #1).
140
+
141
+ == 0.5.3 (2009-11-30)
142
+ * Patch for issue #2 (NoMethodError: undefined method `invalid!' for Savon::WSDL)
143
+
144
+ == 0.5.2 (2009-11-30)
145
+ * Patch for issue #1 (Calls fail if api methods have periods in them)
146
+
147
+ == 0.5.1 (2009-11-29)
148
+ * Optimized default response process.
149
+ * Added WSSE settings via defaults.
150
+ * Added SOAP fault and HTTP error handling.
151
+ * Improved documentation
152
+ * Added specs
153
+
154
+ == 0.5.0 (2009-11-29)
155
+ * Complete rewrite.
156
+
data/README.rdoc ADDED
@@ -0,0 +1,64 @@
1
+ = Savon
2
+
3
+ ==== Heavy metal Ruby SOAP client library
4
+
5
+ {Documentation}[http://savon.rubiii.com] | {Metrics}[http://getcaliper.com/caliper/project?repo=git://github.com/rubiii/savon.git]
6
+
7
+ == Installation
8
+
9
+ $ gem install savon
10
+
11
+ Savon expects you to be familiar with SOAP, WSDL and tools like soapUI.
12
+
13
+ == Instantiate a client
14
+
15
+ Instantiate Savon::Client, passing in the WSDL of your service.
16
+
17
+ client = Savon::Client.new "http://example.com/UserService?wsdl"
18
+
19
+ For production, it is highly recommended to not use Savon::WSDL. Information on {how to disable the WSDL}[http://savon.rubiii.com/docs/latest/classes/Savon/WSDL.html].
20
+
21
+ == Calling a SOAP action
22
+
23
+ Assuming your service applies to the defaults, you can now call any available SOAP action.
24
+
25
+ response = client.get_all_users
26
+
27
+ Savon lets you call SOAP actions using snake_case, because even though they will propably be written in lowerCamelCase or CamelCase, it just feels much more natural.
28
+
29
+ == The WSDL object
30
+
31
+ Savon::WSDL represents the WSDL of your service, including information like the namespace URI and available SOAP actions.
32
+
33
+ client.wsdl.soap_actions
34
+ => [:get_all_users, :get_user_by_id, :user_magic]
35
+
36
+ == The SOAP object
37
+
38
+ Savon::SOAP represents the SOAP request. Pass a block to your SOAP call and the SOAP object is passed to it as the first argument. The object allows setting the SOAP version, header, body and namespaces per request.
39
+
40
+ response = client.get_user_by_id { |soap| soap.body = { :id => 666 } }
41
+
42
+ == The WSSE object
43
+
44
+ Savon::WSSE represents WSSE authentication. Pass a block to your SOAP call and the WSSE object is passed to it as the second argument. The object allows setting the WSSE username, password and whether to use digest authentication.
45
+
46
+ response = client.get_user_by_id do |soap, wsse|
47
+ wsse.username = "gorilla"
48
+ wsse.password = "secret"
49
+ soap.body = { :id => 666 }
50
+ end
51
+
52
+ == The Response object
53
+
54
+ Savon::Response represents the HTTP and SOAP response. It contains and raises errors in case of an HTTP error or SOAP fault (unless disabled). Also you can get the response as XML (for parsing it with an XML library) or translated into a Hash.
55
+
56
+ == HTTP errors and SOAP faults
57
+
58
+ Savon raises a Savon::SOAPFault in case of a SOAP fault and a Savon::HTTPError in case of an HTTP error.
59
+ More information: {Errors}[http://savon.rubiii.com/docs/latest/classes/Savon/Response.html]
60
+
61
+ == Logging
62
+
63
+ Savon logs each request and response to STDOUT. But there are a couple of options to change the default behavior.
64
+ More information: {Logging}[http://savon.rubiii.com/docs/latest/classes/Savon/Request.html]
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require "rake"
2
+ require "spec/rake/spectask"
3
+ require "spec/rake/verify_rcov"
4
+
5
+ task :default => :spec
6
+
7
+ Spec::Rake::SpecTask.new do |spec|
8
+ spec.spec_files = FileList["spec/{savon}/**/*_spec.rb"]
9
+ spec.spec_opts << "--color"
10
+ spec.libs += ["lib", "spec"]
11
+ spec.rcov = true
12
+ end
13
+
14
+ RCov::VerifyTask.new(:spec_verify => :spec) do |verify|
15
+ verify.threshold = 100.0
16
+ verify.index_html = "rcov/index.html"
17
+ end
18
+
19
+ desc "Run integration specs using WEBrick"
20
+ task :spec_integration do
21
+ pid = fork { exec "ruby spec/integration/server.rb" }
22
+ sleep 10 # wait until the server is actually ready
23
+ begin
24
+ task(:run_integration_spec).invoke
25
+ ensure
26
+ Process.kill "TERM", pid
27
+ Process.wait pid
28
+ end
29
+ end
30
+
31
+ desc "" # make this task invisible
32
+ Spec::Rake::SpecTask.new(:run_integration_spec) do |spec|
33
+ spec.spec_files = FileList["spec/{integration}/**/*_spec.rb"]
34
+ spec.spec_opts << "--color"
35
+ spec.libs += ["lib", "spec"]
36
+ end
37
+
38
+ begin
39
+ $:.unshift File.join(File.dirname(__FILE__), "..", "hanna", "lib")
40
+ require "hanna/rdoctask"
41
+
42
+ Rake::RDocTask.new do |rdoc|
43
+ rdoc.title = "Savon - Heavy metal Ruby SOAP client library"
44
+ rdoc.rdoc_dir = "doc"
45
+ rdoc.rdoc_files.include("**/*.rdoc").include("lib/**/*.rb")
46
+ rdoc.options << "--line-numbers"
47
+ rdoc.options << "--webcvs=http://github.com/rubiii/savon/tree/master/"
48
+ end
49
+ rescue LoadError
50
+ puts "'gem install hanna' for documentation"
51
+ end
@@ -0,0 +1,112 @@
1
+ module Savon
2
+
3
+ # = Savon::Client
4
+ #
5
+ # Savon::Client is the main object for connecting to a SOAP service. It includes methods to access
6
+ # both the Savon::WSDL and Savon::Request object.
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
+ # == Savon::WSDL
31
+ #
32
+ # You can access Savon::WSDL via:
33
+ #
34
+ # client.wsdl
35
+ #
36
+ # == Savon::Request
37
+ #
38
+ # You can also access Savon::Request via:
39
+ #
40
+ # client.request
41
+ class Client
42
+
43
+ # Expects a SOAP +endpoint+ string. Also accepts an optional hash of +options+ for specifying
44
+ # a +:proxy+ server to use.
45
+ def initialize(endpoint, options = {})
46
+ @request = Request.new endpoint, options
47
+ @wsdl = WSDL.new @request
48
+ end
49
+
50
+ # Returns the Savon::WSDL.
51
+ attr_reader :wsdl
52
+
53
+ # Returns the Savon::Request.
54
+ attr_reader :request
55
+
56
+ # Returns +true+ for available methods and SOAP actions.
57
+ def respond_to?(method)
58
+ return true if @wsdl.respond_to? method
59
+ super
60
+ end
61
+
62
+ # Same as method_missing. Workaround for SOAP actions that method_missing does not catch
63
+ # because the method does exist.
64
+ def call(method, *args, &block)
65
+ method_missing method, *args, &block
66
+ end
67
+
68
+ private
69
+
70
+ # Dispatches requests to SOAP actions matching a given +method+ name.
71
+ def method_missing(method, *args, &block) #:doc:
72
+ soap_action = soap_action_from method.to_s
73
+ super unless @wsdl.respond_to? soap_action
74
+
75
+ setup_objects *@wsdl.operation_from(soap_action), &block
76
+ Response.new @request.soap(@soap)
77
+ end
78
+
79
+ # Sets whether to use Savon::WSDL by a given +method+ name and returns the original method name
80
+ # without exclamation marks.
81
+ def soap_action_from(method)
82
+ @wsdl.enabled = !method.ends_with?("!")
83
+
84
+ method.chop! if method.ends_with?("!")
85
+ method.to_sym
86
+ end
87
+
88
+ # Returns the SOAP endpoint.
89
+ def soap_endpoint
90
+ @wsdl.enabled? ? @wsdl.soap_endpoint : @request.endpoint
91
+ end
92
+
93
+ # Expects a SOAP operation Hash and sets up Savon::SOAP and Savon::WSSE. Yields them to a given
94
+ # +block+ in case one was given.
95
+ def setup_objects(action, input, &block)
96
+ @soap, @wsse = SOAP.new(action, input, soap_endpoint), WSSE.new
97
+ yield_objects &block if block
98
+ @soap.namespaces["xmlns:wsdl"] ||= @wsdl.namespace_uri if @wsdl.enabled?
99
+ @soap.wsse = @wsse
100
+ end
101
+
102
+ # Yields either Savon::SOAP or Savon::SOAP and Savon::WSSE to a given +block+, depending on
103
+ # the number of arguments expected by the block.
104
+ def yield_objects(&block)
105
+ case block.arity
106
+ when 1 then yield @soap
107
+ when 2 then yield @soap, @wsse
108
+ end
109
+ end
110
+
111
+ end
112
+ end
@@ -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, 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 << 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,8 @@
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
+ end
@@ -0,0 +1,102 @@
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
+ # ==== :order!
39
+ #
40
+ # In case your service requires the tags to be in a specific order (parameterOrder), you have two
41
+ # options. The first is to specify your body as an XML string. The second is to specify the order
42
+ # through an additional array stored under the +:order!+ key.
43
+ #
44
+ # { :name => "Eve", :id => 123, :order! => [:id, :name] }.to_soap_xml
45
+ # # => "<id>123</id><name>Eve</name>"
46
+ #
47
+ # ==== :attributes!
48
+ #
49
+ # If you need attributes, you could either go with an XML string or add another hash under the
50
+ # +:attributes!+ key.
51
+ #
52
+ # { :person => "Eve", :attributes! => { :person => { :id => 666 } } }.to_soap_xml
53
+ # # => '<person id="666">Eve</person>'
54
+ def to_soap_xml
55
+ xml = Builder::XmlMarkup.new
56
+ attributes = delete(:attributes!) || {}
57
+
58
+ order.each do |key|
59
+ attrs = attributes[key] || {}
60
+ value = self[key]
61
+ key = key.to_soap_key
62
+
63
+ case value
64
+ when Array then xml << value.to_soap_xml(key, attrs)
65
+ when Hash then xml.tag!(key, attrs) { xml << value.to_soap_xml }
66
+ else xml.tag!(key, attrs) { xml << value.to_soap_value }
67
+ end
68
+ end
69
+
70
+ xml.target!
71
+ end
72
+
73
+ # Maps keys and values of a Hash created from SOAP response XML to more convenient Ruby Objects.
74
+ def map_soap_response
75
+ inject({}) do |hash, (key, value)|
76
+ value = case value
77
+ when Hash then value["xsi:nil"] ? nil : value.map_soap_response
78
+ when Array then value.map { |val| val.map_soap_response rescue val }
79
+ when String then value.map_soap_response
80
+ end
81
+
82
+ hash.merge key.strip_namespace.snakecase.to_sym => value
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ # Deletes and returns an Array of keys stored under the :order! key. Defaults to return the actual
89
+ # keys of this Hash if no :order! key could be found. Raises an ArgumentError in case the :order!
90
+ # Array does not match the Hash keys.
91
+ def order
92
+ order = delete :order!
93
+ order = keys unless order.kind_of? Array
94
+
95
+ missing, spurious = keys - order, order - keys
96
+ raise ArgumentError, "Missing elements in :order! #{missing.inspect}" unless missing.empty?
97
+ raise ArgumentError, "Spurious elements in :order! #{spurious.inspect}" unless spurious.empty?
98
+
99
+ order
100
+ end
101
+
102
+ 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,19 @@
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 key.
9
+ def to_soap_key
10
+ to_s
11
+ end
12
+
13
+ # Returns the Object as a SOAP request compliant value.
14
+ def to_soap_value
15
+ return to_s unless respond_to? :to_datetime
16
+ to_datetime.to_soap_value
17
+ end
18
+
19
+ end
@@ -0,0 +1,66 @@
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 String as a SOAP request compliant value.
55
+ # Escapes special characters for XML.
56
+ def to_soap_value
57
+ str = dup
58
+ str.gsub! "&", "&amp;"
59
+ str.gsub! '"', "&quot;"
60
+ str.gsub! "'", "&apos;"
61
+ str.gsub! "<", "&lt;"
62
+ str.gsub! ">", "&gt;"
63
+ str
64
+ end
65
+
66
+ 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 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,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"