savon 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cae0817f78856aab3d2755e35f8d8eae26f13ab1
4
+ data.tar.gz: f072a4794247d9bbd34e1c20884c398524b34ef3
5
+ SHA512:
6
+ metadata.gz: 60c8697a66a84e2405318c27900207f14734231baa672ca609e77c8425e9ed6e6a897df1e1e62759775acfb14d66194bb08c32210552b0d19cb570c9ff927d70
7
+ data.tar.gz: 1467af7b259d9a6d67b724eabe9cd9905b37580b923c552ce0b7a76251e6a3e960379dab11ba8e3f2555502aebf56b7f8728a1f331c51975b5a905212da1ed1c
data/.gitignore CHANGED
@@ -2,6 +2,7 @@
2
2
  .DS_Store
3
3
  .yardoc
4
4
  doc
5
+ rdox
5
6
  coverage
6
7
  tmp
7
8
  *.rbc
@@ -1,3 +1,51 @@
1
+ ### 2.3.0 (2013-07-27)
2
+
3
+ Combined release ticket: [#481](https://github.com/savonrb/savon/issues/481)
4
+
5
+ * Feature: [#405](https://github.com/savonrb/savon/issues/405) Improved NTLM support based on HTTPI v2.1.0.
6
+
7
+ * Feature: [#424](https://github.com/savonrb/savon/issues/424) Adds support for multipart responses
8
+ through the updated [savon-multipart](https://github.com/savonrb/savon-multipart) gem. You can now
9
+ specify `multipart: true` either as a global or local option. Please make sure you have the
10
+ updated `savon-multipart` gem installed and loaded, as it is not a direct dependency of Savon.
11
+
12
+ ``` ruby
13
+ require 'savon'
14
+ require 'savon-multipart'
15
+
16
+ # expect multipart responses for every operation
17
+ client = Savon.client(wsdl: wsdl, multipart: true)
18
+
19
+ # only expect a multipart response for this operation
20
+ client.call(:my_operation, multipart: true)
21
+ ```
22
+
23
+ * Feature: [#470](https://github.com/savonrb/savon/issues/470) Added a local `:soap_header` option
24
+ to allow setting the SOAP header per request.
25
+
26
+ * Feature: [#402](https://github.com/savonrb/savon/issues/402) Makes it possible to create mocks
27
+ that don't care about the message sent by using `:any` for the `:message` option.
28
+
29
+ ``` ruby
30
+ savon.expects(:authenticate).with(message: :any)
31
+ ```
32
+
33
+ * Fix: [#450](https://github.com/savonrb/savon/pull/450) Added `Savon::Response#soap_fault`
34
+ and `Savon::Response#http_error` which were present in version 1.
35
+
36
+ * Fix: [#474](https://github.com/savonrb/savon/issues/474) Changed `Savon::Response#header` and
37
+ `Savon::Response#body` to respect the global `:convert_response_tags_to` and `:strip_namespaces`
38
+ options and return the expected result instead of raising a `Savon::InvalidResponseError`.
39
+
40
+ * Fix: [#461](https://github.com/savonrb/savon/issues/461) Fixed two problems related to namespace
41
+ qualified messages and the element `:order!`.
42
+
43
+ * Fix: [#476](https://github.com/savonrb/savon/issues/476) fixes a problem where the namespace
44
+ for the message tag was not correctly determined from the WSDL.
45
+
46
+ * Fix: [#468](https://github.com/savonrb/savon/issues/468) Changed the dependency on Nokogiri
47
+ to < 1.6, because Nokogiri 1.6 dropped support for Ruby 1.8.
48
+
1
49
  ### 2.2.0 (2013-04-21)
2
50
 
3
51
  * Feature: [#416](https://github.com/savonrb/savon/pull/416) The global `namespace_identifier`
@@ -10,14 +58,14 @@
10
58
  This is because regardless of whether you're using the Hash or block syntax to pass global
11
59
  or local options, both are just method calls on some options object.
12
60
 
13
- ```
61
+ ``` ruby
14
62
  NoMethodError: undefined method 'wsdk' for #<Savon::GlobalOptions:0x007fed95a55228>
15
63
  ```
16
64
 
17
65
  As of this change, Savon now catches those errors and raise a `Savon::UnknownOptionError`
18
66
  with a slightly more helpful error message instead.
19
67
 
20
- ```
68
+ ``` ruby
21
69
  Savon::UnknownOptionError:
22
70
  Unknown global option: :wsdk
23
71
  ```
@@ -14,7 +14,7 @@ problems and make sure they don't come back.
14
14
 
15
15
  So if you can reproduce your problem in a spec, that would be awesome! If you can't, please
16
16
  let us know how we could make this easier for you. Also, provide code and the WSDL of the
17
- service your working with so others can try to come up with a spec for your problem.
17
+ service you're working with so others can try to come up with a spec for your problem.
18
18
 
19
19
  After we have a failing spec, it obviously needs to be fixed. Make sure your new spec is the
20
20
  only failing one under the `spec` directory. Travis only runs the "unit tests" at `spec/savon`,
data/Gemfile CHANGED
@@ -1,5 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
  gemspec
3
3
 
4
- gem "httpclient", "~> 2.3.0"
5
- gem "simplecov", :require => false, :group => :test
4
+ gem "httpclient", "~> 2.3.4"
5
+
6
+ gem "simplecov", :require => false
7
+ gem "coveralls", :require => false
data/README.md CHANGED
@@ -5,14 +5,15 @@ Heavy metal SOAP client
5
5
  [Documentation](http://savonrb.com) | [RDoc](http://rubydoc.info/gems/savon) |
6
6
  [Mailing list](https://groups.google.com/forum/#!forum/savonrb) | [Twitter](http://twitter.com/savonrb)
7
7
 
8
- [![Build Status](https://secure.travis-ci.org/savonrb/savon.png)](http://travis-ci.org/savonrb/savon)
8
+ [![Build Status](https://secure.travis-ci.org/savonrb/savon.png?branch=version2)](http://travis-ci.org/savonrb/savon)
9
9
  [![Gem Version](https://badge.fury.io/rb/savon.png)](http://badge.fury.io/rb/savon)
10
10
  [![Code Climate](https://codeclimate.com/github/savonrb/savon.png)](https://codeclimate.com/github/savonrb/savon)
11
+ [![Coverage Status](https://coveralls.io/repos/savonrb/savon/badge.png?branch=version2)](https://coveralls.io/r/savonrb/savon)
11
12
 
12
13
 
13
- ## Installation
14
+ ## Version 2
14
15
 
15
- Savon is available through [Rubygems](http://rubygems.org/gems/savon) and can be installed via:
16
+ Savon version 2 is available through [Rubygems](http://rubygems.org/gems/savon) and can be installed via:
16
17
 
17
18
  ```
18
19
  $ gem install savon
@@ -21,7 +22,7 @@ $ gem install savon
21
22
  or add it to your Gemfile like this:
22
23
 
23
24
  ```
24
- gem 'savon', '~> 2.1.0'
25
+ gem 'savon', '~> 2.2.0'
25
26
  ```
26
27
 
27
28
 
@@ -43,15 +44,23 @@ response.body
43
44
  # => { find_user_response: { id: 42, name: 'Hoff' } }
44
45
  ```
45
46
 
46
- For more examples, you should check out the [integration tests](https://github.com/savonrb/savon/tree/master/spec/integration).
47
+ For more examples, you should check out the
48
+ [integration tests](https://github.com/savonrb/savon/tree/version2/spec/integration).
47
49
 
48
50
 
49
- ## Documentation
51
+ ## Give back
52
+
53
+ If you're using Savon and you or your company is making money from it, then please consider
54
+ donating via [Gittip](https://www.gittip.com/rubiii/) so that I can continue to improve it.
55
+
56
+ [![donate](donate.png)](https://www.gittip.com/rubiii/)
50
57
 
51
- Please make sure to read the documentation for your version:
52
58
 
53
- * [Version 2](http://savonrb.com/version2.html)
54
- * [Version 1](http://savonrb.com)
59
+ ## Documentation
60
+
61
+ Please make sure to [read the documentation](http://savonrb.com/version2/).
55
62
 
56
63
  And if you find any problems with it or if you think something's missing,
57
64
  feel free to [help out and improve the documentation](https://github.com/savonrb/savonrb.com).
65
+
66
+ Donate icon from the [Noun Project](http://thenounproject.com/noun/donate/#icon-No285).
Binary file
@@ -101,12 +101,13 @@ module Savon
101
101
  end
102
102
 
103
103
  def namespaced_message_tag
104
+ tag_name = message_tag
104
105
  if namespace_identifier == nil
105
- [message_tag, message_attributes]
106
- elsif @used_namespaces[[@operation_name.to_s]]
107
- [@used_namespaces[[@operation_name.to_s]], message_tag, message_attributes]
106
+ [tag_name, message_attributes]
107
+ elsif @used_namespaces[[tag_name.to_s]]
108
+ [@used_namespaces[[tag_name.to_s]], tag_name, message_attributes]
108
109
  else
109
- [namespace_identifier, message_tag, message_attributes]
110
+ [namespace_identifier, tag_name, message_attributes]
110
111
  end
111
112
  end
112
113
 
@@ -1,4 +1,3 @@
1
- require "savon/soap"
2
1
 
3
2
  module Savon
4
3
  module CoreExt
@@ -5,37 +5,65 @@ module Savon
5
5
  class Header
6
6
 
7
7
  def initialize(globals, locals)
8
- @globals = globals
9
- @locals = locals
10
- @wsse = create_wsse
8
+ @gyoku_options = { :key_converter => globals[:convert_request_keys_to] }
9
+
10
+ @wsse_auth = globals[:wsse_auth]
11
+ @wsse_timestamp = globals[:wsse_timestamp]
12
+
13
+ @global_header = globals[:soap_header]
14
+ @local_header = locals[:soap_header]
15
+
16
+ @header = build
11
17
  end
12
18
 
19
+ attr_reader :local_header, :global_header, :gyoku_options,
20
+ :wsse_auth, :wsse_timestamp
21
+
13
22
  def empty?
14
- to_s.empty?
23
+ @header.empty?
15
24
  end
16
25
 
17
26
  def to_s
18
- return @header if @header
19
-
20
- gyoku_options = { :key_converter => @globals[:convert_request_keys_to] }
21
- @header = (Hash === header ? Gyoku.xml(header, gyoku_options) : header) + wsse_header
27
+ @header
22
28
  end
23
29
 
24
30
  private
25
31
 
26
- def create_wsse
27
- wsse = Akami.wsse
28
- wsse.credentials(*@globals[:wsse_auth]) if @globals.include? :wsse_auth
29
- wsse.timestamp = @globals[:wsse_timestamp] if @globals.include? :wsse_timestamp
30
- wsse
32
+ def build
33
+ build_header + build_wsse_header
31
34
  end
32
35
 
33
- def header
34
- @header ||= @globals.include?(:soap_header) ? @globals[:soap_header] : {}
36
+ def build_header
37
+ header =
38
+ if global_header.kind_of?(Hash) && local_header.kind_of?(Hash)
39
+ global_header.merge(local_header)
40
+ elsif local_header
41
+ local_header
42
+ else
43
+ global_header
44
+ end
45
+
46
+ convert_to_xml(header)
47
+ end
48
+
49
+ def build_wsse_header
50
+ wsse_header = akami
51
+ wsse_header.respond_to?(:to_xml) ? wsse_header.to_xml : ""
35
52
  end
36
53
 
37
- def wsse_header
38
- @wsse.respond_to?(:to_xml) ? @wsse.to_xml : ""
54
+ def convert_to_xml(hash_or_string)
55
+ if hash_or_string.kind_of? Hash
56
+ Gyoku.xml(hash_or_string, gyoku_options)
57
+ else
58
+ hash_or_string.to_s
59
+ end
60
+ end
61
+
62
+ def akami
63
+ wsse = Akami.wsse
64
+ wsse.credentials(*wsse_auth) if wsse_auth
65
+ wsse.timestamp = wsse_timestamp if wsse_timestamp
66
+ wsse
39
67
  end
40
68
 
41
69
  end
@@ -20,9 +20,7 @@ module Savon
20
20
 
21
21
  if @element_form_default == :qualified
22
22
  translated_operation_name = Gyoku.xml_tag(@operation_name, :key_converter => @key_converter).to_s
23
- # XXX: there is no `@request_key_converter` instance variable!
24
- # the third argument is therefore always `nil`. [dh, 2013-03-09]
25
- @message = QualifiedMessage.new(@types, @used_namespaces, @request_key_converter).to_hash(@message, [translated_operation_name])
23
+ @message = QualifiedMessage.new(@types, @used_namespaces, @key_converter).to_hash(@message, [translated_operation_name])
26
24
  end
27
25
 
28
26
  gyoku_options = {
@@ -54,6 +54,7 @@ module Savon
54
54
  end
55
55
 
56
56
  def verify_message!
57
+ return if @expected[:message] == :any
57
58
  unless @expected[:message] == @actual[:message]
58
59
  expected_message = " with this message: #{@expected[:message].inspect}" if @expected[:message]
59
60
  expected_message ||= " with no message."
@@ -3,7 +3,7 @@ require "savon/block_interface"
3
3
  require "savon/request"
4
4
  require "savon/builder"
5
5
  require "savon/response"
6
- require "savon/log_message"
6
+ require "savon/request_logger"
7
7
 
8
8
  module Savon
9
9
  class Operation
@@ -35,6 +35,8 @@ module Savon
35
35
  @name = name
36
36
  @wsdl = wsdl
37
37
  @globals = globals
38
+
39
+ @logger = RequestLogger.new(globals)
38
40
  end
39
41
 
40
42
  def build(locals = {}, &block)
@@ -46,15 +48,33 @@ module Savon
46
48
  builder = build(locals, &block)
47
49
 
48
50
  response = Savon.notify_observers(@name, builder, @globals, @locals)
49
- response ||= call! build_request(builder)
51
+ response ||= call_with_logging build_request(builder)
50
52
 
51
53
  raise_expected_httpi_response! unless response.kind_of?(HTTPI::Response)
52
54
 
53
- Response.new(response, @globals, @locals)
55
+ create_response(response)
54
56
  end
55
57
 
56
58
  private
57
59
 
60
+ def create_response(response)
61
+ if multipart_supported?
62
+ Multipart::Response.new(response, @globals, @locals)
63
+ else
64
+ Response.new(response, @globals, @locals)
65
+ end
66
+ end
67
+
68
+ def multipart_supported?
69
+ return false unless @globals[:multipart] || @locals[:multipart]
70
+
71
+ if Savon.const_defined? :Multipart
72
+ true
73
+ else
74
+ raise 'Unable to find Savon::Multipart. Make sure the savon-multipart gem is installed and loaded.'
75
+ end
76
+ end
77
+
58
78
  def set_locals(locals, block)
59
79
  locals = LocalOptions.new(locals)
60
80
  BlockInterface.new(locals).evaluate(block) if block
@@ -62,12 +82,8 @@ module Savon
62
82
  @locals = locals
63
83
  end
64
84
 
65
- def call!(request)
66
- log_request(request) if log?
67
- response = HTTPI.post(request)
68
- log_response(response) if log?
69
-
70
- response
85
+ def call_with_logging(request)
86
+ @logger.log(request) { HTTPI.post(request) }
71
87
  end
72
88
 
73
89
  def build_request(builder)
@@ -102,33 +118,6 @@ module Savon
102
118
  @globals[:endpoint] || @wsdl.endpoint
103
119
  end
104
120
 
105
- def log_request(request)
106
- logger.info "SOAP request: #{request.url}"
107
- logger.info headers_to_log(request.headers)
108
- logger.debug body_to_log(request.body)
109
- end
110
-
111
- def log_response(response)
112
- logger.info "SOAP response (status #{response.code})"
113
- logger.debug body_to_log(response.body)
114
- end
115
-
116
- def headers_to_log(headers)
117
- headers.map { |key, value| "#{key}: #{value}" }.join(", ")
118
- end
119
-
120
- def body_to_log(body)
121
- LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s
122
- end
123
-
124
- def logger
125
- @globals[:logger]
126
- end
127
-
128
- def log?
129
- @globals[:log]
130
- end
131
-
132
121
  def raise_expected_httpi_response!
133
122
  raise Error, "Observers need to return an HTTPI::Response to mock " \
134
123
  "the request or nil to execute the request."
@@ -53,7 +53,8 @@ module Savon
53
53
  :pretty_print_xml => false,
54
54
  :raise_errors => true,
55
55
  :strip_namespaces => true,
56
- :convert_response_tags_to => lambda { |tag| tag.snakecase.to_sym }
56
+ :convert_response_tags_to => lambda { |tag| tag.snakecase.to_sym},
57
+ :multipart => false,
57
58
  }
58
59
 
59
60
  options = defaults.merge(options)
@@ -117,7 +118,7 @@ module Savon
117
118
  @options[:encoding] = encoding
118
119
  end
119
120
 
120
- # The global SOAP header. Expected to be a Hash.
121
+ # The global SOAP header. Expected to be a Hash or responding to #to_s.
121
122
  def soap_header(header)
122
123
  @options[:soap_header] = header
123
124
  end
@@ -219,6 +220,11 @@ module Savon
219
220
  @options[:digest_auth] = credentials.flatten
220
221
  end
221
222
 
223
+ # NTLM auth credentials.
224
+ def ntlm(*credentials)
225
+ @options[:ntlm] = credentials.flatten
226
+ end
227
+
222
228
  # WSSE auth credentials for Akami.
223
229
  def wsse_auth(*credentials)
224
230
  @options[:wsse_auth] = credentials.flatten
@@ -246,6 +252,11 @@ module Savon
246
252
  def convert_response_tags_to(converter = nil, &block)
247
253
  @options[:convert_response_tags_to] = block || converter
248
254
  end
255
+
256
+ # Instruct Savon to create a multipart response if available.
257
+ def multipart(multipart)
258
+ @options[:multipart] = multipart
259
+ end
249
260
  end
250
261
 
251
262
  class LocalOptions < Options
@@ -255,12 +266,20 @@ module Savon
255
266
 
256
267
  defaults = {
257
268
  :advanced_typecasting => true,
258
- :response_parser => :nokogiri
269
+ :response_parser => :nokogiri,
270
+ :multipart => false
259
271
  }
260
272
 
261
273
  super defaults.merge(options)
262
274
  end
263
275
 
276
+ # The local SOAP header. Expected to be a Hash or respond to #to_s.
277
+ # Will be merged with the global SOAP header if both are Hashes.
278
+ # Otherwise the local option will be prefered.
279
+ def soap_header(header)
280
+ @options[:soap_header] = header
281
+ end
282
+
264
283
  # The SOAP message to send. Expected to be a Hash or a String.
265
284
  def message(message)
266
285
  @options[:message] = message
@@ -302,5 +321,10 @@ module Savon
302
321
  @options[:response_parser] = parser
303
322
  end
304
323
 
324
+ # Instruct Savon to create a multipart response if available.
325
+ def multipart(multipart)
326
+ @options[:multipart] = multipart
327
+ end
328
+
305
329
  end
306
330
  end