savon 0.9.9 → 0.9.10
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.
- 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.
|