savon 0.6.0 → 0.7.0

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.
Files changed (51) hide show
  1. data/CHANGELOG +112 -0
  2. data/README.textile +42 -35
  3. data/Rakefile +28 -33
  4. data/lib/savon/client.rb +42 -19
  5. data/lib/savon/core_ext/hash.rb +35 -22
  6. data/lib/savon/core_ext/net_http.rb +20 -0
  7. data/lib/savon/core_ext/object.rb +7 -0
  8. data/lib/savon/core_ext/string.rb +1 -1
  9. data/lib/savon/core_ext.rb +2 -2
  10. data/lib/savon/request.rb +73 -33
  11. data/lib/savon/response.rb +23 -36
  12. data/lib/savon/soap.rb +63 -30
  13. data/lib/savon/wsdl.rb +82 -35
  14. data/lib/savon/wsse.rb +41 -35
  15. data/lib/savon.rb +3 -2
  16. data/spec/basic_spec_helper.rb +12 -0
  17. data/spec/endpoint_helper.rb +22 -0
  18. data/spec/fixtures/response/response_fixture.rb +36 -0
  19. data/spec/fixtures/response/xml/authentication.xml +14 -0
  20. data/spec/fixtures/response/xml/multi_ref.xml +39 -0
  21. data/spec/fixtures/response/xml/soap_fault12.xml +18 -0
  22. data/spec/fixtures/wsdl/wsdl_fixture.rb +37 -0
  23. data/spec/fixtures/wsdl/xml/authentication.xml +63 -0
  24. data/spec/fixtures/wsdl/xml/namespaced_actions.xml +307 -0
  25. data/spec/fixtures/wsdl/xml/no_namespace.xml +115 -0
  26. data/spec/http_stubs.rb +17 -19
  27. data/spec/integration/http_basic_auth_spec.rb +12 -0
  28. data/spec/integration/server.rb +51 -0
  29. data/spec/savon/client_spec.rb +52 -44
  30. data/spec/savon/core_ext/datetime_spec.rb +2 -2
  31. data/spec/savon/core_ext/hash_spec.rb +34 -31
  32. data/spec/savon/core_ext/net_http_spec.rb +38 -0
  33. data/spec/savon/core_ext/object_spec.rb +16 -2
  34. data/spec/savon/core_ext/string_spec.rb +4 -4
  35. data/spec/savon/core_ext/symbol_spec.rb +1 -1
  36. data/spec/savon/core_ext/uri_spec.rb +1 -1
  37. data/spec/savon/request_spec.rb +49 -63
  38. data/spec/savon/response_spec.rb +48 -51
  39. data/spec/savon/savon_spec.rb +12 -19
  40. data/spec/savon/soap_spec.rb +81 -90
  41. data/spec/savon/wsdl_spec.rb +62 -30
  42. data/spec/savon/wsse_spec.rb +58 -84
  43. data/spec/spec_helper.rb +3 -13
  44. metadata +29 -15
  45. data/VERSION +0 -1
  46. data/spec/fixtures/multiple_user_response.xml +0 -22
  47. data/spec/fixtures/user_fixture.rb +0 -54
  48. data/spec/fixtures/user_response.xml +0 -15
  49. data/spec/fixtures/user_wsdl.xml +0 -124
  50. data/spec/spec_helper_methods.rb +0 -33
  51. /data/spec/fixtures/{soap_fault.xml → response/xml/soap_fault.xml} +0 -0
data/CHANGELOG ADDED
@@ -0,0 +1,112 @@
1
+ == 0.7.0 (2010-01-09)
2
+ This version comes with several changes to the public API!
3
+ Pay attention to the following list and read the updated Wiki: http://wiki.github.com/rubiii/savon
4
+
5
+ * Changed how Savon::WSDL can be disabled. Instead of disabling the WSDL globally/per request via two
6
+ different methods, you now simply append an exclamation mark (!) to your SOAP call: client.get_all_users!
7
+ Make sure you know what you're doing because when the WSDL is disabled, Savon does not know about which
8
+ SOAP actions are valid and just dispatches everything.
9
+ * The Net::HTTP object used by Savon::Request to retrieve WSDL documents and execute SOAP calls is now public.
10
+ While this makes the library even more flexible, it also comes with two major changes:
11
+ * SSL client authentication needs to be defined directly on the Net::HTTP object:
12
+ client.request.http.client_cert = ...
13
+ I added a shortcut method for setting all options through a Hash similar to the previous implementation:
14
+ client.request.http.ssl_client_auth :client_cert => ...
15
+ * Open and read timeouts also need to be set on the Net::HTTP object:
16
+ client.request.http.open_timeout = 30
17
+ client.request.http.read_timeout = 30
18
+ * Please refer to the Net::HTTP documentation for more details:
19
+ http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/index.html
20
+ * Thanks to JulianMorrison, Savon now supports HTTP basic authentication:
21
+ client.request.http.basic_auth "username", "password"
22
+ * Julian also added a way to explicitly specify the order of Hash keys and values, so you should now be able
23
+ to work with services requiring a specific order of input parameters while still using Hash input.
24
+ For example: client.find_user { |soap| soap.body = { :name => "Lucy", :id => 666, :@inorder => [:id, :name] } }
25
+ * Savon::Response#to_hash now returns the content inside of "soap:Body" instead of trying to go one level
26
+ deeper and return it's content. The previous implementation only worked when the "soap:Body" element
27
+ contained a single child. See: http://github.com/rubiii/savon/issues#issue/17
28
+ * Added Savon::SOAP#namespace as a shortcut for setting the "xmlns:wsdl" namespace.
29
+ Usage example: soap.namespace = "http://example.com"
30
+
31
+ == 0.6.8 (2010-01-01)
32
+ * Improved specifications for various kinds of WSDL documents.
33
+ * Added support for SOAP endpoints which are different than the WSDL endpoint of a service.
34
+ * Changed how SOAP actions and inputs are retrieved from the WSDL documents. This might break a few existing
35
+ implementations, but makes Savon work well with even more services. If this change breaks your implementation,
36
+ please take a look at the +action+ and +input+ methods of the Savon::SOAP object.
37
+ One specific problem I know of is working with the createsend WSDL and its namespaced actions.
38
+ To make it work, call the SOAP action without namespace and specify the input manually:
39
+ client.get_api_key { |soap| soap.input = "User.GetApiKey" }
40
+
41
+ == 0.6.7 (2009-12-18)
42
+ * Implemented support for a proxy server. The proxy URI can be set through an optional Hash of options passed
43
+ to instantiating Savon::Client (Dave Woodward <dave@futuremint.com>)
44
+ * Implemented support for SSL client authentication. Settings can be set through an optional Hash of arguments
45
+ passed to instantiating Savon::Client (colonhyphenp)
46
+ * Patch for issue #10 (Problem with operation tags without a namespace).
47
+
48
+ == 0.6.6 (2009-12-14)
49
+ * Default to use the name of the SOAP action (the method called in a client) in lowerCamelCase for SOAP action
50
+ and input when Savon::WSDL is disabled. You still need to specify soap.action and maybe soap.input in case
51
+ your SOAP actions are named any different.
52
+
53
+ == 0.6.5 (2009-12-13)
54
+ * Added an open_timeout method to Savon::Request.
55
+
56
+ == 0.6.4 (2009-12-13)
57
+ * Refactored specs to be less unit-like.
58
+ * Added a getter for the Savon::Request to Savon::Client and a read_timeout setter for HTTP requests.
59
+ * wsdl.soap_actions now returns an Array of SOAP actions. For the previous "mapping" please use wsdl.operations.
60
+ * Replaced WSDL document with stream parsing.
61
+
62
+ Benchmarks (1000 SOAP calls):
63
+
64
+ user system total real
65
+ 0.6.4 72.180000 8.280000 80.460000 (750.799011)
66
+ 0.6.3 192.900000 19.630000 212.530000 (914.031865)
67
+
68
+ == 0.6.3 (2009-12-11)
69
+ * Removing 2 ruby deprecation warnings for parenthesized arguments. (Dave Woodward <dave@futuremint.com>)
70
+ * Added global and per request options for disabling Savon::WSDL.
71
+
72
+ Benchmarks (1000 SOAP calls):
73
+
74
+ user system total real
75
+ WSDL 192.900000 19.630000 212.530000 (914.031865)
76
+ disabled WSDL 5.680000 1.340000 7.020000 (298.265318)
77
+
78
+ * Improved XPath expressions for parsing the WSDL document.
79
+
80
+ Benchmarks (1000 SOAP calls):
81
+
82
+ user system total real
83
+ 0.6.3 192.900000 19.630000 212.530000 (914.031865)
84
+ 0.6.2 574.720000 78.380000 653.100000 (1387.778539)
85
+
86
+ == 0.6.2 (2009-12-06)
87
+ * Added support for changing the name of the SOAP input node.
88
+ * Added a CHANGELOG.
89
+
90
+ == 0.6.1 (2009-12-06)
91
+ * Fixed a problem with WSSE credentials, where every request contained a WSSE authentication header.
92
+
93
+ == 0.6.0 (2009-12-06)
94
+ * method_missing now yields the SOAP and WSSE objects to a given block.
95
+ * The response_process (which previously was a block passed to method_missing) was replaced by Savon::Response.
96
+ * Improved SOAP action handling (another problem that came up with issue #1).
97
+
98
+ == 0.5.3 (2009-11-30)
99
+ * Patch for issue #2 (NoMethodError: undefined method `invalid!' for Savon::WSDL)
100
+
101
+ == 0.5.2 (2009-11-30)
102
+ * Patch for issue #1 (Calls fail if api methods have periods in them)
103
+
104
+ == 0.5.1 (2009-11-29)
105
+ * Optimized default response process.
106
+ * Added WSSE settings via defaults.
107
+ * Added SOAP fault and HTTP error handling.
108
+ * Improved documentation
109
+ * Added specs
110
+
111
+ == 0.5.0 (2009-11-29)
112
+ * Complete rewrite.
data/README.textile CHANGED
@@ -1,68 +1,75 @@
1
- h2. Savon
1
+ h1. Savon
2
2
 
3
- p. Savon can be installed as a gem via:
3
+ h4. Heavy metal Ruby SOAP client library
4
4
 
5
- bc. $ gem install savon
6
-
7
- h4. Dependencies
8
-
9
- bc. builder >= 2.1.2
10
- crack >= 0.1.4
5
+ p. "RDoc":http://rdoc.info/projects/rubiii/savon | "Wiki":http://wiki.github.com/rubiii/savon | "ToDo":http://rubiii.tadalist.com/lists/1459816/public | "Metrics":http://getcaliper.com/caliper/project?repo=git://github.com/rubiii/savon.git
11
6
 
12
7
  h2. Warning
13
8
 
14
- p. To use this heavy metal library, you should be familiar with SOAP, WSDL and tools like soapUI.
9
+ p. Savon 0.7.0 comes with several changes to the public API. Pay attention to the CHANGELOG and the updated Wiki.
15
10
 
16
- h2. Instantiate Savon::Client
11
+ h2. Installation
17
12
 
18
- p. Instantiate a new instance of Savon::Client, passing in the WSDL of your service.
13
+ bc. $ gem install savon
19
14
 
20
- bc. proxy = Savon::Client.new "http://example.com/UserService?wsdl"
15
+ p. Savon expects you to be familiar with SOAP, WSDL and tools like soapUI.
21
16
 
22
- h2. The WSDL
17
+ h2. Instantiate a client
23
18
 
24
- p. You can find out about the SOAP actions available on the webservice by using the WSDL object.
19
+ p. Instantiate Savon::Client, passing in the WSDL of your service.
25
20
 
26
- bc. proxy.wsdl.soap_actions
27
- => [:get_all_users, :get_user_by_id, :user_magic]
21
+ bc. client = Savon::Client.new "http://example.com/UserService?wsdl"
28
22
 
29
- p. Find out more about the "WSDL":http://wiki.github.com/rubiii/savon/wsdl object.
23
+ p. More information: "Client":http://wiki.github.com/rubiii/savon/client
30
24
 
31
25
  h2. Calling a SOAP action
32
26
 
33
- p. Now, assuming your service applies to the default "Options":http://wiki.github.com/rubiii/savon/options, you can just call any available SOAP action.
27
+ p. Assuming your service applies to the defaults, you can now call any available SOAP action.
34
28
 
35
- bc. response = proxy.get_all_users
29
+ bc. response = client.get_all_users
36
30
 
37
31
  p. 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.
38
32
 
39
- h2. Parameters
33
+ h2. The WSDL object
40
34
 
41
- p. Specifying parameters for the SOAP service, can be done by simply passing a Hash to the SOAP action.
35
+ p. Savon::WSDL represents the WSDL of your service, including information like the namespace URI and available SOAP actions.
42
36
 
43
- bc. response = proxy.get_user_by_id :id => 666
37
+ bc. client.wsdl.soap_actions
38
+ => [:get_all_users, :get_user_by_id, :user_magic]
44
39
 
45
- p. Learn more about [[Parameters]].
40
+ p. More information: "WSDL":http://wiki.github.com/rubiii/savon/wsdl
46
41
 
47
- h2. The response
42
+ h2. The SOAP object
48
43
 
49
- p. By default, the SOAP response is translated into a Hash. Take a look at the "Options":http://wiki.github.com/rubiii/savon/options for more information.
44
+ p. 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.
50
45
 
51
- bc. proxy.get_user_by_id :id => 666
52
- => { :user_response => { :id => "666", :username => "gorilla" } }
46
+ bc. response = client.get_user_by_id { |soap| soap.body = { :id => 666 } }
53
47
 
54
- h2. HTTP errors and SOAP faults
48
+ p. More information: "SOAP":http://wiki.github.com/rubiii/savon/soap
55
49
 
56
- p. Savon raises a Savon::SOAPFault in case of a SOAP fault and a Savon::HTTPError in case of an HTTP error. More information about "Errors":http://wiki.github.com/rubiii/savon/errors.
50
+ h2. The WSSE object
57
51
 
58
- h2. Logging
52
+ p. 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.
53
+
54
+ bc. response = client.get_user_by_id do |soap, wsse|
55
+ wsse.username = "gorilla"
56
+ wsse.password = "secret"
57
+ soap.body = { :id => 666 }
58
+ end
59
+
60
+ p. More information: "WSSE":http://wiki.github.com/rubiii/savon/wsse
59
61
 
60
- p. By default Savon logs each request and response to STDOUT. Specifying your own logger is as easy as it gets:
62
+ h2. The Response object
61
63
 
62
- bc. Savon::Request.logger = RAILS_DEFAULT_LOGGER
64
+ p. 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.
65
+ More information: "Response":http://wiki.github.com/rubiii/savon/response
63
66
 
64
- Read more about "Logging":http://wiki.github.com/rubiii/savon/logging.
67
+ h2. HTTP errors and SOAP faults
68
+
69
+ p. Savon raises a Savon::SOAPFault in case of a SOAP fault and a Savon::HTTPError in case of an HTTP error.
70
+ More information: "Errors":http://wiki.github.com/rubiii/savon/errors
65
71
 
66
- h2. RDoc and Wiki
72
+ h2. Logging
67
73
 
68
- p. Further information: "Wiki":http://wiki.github.com/rubiii/savon and "RDoc":http://rdoc.info/projects/rubiii/savon
74
+ p. Savon logs each request and response to STDOUT. But there are a couple of options to change the default behavior.
75
+ More information: "Logging":http://wiki.github.com/rubiii/savon/logging
data/Rakefile CHANGED
@@ -1,50 +1,45 @@
1
- require "rubygems"
2
1
  require "rake"
3
2
  require "spec/rake/spectask"
3
+ require "spec/rake/verify_rcov"
4
4
  require "rake/rdoctask"
5
5
 
6
- task :default => :spec
6
+ task :default => :spec_verify
7
7
 
8
8
  Spec::Rake::SpecTask.new do |spec|
9
- spec.spec_files = FileList["spec/**/*_spec.rb"]
9
+ spec.spec_files = FileList["spec/{savon}/**/*_spec.rb"]
10
10
  spec.spec_opts << "--color"
11
- spec.libs << "lib"
11
+ spec.libs += ["lib", "spec"]
12
12
  spec.rcov = true
13
13
  spec.rcov_dir = "rcov"
14
14
  end
15
15
 
16
+ RCov::VerifyTask.new(:spec_verify => :spec) do |verify|
17
+ verify.threshold = 100.0
18
+ verify.index_html = "rcov/index.html"
19
+ end
20
+
21
+ desc "Run integration specs using WEBrick"
22
+ task :spec_integration do
23
+ pid = fork { exec "ruby spec/integration/server.rb" }
24
+ sleep 10 # wait until the server is actually ready
25
+ begin
26
+ task(:run_integration_spec).invoke
27
+ ensure
28
+ Process.kill "TERM", pid
29
+ Process.wait pid
30
+ end
31
+ end
32
+
33
+ desc "" # make this task invisible for "rake -T"
34
+ Spec::Rake::SpecTask.new(:run_integration_spec) do |spec|
35
+ spec.spec_files = FileList["spec/{integration}/**/*_spec.rb"]
36
+ spec.spec_opts << "--color"
37
+ spec.libs += ["lib", "spec"]
38
+ end
39
+
16
40
  Rake::RDocTask.new do |rdoc|
17
41
  rdoc.title = "Savon"
18
42
  rdoc.rdoc_dir = "rdoc"
19
43
  rdoc.rdoc_files.include("lib/**/*.rb")
20
44
  rdoc.options = ["--line-numbers", "--inline-source"]
21
45
  end
22
-
23
- begin
24
- require "jeweler"
25
- Jeweler::Tasks.new do |spec|
26
- spec.name = "savon"
27
- spec.author = "Daniel Harrington"
28
- spec.email = "me@rubiii.com"
29
- spec.homepage = "http://github.com/rubiii/savon"
30
- spec.summary = "Heavy metal Ruby SOAP client library"
31
- spec.description = spec.summary
32
-
33
- spec.files = FileList["[A-Z]*", "{lib,spec}/**/*.{rb,xml}"]
34
-
35
- spec.rdoc_options += [
36
- "--title", "Savon",
37
- "--line-numbers",
38
- "--inline-source"
39
- ]
40
-
41
- spec.add_runtime_dependency("builder", ">= 2.1.2")
42
- spec.add_runtime_dependency("crack", ">= 0.1.4")
43
-
44
- spec.add_development_dependency("rspec", ">= 1.2.8")
45
- spec.add_development_dependency("mocha", ">= 0.9.7")
46
- spec.add_development_dependency("fakeweb", ">= 1.2.7")
47
- end
48
- rescue LoadError
49
- puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
50
- end
data/lib/savon/client.rb CHANGED
@@ -6,15 +6,19 @@ module Savon
6
6
  # with SOAP services and XML.
7
7
  class Client
8
8
 
9
- # Expects a SOAP +endpoint+ String.
10
- def initialize(endpoint)
11
- @request = Request.new endpoint
9
+ # Expects a SOAP +endpoint+ String. Also accepts an optional Hash of
10
+ # +options+ for specifying a proxy server and SSL client authentication.
11
+ def initialize(endpoint, options = {})
12
+ @request = Request.new endpoint, options
12
13
  @wsdl = WSDL.new @request
13
14
  end
14
15
 
15
16
  # Returns the Savon::WSDL.
16
17
  attr_reader :wsdl
17
18
 
19
+ # Returns the Savon::Request.
20
+ attr_reader :request
21
+
18
22
  # Returns +true+ for available methods and SOAP actions.
19
23
  def respond_to?(method)
20
24
  return true if @wsdl.respond_to? method
@@ -25,37 +29,56 @@ module Savon
25
29
 
26
30
  # Dispatches requests to SOAP actions matching a given +method+ name.
27
31
  def method_missing(method, *args, &block) #:doc:
28
- super unless @wsdl.respond_to? method
32
+ soap_call = soap_call_from method.to_s
33
+ super if @wsdl.enabled? && !@wsdl.respond_to?(soap_call)
34
+
35
+ setup_objects operation_from(soap_call), &block
36
+ Response.new @request.soap(@soap)
37
+ end
38
+
39
+ # Sets whether to use Savon::WSDL by a given +method+ name and
40
+ # removes exclamation marks from the given +method+ name.
41
+ def soap_call_from(method)
42
+ if method[-1, 1] == "!"
43
+ @wsdl.enabled = false
44
+ method[0, method.length-1].to_sym
45
+ else
46
+ @wsdl.enabled = true
47
+ method.to_sym
48
+ end
49
+ end
50
+
51
+ # Returns a SOAP operation Hash containing the SOAP action and input
52
+ # for a given +soap_call+.
53
+ def operation_from(soap_call)
54
+ return @wsdl.operations[soap_call] if @wsdl.enabled?
55
+ { :action => soap_call.to_soap_key, :input => soap_call.to_soap_key }
56
+ end
29
57
 
30
- setup method, &block
31
- dispatch method
58
+ # Returns the SOAP endpoint.
59
+ def soap_endpoint
60
+ @wsdl.enabled? ? @wsdl.soap_endpoint : @request.endpoint
32
61
  end
33
62
 
34
- # Expects a SOAP action and sets up Savon::SOAP and Savon::WSSE.
63
+ # Expects a SOAP operation Hash and sets up Savon::SOAP and Savon::WSSE.
35
64
  # Yields them to a given +block+ in case one was given.
36
- def setup(soap_action, &block)
37
- @soap = SOAP.new @wsdl.soap_actions[soap_action]
38
- @wsse = WSSE.new
65
+ def setup_objects(operation, &block)
66
+ @soap, @wsse = SOAP.new, WSSE.new
67
+ @soap.action, @soap.input, @soap.endpoint = operation[:action], operation[:input], soap_endpoint
39
68
 
40
- yield_parameters &block if block
69
+ yield_objects &block if block
41
70
 
42
- @soap.namespaces["xmlns:wsdl"] = @wsdl.namespace_uri
71
+ @soap.namespaces["xmlns:wsdl"] ||= @wsdl.namespace_uri if @wsdl.enabled?
43
72
  @soap.wsse = @wsse
44
73
  end
45
74
 
46
75
  # Yields Savon::SOAP and Savon::WSSE to a given +block+.
47
- def yield_parameters(&block)
76
+ def yield_objects(&block)
48
77
  case block.arity
49
78
  when 1 then yield @soap
50
79
  when 2 then yield @soap, @wsse
51
80
  end
52
81
  end
53
82
 
54
- # Dispatches a given +soap_action+ and returns a Savon::Response instance.
55
- def dispatch(soap_action)
56
- response = @request.soap @soap
57
- Response.new response
58
- end
59
-
60
83
  end
61
84
  end
@@ -1,29 +1,34 @@
1
1
  class Hash
2
2
 
3
- # Expects an Array of Regexp Objects of which every Regexp recursively
4
- # matches a key to be accessed. Returns the value of the last Regexp filter
5
- # found in the Hash or an empty Hash in case the path of Regexp filters
6
- # did not match the Hash structure.
7
- def find_regexp(regexp)
8
- regexp = [regexp] unless regexp.kind_of? Array
9
- result = dup
3
+ # Error message for missing :@inorder elements.
4
+ InOrderMissing = "Missing elements in :@inorder %s"
10
5
 
11
- regexp.each do |pattern|
12
- result_key = result.keys.find { |key| key.to_s.match pattern }
13
- result = result[result_key] ? result[result_key] : {}
14
- end
15
- result
6
+ # Error message for spurious :@inorder elements.
7
+ InOrderSpurious = "Spurious elements in :@inorder %s"
8
+
9
+ # Returns the values from the 'soap:Body' element or an empty Hash
10
+ # in case the node could not be found.
11
+ def find_soap_body
12
+ envelope = self[self.keys.first] || {}
13
+ body_key = envelope.keys.find { |key| /.+:Body/ =~ key } rescue nil
14
+ body_key ? envelope[body_key].map_soap_response : {}
16
15
  end
17
16
 
18
17
  # Returns the Hash translated into SOAP request compatible XML.
19
18
  #
20
- # === Example
19
+ # To control the order of output, add a key of :@inorder with
20
+ # the value being an Array listing keys in order.
21
+ #
22
+ # === Examples
21
23
  #
22
24
  # { :find_user => { :id => 666 } }.to_soap_xml
23
25
  # => "<findUser><id>666</id></findUser>"
26
+ #
27
+ # { :find_user => { :name => "Lucy", :id => 666, :@inorder => [:id, :name] } }.to_soap_xml
28
+ # => "<findUser><id>666</id><name>Lucy</name></findUser>"
24
29
  def to_soap_xml
25
30
  @soap_xml = Builder::XmlMarkup.new
26
- each { |key, value| nested_data_to_soap_xml key, value }
31
+ inorder(self).each { |key| nested_data_to_soap_xml key, self[key] }
27
32
  @soap_xml.target!
28
33
  end
29
34
 
@@ -34,12 +39,9 @@ class Hash
34
39
  key = key.strip_namespace.snakecase.to_sym
35
40
 
36
41
  value = case value
37
- when Hash
38
- value["xsi:nil"] ? nil : value.map_soap_response
39
- when Array
40
- value.map { |a_value| a_value.map_soap_response rescue a_value }
41
- when String
42
- value.map_soap_response
42
+ when Hash then value["xsi:nil"] ? nil : value.map_soap_response
43
+ when Array then value.map { |a_value| a_value.map_soap_response rescue a_value }
44
+ when String then value.map_soap_response
43
45
  end
44
46
  hash.merge key => value
45
47
  end
@@ -52,14 +54,25 @@ private
52
54
  def nested_data_to_soap_xml(key, value)
53
55
  case value
54
56
  when Array
55
- value.map { |sitem| nested_data_to_soap_xml key, sitem }
57
+ value.map { |subitem| nested_data_to_soap_xml key, subitem }
56
58
  when Hash
57
59
  @soap_xml.tag!(key.to_soap_key) do
58
- value.each { |subkey, subvalue| nested_data_to_soap_xml subkey, subvalue }
60
+ inorder(value).each { |subkey| nested_data_to_soap_xml subkey, value[subkey] }
59
61
  end
60
62
  else
61
63
  @soap_xml.tag!(key.to_soap_key) { @soap_xml << value.to_soap_value }
62
64
  end
63
65
  end
64
66
 
67
+ # Takes a +hash+, removes the :@inorder marker and returns its keys.
68
+ # Raises an error in case an :@inorder Array does not match the Hash keys.
69
+ def inorder(hash)
70
+ inorder = hash.delete :@inorder
71
+ hash_keys = hash.keys
72
+ inorder = hash_keys unless inorder.kind_of? Array
73
+ raise InOrderMissing % (hash_keys - inorder).inspect unless (hash_keys - inorder).empty?
74
+ raise InOrderSpurious % (inorder - hash_keys).inspect unless (inorder - hash_keys).empty?
75
+ inorder
76
+ end
77
+
65
78
  end
@@ -0,0 +1,20 @@
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
10
+ # through a Hash of +options+.
11
+ def ssl_client_auth(options)
12
+ self.use_ssl = true
13
+ self.cert = options[:cert] if options[:cert]
14
+ self.key = options[:key] if options[:key]
15
+ self.ca_file = options[:ca_file] if options[:ca_file]
16
+ self.verify_mode = options[:verify_mode] if options[:verify_mode]
17
+ end
18
+
19
+ end
20
+ end
@@ -1,5 +1,12 @@
1
1
  class Object
2
2
 
3
+ # Returns +true+ if the Object is false, empty, or a whitespace string.
4
+ # For example, "", false, nil, [], and {} are blank.
5
+ # Implementation from ActiveSupport.
6
+ def blank?
7
+ respond_to?(:empty?) ? empty? : !self
8
+ end unless defined? blank?
9
+
3
10
  # Returns the Object as a SOAP request compliant key.
4
11
  def to_soap_key
5
12
  to_s
@@ -33,7 +33,7 @@ class String
33
33
 
34
34
  # Translates SOAP response values to more convenient Ruby Objects.
35
35
  def map_soap_response
36
- return DateTime.parse self if Savon::SOAPDateTimeRegexp === self
36
+ return DateTime.parse( self ) if Savon::SOAPDateTimeRegexp === self
37
37
  return true if self.strip.downcase == "true"
38
38
  return false if self.strip.downcase == "false"
39
39
  self
@@ -1,3 +1,3 @@
1
- %w(object string symbol datetime hash uri).each do |file|
2
- require "savon/core_ext/#{file}"
1
+ %w(object string symbol datetime hash uri net_http).each do |file|
2
+ require File.dirname(__FILE__) + "/core_ext/#{file}"
3
3
  end