savon 0.9.9 → 0.9.10
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +1 -0
- data/.yardopts +1 -0
- data/CHANGELOG.md +30 -0
- data/Rakefile +14 -1
- data/lib/savon.rb +13 -5
- data/lib/savon/client.rb +7 -2
- data/lib/savon/config.rb +13 -89
- data/lib/savon/log_message.rb +47 -0
- data/lib/savon/logger.rb +37 -0
- data/lib/savon/model.rb +6 -1
- data/lib/savon/soap/request.rb +16 -13
- data/lib/savon/soap/response.rb +4 -3
- data/lib/savon/soap/xml.rb +17 -10
- data/lib/savon/version.rb +1 -1
- data/savon.gemspec +4 -7
- data/spec/integration/stockquote_spec.rb +14 -0
- data/spec/savon/config_spec.rb +20 -0
- data/spec/savon/logger_spec.rb +51 -0
- data/spec/savon/model_spec.rb +11 -2
- data/spec/savon/savon_spec.rb +17 -91
- data/spec/savon/soap/request_spec.rb +8 -16
- data/spec/savon/soap/response_spec.rb +14 -32
- data/spec/savon/soap/xml_spec.rb +25 -24
- data/spec/spec_helper.rb +3 -2
- metadata +63 -94
- data/lib/savon/core_ext/object.rb +0 -14
- data/spec/savon/core_ext/object_spec.rb +0 -19
data/.travis.yml
CHANGED
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,33 @@
|
|
1
|
+
## 0.9.10 (2012-06-06)
|
2
|
+
|
3
|
+
* Feature: [#289](https://github.com/rubiii/savon/pull/289) - Allow the SOAP envelope header to be set as a String.
|
4
|
+
|
5
|
+
* Feature: In addition to the global configuration, there's now also one configuration per client.
|
6
|
+
The global config is cloned when a new client is initialized and gets used instead of the global one.
|
7
|
+
In addition, for `Savon::Model` classes, the config is cloned per class.
|
8
|
+
|
9
|
+
Closes [#84](https://github.com/rubiii/savon/issues/84) by allowing one logger per client and
|
10
|
+
[#270](https://github.com/rubiii/savon/issues/270) by allowing to specify error handling per client.
|
11
|
+
|
12
|
+
* Feature: Added an option to pretty print XML in log messages. Closes [#256](https://github.com/rubiii/savon/issues/256)
|
13
|
+
and [#280](https://github.com/rubiii/savon/issues/280).
|
14
|
+
|
15
|
+
``` ruby
|
16
|
+
# global
|
17
|
+
Savon.configure do |config|
|
18
|
+
config.pretty_print_xml = true
|
19
|
+
end
|
20
|
+
|
21
|
+
# per client
|
22
|
+
client.config.pretty_print_xml = true
|
23
|
+
```
|
24
|
+
|
25
|
+
* Refactoring:
|
26
|
+
* Added `Savon.client` as a shortcut for creating a new `Savon::Client`
|
27
|
+
* Changed `Savon::Config` from a module to a class.
|
28
|
+
* Moved logging to the new `Savon::Logger` object.
|
29
|
+
* Removed the `blank?` extension from `Object`.
|
30
|
+
|
1
31
|
## 0.9.9 (2012-02-17)
|
2
32
|
|
3
33
|
* Improvement: [pull request 255](https://github.com/rubiii/savon/pull/255) - Raise an error if fetching
|
data/Rakefile
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
-
RSpec::Core::RakeTask.new
|
4
|
+
RSpec::Core::RakeTask.new do |t|
|
5
|
+
t.pattern = "spec/savon/**/*_spec.rb"
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "Run RSpec integration examples"
|
9
|
+
RSpec::Core::RakeTask.new "spec:integration" do |t|
|
10
|
+
t.pattern = "spec/integration/**/*_spec.rb"
|
11
|
+
end
|
12
|
+
|
13
|
+
# http://stackoverflow.com/q/5771758/279024
|
14
|
+
task :require_ruby_18 do
|
15
|
+
raise "This must be run on Ruby 1.8" unless RUBY_VERSION =~ /^1\.8/
|
16
|
+
end
|
17
|
+
task :release => [:require_ruby_18]
|
5
18
|
|
6
19
|
task :default => :spec
|
7
20
|
task :test => :spec
|
data/lib/savon.rb
CHANGED
@@ -4,12 +4,20 @@ require "savon/client"
|
|
4
4
|
require "savon/model"
|
5
5
|
|
6
6
|
module Savon
|
7
|
-
extend
|
7
|
+
extend self
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
def self.configure
|
12
|
-
yield self if block_given?
|
9
|
+
def client(*args)
|
10
|
+
Client.new(*args)
|
13
11
|
end
|
14
12
|
|
13
|
+
def configure
|
14
|
+
yield config
|
15
|
+
end
|
16
|
+
|
17
|
+
def config
|
18
|
+
@config ||= Config.default
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_writer :config
|
22
|
+
|
15
23
|
end
|
data/lib/savon/client.rb
CHANGED
@@ -30,11 +30,16 @@ module Savon
|
|
30
30
|
# wsdl.namespace = "http://users.example.com"
|
31
31
|
# end
|
32
32
|
def initialize(wsdl_document = nil, &block)
|
33
|
+
self.config = Savon.config.clone
|
33
34
|
wsdl.document = wsdl_document if wsdl_document
|
35
|
+
|
34
36
|
process 1, &block if block
|
35
37
|
wsdl.request = http
|
36
38
|
end
|
37
39
|
|
40
|
+
# Accessor for the <tt>Savon::Config</tt>.
|
41
|
+
attr_accessor :config
|
42
|
+
|
38
43
|
# Returns the <tt>Savon::Wasabi::Document</tt>.
|
39
44
|
def wsdl
|
40
45
|
@wsdl ||= Wasabi::Document.new
|
@@ -71,12 +76,12 @@ module Savon
|
|
71
76
|
def request(*args, &block)
|
72
77
|
raise ArgumentError, "Savon::Client#request requires at least one argument" if args.empty?
|
73
78
|
|
74
|
-
self.soap = SOAP::XML.new
|
79
|
+
self.soap = SOAP::XML.new(config)
|
75
80
|
preconfigure extract_options(args)
|
76
81
|
process &block if block
|
77
82
|
soap.wsse = wsse
|
78
83
|
|
79
|
-
response = SOAP::Request.new(http, soap).response
|
84
|
+
response = SOAP::Request.new(config, http, soap).response
|
80
85
|
set_cookie response.http.headers
|
81
86
|
response
|
82
87
|
end
|
data/lib/savon/config.rb
CHANGED
@@ -1,103 +1,27 @@
|
|
1
|
-
require "logger"
|
2
|
-
require "savon/soap"
|
1
|
+
require "savon/logger"
|
3
2
|
require "savon/hooks/group"
|
3
|
+
require "savon/soap"
|
4
4
|
|
5
5
|
module Savon
|
6
|
-
|
7
|
-
|
8
|
-
# Sets whether to log HTTP requests.
|
9
|
-
attr_writer :log
|
10
|
-
|
11
|
-
# Returns whether to log HTTP requests. Defaults to +true+.
|
12
|
-
def log?
|
13
|
-
@log != false
|
14
|
-
end
|
15
|
-
|
16
|
-
# Sets the logger to use.
|
17
|
-
attr_writer :logger
|
18
|
-
|
19
|
-
# Returns the logger. Defaults to an instance of +Logger+ writing to STDOUT.
|
20
|
-
def logger
|
21
|
-
@logger ||= ::Logger.new STDOUT
|
22
|
-
end
|
23
|
-
|
24
|
-
# Sets the log level.
|
25
|
-
attr_writer :log_level
|
26
|
-
|
27
|
-
# Returns the log level. Defaults to :debug.
|
28
|
-
def log_level
|
29
|
-
@log_level ||= :debug
|
30
|
-
end
|
31
|
-
|
32
|
-
# Logs a given +message+. Optionally filtered if +xml+ is truthy.
|
33
|
-
def log(message, xml = false)
|
34
|
-
return unless log?
|
35
|
-
message = filter_xml(message) if xml && !log_filter.empty?
|
36
|
-
logger.send log_level, message
|
37
|
-
end
|
38
|
-
|
39
|
-
# Returns the log filter. Defaults to an empty Array.
|
40
|
-
def log_filter
|
41
|
-
@log_filter ||= []
|
42
|
-
end
|
43
|
-
|
44
|
-
# Sets the log filter. Expects an Array.
|
45
|
-
attr_writer :log_filter
|
46
|
-
|
47
|
-
# Filters the given +xml+ based on log filter.
|
48
|
-
def filter_xml(xml)
|
49
|
-
doc = Nokogiri::XML(xml)
|
50
|
-
return xml unless doc.errors.empty?
|
51
|
-
|
52
|
-
log_filter.each do |filter|
|
53
|
-
doc.xpath("//*[local-name()='#{filter}']").map { |node| node.content = "***FILTERED***" }
|
54
|
-
end
|
55
|
-
|
56
|
-
doc.root.to_s
|
57
|
-
end
|
58
|
-
|
59
|
-
# Sets whether to raise HTTP errors and SOAP faults.
|
60
|
-
attr_writer :raise_errors
|
6
|
+
Config = Struct.new(:logger, :pretty_print_xml, :raise_errors, :soap_version, :env_namespace, :soap_header) do
|
61
7
|
|
62
|
-
|
63
|
-
|
64
|
-
|
8
|
+
def self.default
|
9
|
+
config = new
|
10
|
+
config.logger = Logger.new
|
11
|
+
config.raise_errors = true
|
12
|
+
config.soap_version = SOAP::DefaultVersion
|
13
|
+
config
|
65
14
|
end
|
66
15
|
|
67
|
-
# Sets the global SOAP version.
|
68
|
-
def soap_version=(version)
|
69
|
-
raise ArgumentError, "Invalid SOAP version: #{version}" if version && !SOAP::Versions.include?(version)
|
70
|
-
@version = version
|
71
|
-
end
|
72
|
-
|
73
|
-
# Returns SOAP version. Defaults to +DefaultVersion+.
|
74
|
-
def soap_version
|
75
|
-
@version ||= SOAP::DefaultVersion
|
76
|
-
end
|
77
|
-
|
78
|
-
# Accessor for the global env_namespace.
|
79
|
-
attr_accessor :env_namespace
|
80
|
-
|
81
|
-
# Accessor for the global soap_header.
|
82
|
-
attr_accessor :soap_header
|
83
|
-
|
84
|
-
# Returns the hooks.
|
85
16
|
def hooks
|
86
17
|
@hooks ||= Hooks::Group.new
|
87
18
|
end
|
88
19
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
self.log_level = nil
|
94
|
-
self.log_filter = nil
|
95
|
-
self.raise_errors = nil
|
96
|
-
self.soap_version = nil
|
97
|
-
self.env_namespace = nil
|
98
|
-
self.soap_header = nil
|
20
|
+
def clone
|
21
|
+
config = super
|
22
|
+
config.logger = config.logger.clone
|
23
|
+
config
|
99
24
|
end
|
100
25
|
|
101
26
|
end
|
102
27
|
end
|
103
|
-
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Savon
|
2
|
+
class LogMessage
|
3
|
+
|
4
|
+
def initialize(message, filter, options = {})
|
5
|
+
self.message = message
|
6
|
+
self.filter = filter
|
7
|
+
self.with_pretty = options[:pretty]
|
8
|
+
self.with_filter = options[:filter]
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_accessor :message, :filter, :with_pretty, :with_filter
|
12
|
+
|
13
|
+
def filter?
|
14
|
+
with_filter && filter.any?
|
15
|
+
end
|
16
|
+
|
17
|
+
def pretty?
|
18
|
+
with_pretty
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
return message unless pretty? || filter?
|
23
|
+
|
24
|
+
doc = Nokogiri::XML(message)
|
25
|
+
doc = apply_filter(doc) if filter?
|
26
|
+
doc.to_xml(pretty_options)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def apply_filter(doc)
|
32
|
+
return doc unless doc.errors.empty?
|
33
|
+
|
34
|
+
filter.each do |fi|
|
35
|
+
doc.xpath("//*[local-name()='#{fi}']").each { |node| node.content = "***FILTERED***" }
|
36
|
+
end
|
37
|
+
|
38
|
+
doc
|
39
|
+
end
|
40
|
+
|
41
|
+
def pretty_options
|
42
|
+
return {} unless pretty?
|
43
|
+
{ :indent => 2 }
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
data/lib/savon/logger.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require "logger"
|
2
|
+
require "nokogiri"
|
3
|
+
require "savon/log_message"
|
4
|
+
|
5
|
+
module Savon
|
6
|
+
class Logger
|
7
|
+
|
8
|
+
def initialize(device = $stdout)
|
9
|
+
self.device = device
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :device
|
13
|
+
|
14
|
+
def log(message, options = {})
|
15
|
+
log_raw LogMessage.new(message, filter, options).to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
def log_raw(message)
|
19
|
+
subject.send(level, message)
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_writer :subject, :level, :filter
|
23
|
+
|
24
|
+
def subject
|
25
|
+
@subject ||= ::Logger.new(device)
|
26
|
+
end
|
27
|
+
|
28
|
+
def level
|
29
|
+
@level ||= :debug
|
30
|
+
end
|
31
|
+
|
32
|
+
def filter
|
33
|
+
@filter ||= []
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/savon/model.rb
CHANGED
@@ -31,7 +31,7 @@ module Savon
|
|
31
31
|
class_action_module.module_eval %{
|
32
32
|
def #{action.to_s.snakecase}(body = nil, &block)
|
33
33
|
response = client.request :wsdl, #{action.inspect}, :body => body, &block
|
34
|
-
|
34
|
+
config.hooks.select(:model_soap_response).call(response) || response
|
35
35
|
end
|
36
36
|
}
|
37
37
|
end
|
@@ -49,6 +49,11 @@ module Savon
|
|
49
49
|
def class_action_module
|
50
50
|
@class_action_module ||= Module.new do
|
51
51
|
|
52
|
+
# Returns the memoized <tt>Savon::Config</tt>.
|
53
|
+
def config
|
54
|
+
@config ||= Savon.config.clone
|
55
|
+
end
|
56
|
+
|
52
57
|
# Returns the memoized <tt>Savon::Client</tt>.
|
53
58
|
def client(&block)
|
54
59
|
@client ||= Savon::Client.new(&block)
|
data/lib/savon/soap/request.rb
CHANGED
@@ -14,23 +14,26 @@ module Savon
|
|
14
14
|
|
15
15
|
# Expects an <tt>HTTPI::Request</tt> and a <tt>Savon::SOAP::XML</tt> object
|
16
16
|
# to execute a SOAP request and returns the response.
|
17
|
-
def self.execute(http, soap)
|
18
|
-
new(http, soap).response
|
17
|
+
def self.execute(config, http, soap)
|
18
|
+
new(config, http, soap).response
|
19
19
|
end
|
20
20
|
|
21
|
-
# Expects an <tt>HTTPI::Request</tt
|
22
|
-
|
21
|
+
# Expects an <tt>HTTPI::Request</tt>, a <tt>Savon::SOAP::XML</tt> object
|
22
|
+
# and a <tt>Savon::Config</tt>.
|
23
|
+
def initialize(config, http, soap)
|
24
|
+
self.config = config
|
23
25
|
self.soap = soap
|
24
26
|
self.http = configure(http)
|
25
27
|
end
|
26
28
|
|
27
|
-
attr_accessor :soap, :http
|
29
|
+
attr_accessor :soap, :http, :config
|
28
30
|
|
29
31
|
# Executes the request and returns the response.
|
30
32
|
def response
|
31
|
-
@response ||=
|
32
|
-
|
33
|
-
|
33
|
+
@response ||= begin
|
34
|
+
response = config.hooks.select(:soap_request).call(self) || with_logging { HTTPI.post(http) }
|
35
|
+
SOAP::Response.new(config, response)
|
36
|
+
end
|
34
37
|
end
|
35
38
|
|
36
39
|
private
|
@@ -54,15 +57,15 @@ module Savon
|
|
54
57
|
|
55
58
|
# Logs the SOAP request +url+, +headers+ and +body+.
|
56
59
|
def log_request(url, headers, body)
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
+
config.logger.log "SOAP request: #{url}"
|
61
|
+
config.logger.log headers.map { |key, value| "#{key}: #{value}" }.join(", ")
|
62
|
+
config.logger.log body, :pretty => config.pretty_print_xml, :filter => true
|
60
63
|
end
|
61
64
|
|
62
65
|
# Logs the SOAP response +code+ and +body+.
|
63
66
|
def log_response(code, body)
|
64
|
-
|
65
|
-
|
67
|
+
config.logger.log "SOAP response (status #{code}):"
|
68
|
+
config.logger.log body, :pretty => config.pretty_print_xml
|
66
69
|
end
|
67
70
|
|
68
71
|
end
|
data/lib/savon/soap/response.rb
CHANGED
@@ -12,12 +12,13 @@ module Savon
|
|
12
12
|
class Response
|
13
13
|
|
14
14
|
# Expects an <tt>HTTPI::Response</tt> and handles errors.
|
15
|
-
def initialize(response)
|
15
|
+
def initialize(config, response)
|
16
|
+
self.config = config
|
16
17
|
self.http = response
|
17
|
-
raise_errors if
|
18
|
+
raise_errors if config.raise_errors
|
18
19
|
end
|
19
20
|
|
20
|
-
attr_accessor :http
|
21
|
+
attr_accessor :http, :config
|
21
22
|
|
22
23
|
# Returns whether the request was successful.
|
23
24
|
def success?
|
data/lib/savon/soap/xml.rb
CHANGED
@@ -25,12 +25,15 @@ module Savon
|
|
25
25
|
}
|
26
26
|
|
27
27
|
# Accepts an +endpoint+, an +input+ tag and a SOAP +body+.
|
28
|
-
def initialize(endpoint = nil, input = nil, body = nil)
|
28
|
+
def initialize(config, endpoint = nil, input = nil, body = nil)
|
29
|
+
self.config = config
|
29
30
|
self.endpoint = endpoint if endpoint
|
30
31
|
self.input = input if input
|
31
32
|
self.body = body if body
|
32
33
|
end
|
33
34
|
|
35
|
+
attr_accessor :config
|
36
|
+
|
34
37
|
# Accessor for the SOAP +input+ tag.
|
35
38
|
attr_accessor :input
|
36
39
|
|
@@ -43,9 +46,9 @@ module Savon
|
|
43
46
|
@version = version
|
44
47
|
end
|
45
48
|
|
46
|
-
# Returns the SOAP +version+. Defaults to <tt>Savon.soap_version</tt>.
|
49
|
+
# Returns the SOAP +version+. Defaults to <tt>Savon.config.soap_version</tt>.
|
47
50
|
def version
|
48
|
-
@version ||=
|
51
|
+
@version ||= config.soap_version
|
49
52
|
end
|
50
53
|
|
51
54
|
# Sets the SOAP +header+ Hash.
|
@@ -53,7 +56,7 @@ module Savon
|
|
53
56
|
|
54
57
|
# Returns the SOAP +header+. Defaults to an empty Hash.
|
55
58
|
def header
|
56
|
-
@header ||=
|
59
|
+
@header ||= config.soap_header.nil? ? {} : config.soap_header
|
57
60
|
end
|
58
61
|
|
59
62
|
# Sets the SOAP envelope namespace.
|
@@ -61,7 +64,7 @@ module Savon
|
|
61
64
|
|
62
65
|
# Returns the SOAP envelope namespace. Uses the global namespace if set Defaults to :env.
|
63
66
|
def env_namespace
|
64
|
-
@env_namespace ||=
|
67
|
+
@env_namespace ||= config.env_namespace.nil? ? :env : config.env_namespace
|
65
68
|
end
|
66
69
|
|
67
70
|
# Sets the +namespaces+ Hash.
|
@@ -70,8 +73,9 @@ module Savon
|
|
70
73
|
# Returns the +namespaces+. Defaults to a Hash containing the SOAP envelope namespace.
|
71
74
|
def namespaces
|
72
75
|
@namespaces ||= begin
|
73
|
-
key =
|
74
|
-
|
76
|
+
key = ["xmlns"]
|
77
|
+
key << env_namespace if env_namespace && env_namespace != ""
|
78
|
+
{ key.join(":") => SOAP::Namespace[version] }
|
75
79
|
end
|
76
80
|
end
|
77
81
|
|
@@ -172,8 +176,11 @@ module Savon
|
|
172
176
|
# Expects a builder +xml+ instance, a tag +name+ and accepts optional +namespaces+
|
173
177
|
# and a block to create an XML tag.
|
174
178
|
def tag(xml, name, namespaces = {}, &block)
|
175
|
-
|
176
|
-
|
179
|
+
if env_namespace && env_namespace != ""
|
180
|
+
xml.tag! env_namespace, name, namespaces, &block
|
181
|
+
else
|
182
|
+
xml.tag! name, namespaces, &block
|
183
|
+
end
|
177
184
|
end
|
178
185
|
|
179
186
|
# Returns the complete Hash of namespaces.
|
@@ -185,7 +192,7 @@ module Savon
|
|
185
192
|
|
186
193
|
# Returns the SOAP header as an XML String.
|
187
194
|
def header_for_xml
|
188
|
-
@header_for_xml ||= Gyoku.xml(header) + wsse_header
|
195
|
+
@header_for_xml ||= (Hash === header ? Gyoku.xml(header) : header) + wsse_header
|
189
196
|
end
|
190
197
|
|
191
198
|
# Returns the WSSE header or an empty String in case WSSE was not set.
|