s-savon 0.8.6

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 (61) hide show
  1. data/.gitignore +9 -0
  2. data/.rspec +1 -0
  3. data/.yardopts +2 -0
  4. data/CHANGELOG.md +461 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE +20 -0
  7. data/README.md +37 -0
  8. data/Rakefile +40 -0
  9. data/lib/savon.rb +14 -0
  10. data/lib/savon/client.rb +157 -0
  11. data/lib/savon/core_ext/hash.rb +70 -0
  12. data/lib/savon/core_ext/object.rb +14 -0
  13. data/lib/savon/core_ext/string.rb +51 -0
  14. data/lib/savon/core_ext/time.rb +14 -0
  15. data/lib/savon/error.rb +6 -0
  16. data/lib/savon/global.rb +75 -0
  17. data/lib/savon/http/error.rb +42 -0
  18. data/lib/savon/soap.rb +24 -0
  19. data/lib/savon/soap/fault.rb +59 -0
  20. data/lib/savon/soap/request.rb +61 -0
  21. data/lib/savon/soap/response.rb +80 -0
  22. data/lib/savon/soap/xml.rb +187 -0
  23. data/lib/savon/version.rb +5 -0
  24. data/lib/savon/wsdl/document.rb +112 -0
  25. data/lib/savon/wsdl/parser.rb +102 -0
  26. data/lib/savon/wsdl/request.rb +35 -0
  27. data/lib/savon/wsse.rb +150 -0
  28. data/savon.gemspec +29 -0
  29. data/spec/fixtures/gzip/message.gz +0 -0
  30. data/spec/fixtures/response/another_soap_fault.xml +14 -0
  31. data/spec/fixtures/response/authentication.xml +14 -0
  32. data/spec/fixtures/response/header.xml +13 -0
  33. data/spec/fixtures/response/list.xml +18 -0
  34. data/spec/fixtures/response/multi_ref.xml +39 -0
  35. data/spec/fixtures/response/soap_fault.xml +8 -0
  36. data/spec/fixtures/response/soap_fault12.xml +18 -0
  37. data/spec/fixtures/wsdl/authentication.xml +63 -0
  38. data/spec/fixtures/wsdl/geotrust.xml +156 -0
  39. data/spec/fixtures/wsdl/namespaced_actions.xml +307 -0
  40. data/spec/fixtures/wsdl/no_namespace.xml +115 -0
  41. data/spec/fixtures/wsdl/two_bindings.xml +25 -0
  42. data/spec/savon/client_spec.rb +346 -0
  43. data/spec/savon/core_ext/hash_spec.rb +121 -0
  44. data/spec/savon/core_ext/object_spec.rb +19 -0
  45. data/spec/savon/core_ext/string_spec.rb +57 -0
  46. data/spec/savon/core_ext/time_spec.rb +13 -0
  47. data/spec/savon/http/error_spec.rb +52 -0
  48. data/spec/savon/savon_spec.rb +85 -0
  49. data/spec/savon/soap/fault_spec.rb +89 -0
  50. data/spec/savon/soap/request_spec.rb +45 -0
  51. data/spec/savon/soap/response_spec.rb +174 -0
  52. data/spec/savon/soap/xml_spec.rb +335 -0
  53. data/spec/savon/soap_spec.rb +21 -0
  54. data/spec/savon/wsdl/document_spec.rb +132 -0
  55. data/spec/savon/wsdl/parser_spec.rb +99 -0
  56. data/spec/savon/wsdl/request_spec.rb +15 -0
  57. data/spec/savon/wsse_spec.rb +213 -0
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/endpoint.rb +25 -0
  60. data/spec/support/fixture.rb +37 -0
  61. metadata +251 -0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+ gemspec
3
+
4
+ gem "httpclient", "~> 2.1.5"
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Daniel Harrington
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ Savon
2
+ =====
3
+
4
+ Heavy metal Ruby SOAP client
5
+
6
+ [Guide](http://rubiii.github.com/savon) | [Rubydoc](http://rubydoc.info/gems/savon) | [Google Group](http://groups.google.com/group/savon-soap) | [Wishlist](http://savon.uservoice.com) | [Bugs](http://github.com/rubiii/savon/issues)
7
+
8
+ Installation
9
+ ------------
10
+
11
+ Savon is available through [Rubygems](http://rubygems.org/gems/savon) and can be installed via:
12
+
13
+ $ gem install savon
14
+
15
+ Basic workflow
16
+ --------------
17
+
18
+ # Setting up a Savon::Client representing a SOAP service.
19
+ client = Savon::Client.new do
20
+ wsdl.document = "http://service.example.com?wsdl"
21
+ end
22
+
23
+ client.wsdl.soap_actions
24
+ # => [:create_user, :get_user, :get_all_users]
25
+
26
+ # Executing a SOAP request to call a "getUser" action.
27
+ response = client.request :get_user do
28
+ soap.body = { :id => 1 }
29
+ end
30
+
31
+ response.to_hash
32
+ # => { :get_user_response => { :first_name => "The", :last_name => "Hoff" } }
33
+
34
+ Excited to learn more?
35
+ ----------------------
36
+
37
+ Then you might want to go ahead and read the [Savon Guide](http://rubiii.github.com/savon).
@@ -0,0 +1,40 @@
1
+ require "rake"
2
+
3
+ begin
4
+ require "yard"
5
+
6
+ YARD::Rake::YardocTask.new do |t|
7
+ t.files = ["README.md", "lib/**/*.rb"]
8
+ end
9
+ rescue LoadError
10
+ desc message = %{"gem install yard" to generate documentation}
11
+ task("yard") { abort message }
12
+ end
13
+
14
+ begin
15
+ require "metric_fu"
16
+
17
+ MetricFu::Configuration.run do |c|
18
+ c.metrics = [:churn, :flog, :flay, :reek, :roodi, :saikuro] # :rcov seems to be broken
19
+ c.graphs = [:flog, :flay, :reek, :roodi]
20
+ c.flay = { :dirs_to_flay => ["lib"], :minimum_score => 20 }
21
+ c.rcov[:rcov_opts] << "-Ilib -Ispec"
22
+ end
23
+ rescue LoadError
24
+ desc message = %{"gem install metric_fu" to generate metrics}
25
+ task("metrics:all") { abort message }
26
+ end
27
+
28
+ begin
29
+ require "rspec/core/rake_task"
30
+
31
+ RSpec::Core::RakeTask.new do |t|
32
+ t.rspec_opts = %w(-fd -c)
33
+ end
34
+ rescue LoadError
35
+ desc message = %{"gem install rspec --pre" to run the specs}
36
+ task(:spec) { abort message }
37
+ end
38
+
39
+ task :default => :spec
40
+ task :test => :spec
@@ -0,0 +1,14 @@
1
+ require "savon/version"
2
+ require "savon/global"
3
+ require "savon/client"
4
+
5
+ module Savon
6
+ extend Global
7
+
8
+ # Yields this module to a given +block+. Please refer to the
9
+ # <tt>Savon::Global</tt> module for configuration options.
10
+ def self.configure
11
+ yield self if block_given?
12
+ end
13
+
14
+ end
@@ -0,0 +1,157 @@
1
+ require "httpi/request"
2
+ require "savon/soap/xml"
3
+ require "savon/soap/request"
4
+ require "savon/soap/response"
5
+ require "savon/wsdl/document"
6
+ require "savon/wsse"
7
+
8
+ module Savon
9
+
10
+ # = Savon::Client
11
+ #
12
+ # Savon::Client is the main object for connecting to a SOAP service. It includes methods to access
13
+ # both the Savon::WSDL::Document and HTTPI::Request object.
14
+ class Client
15
+
16
+ # Initializes the Savon::Client for a SOAP service. Accepts a +block+ which is evaluated in the
17
+ # context of this object to let you access the +wsdl+, +http+, and +wsse+ methods.
18
+ #
19
+ # == Examples
20
+ #
21
+ # # Using a remote WSDL
22
+ # client = Savon::Client.new { wsdl.document = "http://example.com/UserService?wsdl" }
23
+ #
24
+ # # Using a local WSDL
25
+ # client = Savon::Client.new { wsdl.document = "../wsdl/user_service.xml" }
26
+ #
27
+ # # Directly accessing a SOAP endpoint
28
+ # client = Savon::Client.new do
29
+ # wsdl.endpoint = "http://example.com/UserService"
30
+ # wsdl.namespace = "http://users.example.com"
31
+ # end
32
+ def initialize(&block)
33
+ process 1, &block if block
34
+ wsdl.request = http
35
+ end
36
+
37
+ # Returns the <tt>Savon::WSDL::Document</tt>.
38
+ def wsdl
39
+ @wsdl ||= WSDL::Document.new
40
+ end
41
+
42
+ # Returns the <tt>HTTPI::Request</tt>.
43
+ def http
44
+ @http ||= HTTPI::Request.new
45
+ end
46
+
47
+ # Returns the <tt>Savon::WSSE</tt> object.
48
+ def wsse
49
+ @wsse ||= WSSE.new
50
+ end
51
+
52
+ # Returns the <tt>Savon::SOAP::XML</tt> object. Please notice, that this object is only available
53
+ # in a block given to <tt>Savon::Client#request</tt>. A new instance of this object is created
54
+ # per SOAP request.
55
+ attr_reader :soap
56
+
57
+ # Executes a SOAP request for a given SOAP action. Accepts a +block+ which is evaluated in the
58
+ # context of this object to let you access the +soap+, +wsdl+, +http+ and +wsse+ methods.
59
+ #
60
+ # == Examples
61
+ #
62
+ # # Calls a "getUser" SOAP action with the payload of "<userId>123</userId>"
63
+ # client.request(:get_user) { soap.body = { :user_id => 123 } }
64
+ #
65
+ # # Prefixes the SOAP input tag with a given namespace: "<wsdl:GetUser>...</wsdl:GetUser>"
66
+ # client.request(:wsdl, "GetUser") { soap.body = { :user_id => 123 } }
67
+ #
68
+ # # SOAP input tag with attributes: <getUser xmlns:wsdl="http://example.com">...</getUser>"
69
+ # client.request(:get_user, "xmlns:wsdl" => "http://example.com")
70
+ def request(*args, &block)
71
+ raise ArgumentError, "Savon::Client#request requires at least one argument" if args.empty?
72
+
73
+ self.soap = SOAP::XML.new
74
+ preconfigure extract_options(args)
75
+ process &block if block
76
+ soap.wsse = wsse
77
+
78
+ response = SOAP::Request.new(http, soap).response
79
+ set_cookie response.http.headers
80
+ response
81
+ end
82
+
83
+ private
84
+
85
+ # Writer for the <tt>Savon::SOAP::XML</tt> object.
86
+ attr_writer :soap
87
+
88
+ # Accessor for the original self of a given block.
89
+ attr_accessor :original_self
90
+
91
+ # Passes a cookie from the last request +headers+ to the next one.
92
+ def set_cookie(headers)
93
+ http.headers["Cookie"] = headers["Set-Cookie"] if headers["Set-Cookie"]
94
+ end
95
+
96
+ # Expects an Array of +args+ and returns an Array containing the namespace (might be +nil+),
97
+ # the SOAP input and a Hash of attributes for the input tag (which might be empty).
98
+ def extract_options(args)
99
+ attributes = Hash === args.last ? args.pop : {}
100
+ namespace = args.size > 1 ? args.shift.to_sym : nil
101
+ input = args.first
102
+
103
+ [namespace, input, attributes]
104
+ end
105
+
106
+ # Expects and Array of +options+ and preconfigures the system.
107
+ def preconfigure(options)
108
+ soap.endpoint = wsdl.endpoint
109
+ soap.namespace_identifier = options[0]
110
+ soap.namespace = wsdl.namespace
111
+ soap.element_form_default = wsdl.element_form_default if wsdl.present?
112
+ soap.body = options[2].delete(:body) if options[2][:body]
113
+
114
+ set_soap_action options[1]
115
+ set_soap_input *options
116
+ end
117
+
118
+ # Expects an +input+ and sets the +SOAPAction+ HTTP headers.
119
+ def set_soap_action(input)
120
+ soap_action = wsdl.soap_action input.to_sym
121
+ soap_action ||= input.kind_of?(String) ? input : input.to_s.lower_camelcase
122
+ http.headers["SOAPAction"] = %{"#{soap_action}"}
123
+ end
124
+
125
+ # Expects a +namespace+, +input+ and +attributes+ and sets the SOAP input.
126
+ def set_soap_input(namespace, input, attributes)
127
+ new_input = wsdl.soap_input input.to_sym
128
+ new_input ||= input.kind_of?(String) ? input.to_sym : input.to_s.lower_camelcase.to_sym
129
+ soap.input = [namespace, new_input, attributes].compact
130
+ end
131
+
132
+ # Processes a given +block+. Yields objects if the block expects any arguments.
133
+ # Otherwise evaluates the block in the context of this object.
134
+ def process(offset = 0, &block)
135
+ block.arity > 0 ? yield_objects(offset, &block) : evaluate(&block)
136
+ end
137
+
138
+ # Yields a number of objects to a given +block+ depending on how many arguments
139
+ # the block is expecting.
140
+ def yield_objects(offset, &block)
141
+ yield *[soap, wsdl, http, wsse][offset, block.arity]
142
+ end
143
+
144
+ # Evaluates a given +block+ inside this object. Stores the original block binding.
145
+ def evaluate(&block)
146
+ self.original_self = eval "self", block.binding
147
+ instance_eval &block
148
+ end
149
+
150
+ # Handles calls to undefined methods by delegating to the original block binding.
151
+ def method_missing(method, *args, &block)
152
+ super unless original_self
153
+ original_self.send method, *args, &block
154
+ end
155
+
156
+ end
157
+ end
@@ -0,0 +1,70 @@
1
+ require "builder"
2
+
3
+ require "savon"
4
+ require "savon/core_ext/object"
5
+ require "savon/core_ext/string"
6
+
7
+ module Savon
8
+ module CoreExt
9
+ module Hash
10
+
11
+ # Returns a new Hash with +self+ and +other_hash+ merged recursively.
12
+ # Modifies the receiver in place.
13
+ def deep_merge!(other_hash)
14
+ other_hash.each_pair do |k,v|
15
+ tv = self[k]
16
+ self[k] = tv.is_a?(Hash) && v.is_a?(Hash) ? tv.deep_merge(v) : v
17
+ end
18
+ self
19
+ end unless defined? deep_merge!
20
+
21
+ # Returns the values from the soap:Header element or an empty Hash in case the element could
22
+ # not be found.
23
+ def find_soap_header
24
+ find_soap_element /.+:Header/
25
+ end
26
+
27
+ # Returns the values from the soap:Body element or an empty Hash in case the element could
28
+ # not be found.
29
+ def find_soap_body
30
+ find_soap_element /.+:Body/
31
+ end
32
+
33
+ # Maps keys and values of a Hash created from SOAP response XML to more convenient Ruby Objects.
34
+ def map_soap_response
35
+ inject({}) do |hash, (key, value)|
36
+ value = case value
37
+ when ::Hash then value["xsi:nil"] ? nil : value.map_soap_response
38
+ when ::Array then value.map { |val| val.map_soap_response rescue val }
39
+ when ::String then value.map_soap_response
40
+ end
41
+
42
+ new_key = if Savon.strip_namespaces?
43
+ key.strip_namespace.snakecase.to_sym
44
+ else
45
+ key.snakecase
46
+ end
47
+
48
+ if hash[new_key] # key already exists, value should be added as an Array
49
+ hash[new_key] = [hash[new_key], value].flatten
50
+ result = hash
51
+ else
52
+ result = hash.merge new_key => value
53
+ end
54
+ result
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def find_soap_element(element)
61
+ envelope = self[keys.first] || {}
62
+ element_key = envelope.keys.find { |key| element =~ key } rescue nil
63
+ element_key ? envelope[element_key].map_soap_response : {}
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+
70
+ Hash.send :include, Savon::CoreExt::Hash
@@ -0,0 +1,14 @@
1
+ module Savon
2
+ module CoreExt
3
+ module Object
4
+
5
+ # Returns +true+ if the Object is nil, false or empty. Implementation from ActiveSupport.
6
+ def blank?
7
+ respond_to?(:empty?) ? empty? : !self
8
+ end unless defined? blank?
9
+
10
+ end
11
+ end
12
+ end
13
+
14
+ Object.send :include, Savon::CoreExt::Object
@@ -0,0 +1,51 @@
1
+ require "savon/soap"
2
+
3
+ module Savon
4
+ module CoreExt
5
+ module String
6
+
7
+ # Returns the String in snake_case.
8
+ def snakecase
9
+ str = dup
10
+ str.gsub! /::/, '/'
11
+ str.gsub! /([A-Z]+)([A-Z][a-z])/, '\1_\2'
12
+ str.gsub! /([a-z\d])([A-Z])/, '\1_\2'
13
+ str.tr! ".", "_"
14
+ str.tr! "-", "_"
15
+ str.downcase!
16
+ str
17
+ end
18
+
19
+ # Returns the String in lowerCamelCase.
20
+ def lower_camelcase
21
+ str = dup
22
+ str.gsub!(/\/(.?)/) { "::#{$1.upcase}" }
23
+ str.gsub!(/(?:_+|-+)([a-z])/) { $1.upcase }
24
+ str.gsub!(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
25
+ str
26
+ end
27
+
28
+ # Returns whether the String starts with a given +prefix+.
29
+ def starts_with?(prefix)
30
+ prefix = prefix.to_s
31
+ self[0, prefix.length] == prefix
32
+ end unless defined? starts_with?
33
+
34
+ # Returns the String without namespace.
35
+ def strip_namespace
36
+ split(":").last
37
+ end
38
+
39
+ # Translates SOAP response values to Ruby Objects.
40
+ def map_soap_response
41
+ return ::DateTime.parse(self) if Savon::SOAP::DateTimeRegexp === self
42
+ return true if self.strip.downcase == "true"
43
+ return false if self.strip.downcase == "false"
44
+ self
45
+ end
46
+
47
+ end
48
+ end
49
+ end
50
+
51
+ String.send :include, Savon::CoreExt::String
@@ -0,0 +1,14 @@
1
+ module Savon
2
+ module CoreExt
3
+ module Time
4
+
5
+ # Returns an xs:dateTime formatted String.
6
+ def xs_datetime
7
+ strftime "%Y-%m-%dT%H:%M:%S%Z"
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+
14
+ Time.send :include, Savon::CoreExt::Time
@@ -0,0 +1,6 @@
1
+ module Savon
2
+
3
+ # Base class for Savon errors.
4
+ class Error < RuntimeError; end
5
+
6
+ end
@@ -0,0 +1,75 @@
1
+ require "logger"
2
+ require "savon/soap"
3
+
4
+ module Savon
5
+ module Global
6
+
7
+ # Sets whether to log HTTP requests.
8
+ attr_writer :log
9
+
10
+ # Returns whether to log HTTP requests. Defaults to +true+.
11
+ def log?
12
+ @log != false
13
+ end
14
+
15
+ # Sets the logger to use.
16
+ attr_writer :logger
17
+
18
+ # Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
19
+ def logger
20
+ @logger ||= ::Logger.new STDOUT
21
+ end
22
+
23
+ # Sets the log level.
24
+ attr_writer :log_level
25
+
26
+ # Returns the log level. Defaults to :debug.
27
+ def log_level
28
+ @log_level ||= :debug
29
+ end
30
+
31
+ # Logs a given +message+.
32
+ def log(message)
33
+ logger.send log_level, message if log?
34
+ end
35
+
36
+ # Sets whether to raise HTTP errors and SOAP faults.
37
+ attr_writer :raise_errors
38
+
39
+ # Returns whether to raise errors. Defaults to +true+.
40
+ def raise_errors?
41
+ @raise_errors != false
42
+ end
43
+
44
+ # Sets the global SOAP version.
45
+ def soap_version=(version)
46
+ raise ArgumentError, "Invalid SOAP version: #{version}" unless SOAP::Versions.include? version
47
+ @version = version
48
+ end
49
+
50
+ # Returns SOAP version. Defaults to +DefaultVersion+.
51
+ def soap_version
52
+ @version ||= SOAP::DefaultVersion
53
+ end
54
+
55
+ # Returns whether to strip namespaces in a SOAP response Hash.
56
+ # Defaults to +true+.
57
+ def strip_namespaces?
58
+ @strip_namespaces != false
59
+ end
60
+
61
+ # Sets whether to strip namespaces in a SOAP response Hash.
62
+ attr_writer :strip_namespaces
63
+
64
+ # Reset to default configuration.
65
+ def reset_config!
66
+ self.log = true
67
+ self.logger = ::Logger.new STDOUT
68
+ self.log_level = :debug
69
+ self.raise_errors = true
70
+ self.soap_version = SOAP::DefaultVersion
71
+ self.strip_namespaces = true
72
+ end
73
+
74
+ end
75
+ end