s-savon 0.8.6

Sign up to get free protection for your applications and to get access to all the features.
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