savon 2.2.0 → 2.12.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.travis.yml +20 -9
  4. data/CHANGELOG.md +157 -10
  5. data/CONTRIBUTING.md +1 -1
  6. data/Gemfile +10 -2
  7. data/README.md +38 -13
  8. data/donate.png +0 -0
  9. data/lib/savon/builder.rb +81 -15
  10. data/lib/savon/client.rb +6 -2
  11. data/lib/savon/core_ext/string.rb +0 -1
  12. data/lib/savon/header.rb +68 -17
  13. data/lib/savon/log_message.rb +7 -3
  14. data/lib/savon/message.rb +6 -7
  15. data/lib/savon/mock/expectation.rb +12 -2
  16. data/lib/savon/model.rb +4 -0
  17. data/lib/savon/operation.rb +45 -38
  18. data/lib/savon/options.rb +149 -22
  19. data/lib/savon/qualified_message.rb +31 -25
  20. data/lib/savon/request.rb +24 -4
  21. data/lib/savon/request_logger.rb +48 -0
  22. data/lib/savon/response.rb +35 -18
  23. data/lib/savon/soap_fault.rb +11 -11
  24. data/lib/savon/version.rb +1 -3
  25. data/savon.gemspec +12 -11
  26. data/spec/fixtures/response/empty_soap_fault.xml +13 -0
  27. data/spec/fixtures/response/f5.xml +39 -0
  28. data/spec/fixtures/response/no_body.xml +1 -0
  29. data/spec/fixtures/response/soap_fault_funky.xml +8 -0
  30. data/spec/fixtures/wsdl/brand.xml +624 -0
  31. data/spec/fixtures/wsdl/elements_in_types.xml +43 -0
  32. data/spec/fixtures/wsdl/no_message_tag.xml +1267 -0
  33. data/spec/fixtures/wsdl/vies.xml +176 -0
  34. data/spec/integration/centra_spec.rb +67 -0
  35. data/spec/integration/email_example_spec.rb +1 -1
  36. data/spec/integration/random_quote_spec.rb +23 -0
  37. data/spec/integration/stockquote_example_spec.rb +7 -1
  38. data/spec/integration/support/application.rb +1 -1
  39. data/spec/integration/zipcode_example_spec.rb +1 -1
  40. data/spec/savon/builder_spec.rb +50 -0
  41. data/spec/savon/client_spec.rb +78 -0
  42. data/spec/savon/core_ext/string_spec.rb +9 -9
  43. data/spec/savon/features/message_tag_spec.rb +5 -0
  44. data/spec/savon/http_error_spec.rb +2 -2
  45. data/spec/savon/log_message_spec.rb +18 -1
  46. data/spec/savon/message_spec.rb +70 -0
  47. data/spec/savon/mock_spec.rb +31 -0
  48. data/spec/savon/model_spec.rb +28 -0
  49. data/spec/savon/operation_spec.rb +69 -3
  50. data/spec/savon/options_spec.rb +515 -87
  51. data/spec/savon/qualified_message_spec.rb +101 -0
  52. data/spec/savon/request_logger_spec.rb +37 -0
  53. data/spec/savon/request_spec.rb +85 -10
  54. data/spec/savon/response_spec.rb +118 -27
  55. data/spec/savon/soap_fault_spec.rb +25 -5
  56. data/spec/savon/softlayer_spec.rb +27 -0
  57. data/spec/spec_helper.rb +5 -2
  58. data/spec/support/adapters.rb +48 -0
  59. data/spec/support/integration.rb +1 -1
  60. metadata +76 -93
@@ -2,44 +2,50 @@ require "gyoku"
2
2
 
3
3
  module Savon
4
4
  class QualifiedMessage
5
-
6
5
  def initialize(types, used_namespaces, key_converter)
7
- @types = types
6
+ @types = types
8
7
  @used_namespaces = used_namespaces
9
- @key_converter = key_converter
8
+ @key_converter = key_converter
10
9
  end
11
10
 
12
11
  def to_hash(hash, path)
13
- return unless hash
14
- return hash.map { |value| to_hash(value, path) } if hash.kind_of?(Array)
15
- return hash.to_s unless hash.kind_of? Hash
16
-
17
- hash.inject({}) do |newhash, (key, value)|
18
- translated_key = Gyoku.xml_tag(key, :key_converter => @key_converter).to_s
19
- newpath = path + [translated_key]
20
-
21
- if @used_namespaces[newpath]
22
- newhash.merge(
23
- "#{@used_namespaces[newpath]}:#{translated_key}" =>
24
- to_hash(value, @types[newpath] ? [@types[newpath]] : newpath)
25
- )
12
+ return hash unless hash
13
+ return hash.map { |value| to_hash(value, path) } if hash.is_a?(Array)
14
+ return hash.to_s unless hash.is_a?(Hash)
15
+
16
+ hash.each_with_object({}) do |(key, value), newhash|
17
+ case key
18
+ when :order!
19
+ newhash[key] = add_namespaces_to_values(value, path)
20
+ when :attributes!, :content!
21
+ newhash[key] = to_hash(value, path)
26
22
  else
27
- add_namespaces_to_values(value, path) if key == :order!
28
- newhash.merge(key => value)
23
+ if key.to_s =~ /!$/
24
+ newhash[key] = value
25
+ else
26
+ translated_key = translate_tag(key)
27
+ newkey = add_namespaces_to_values(key, path).first
28
+ newpath = path + [translated_key]
29
+ newhash[newkey] = to_hash(value, @types[newpath] ? [@types[newpath]] : newpath)
30
+ end
29
31
  end
32
+ newhash
30
33
  end
31
34
  end
32
35
 
33
36
  private
34
37
 
35
- def add_namespaces_to_values(values, path)
36
- values.collect! { |value|
37
- camelcased_value = Gyoku.xml_tag(value, :key_converter => @key_converter)
38
- namespace_path = path + [camelcased_value.to_s]
39
- namespace = @used_namespaces[namespace_path]
40
- "#{namespace.blank? ? '' : namespace + ":"}#{camelcased_value}"
41
- }
38
+ def translate_tag(key)
39
+ Gyoku.xml_tag(key, :key_converter => @key_converter).to_s
42
40
  end
43
41
 
42
+ def add_namespaces_to_values(values, path)
43
+ Array(values).collect do |value|
44
+ translated_value = translate_tag(value)
45
+ namespace_path = path + [translated_value]
46
+ namespace = @used_namespaces[namespace_path]
47
+ namespace.blank? ? value : "#{namespace}:#{translated_value}"
48
+ end
49
+ end
44
50
  end
45
51
  end
@@ -26,10 +26,16 @@ module Savon
26
26
  def configure_ssl
27
27
  @http_request.auth.ssl.ssl_version = @globals[:ssl_version] if @globals.include? :ssl_version
28
28
  @http_request.auth.ssl.verify_mode = @globals[:ssl_verify_mode] if @globals.include? :ssl_verify_mode
29
+ @http_request.auth.ssl.ciphers = @globals[:ssl_ciphers] if @globals.include? :ssl_ciphers
29
30
 
30
31
  @http_request.auth.ssl.cert_key_file = @globals[:ssl_cert_key_file] if @globals.include? :ssl_cert_key_file
32
+ @http_request.auth.ssl.cert_key = @globals[:ssl_cert_key] if @globals.include? :ssl_cert_key
31
33
  @http_request.auth.ssl.cert_file = @globals[:ssl_cert_file] if @globals.include? :ssl_cert_file
34
+ @http_request.auth.ssl.cert = @globals[:ssl_cert] if @globals.include? :ssl_cert
32
35
  @http_request.auth.ssl.ca_cert_file = @globals[:ssl_ca_cert_file] if @globals.include? :ssl_ca_cert_file
36
+ @http_request.auth.ssl.ca_cert_path = @globals[:ssl_ca_cert_path] if @globals.include? :ssl_ca_cert_path
37
+ @http_request.auth.ssl.ca_cert = @globals[:ssl_ca_cert] if @globals.include? :ssl_ca_cert
38
+ @http_request.auth.ssl.cert_store = @globals[:ssl_cert_store] if @globals.include? :ssl_cert_store
33
39
 
34
40
  @http_request.auth.ssl.cert_key_password = @globals[:ssl_cert_key_password] if @globals.include? :ssl_cert_key_password
35
41
  end
@@ -37,8 +43,14 @@ module Savon
37
43
  def configure_auth
38
44
  @http_request.auth.basic(*@globals[:basic_auth]) if @globals.include? :basic_auth
39
45
  @http_request.auth.digest(*@globals[:digest_auth]) if @globals.include? :digest_auth
46
+ @http_request.auth.ntlm(*@globals[:ntlm]) if @globals.include? :ntlm
40
47
  end
41
48
 
49
+ def configure_redirect_handling
50
+ if @globals.include? :follow_redirects
51
+ @http_request.follow_redirect = @globals[:follow_redirects]
52
+ end
53
+ end
42
54
  end
43
55
 
44
56
  class WSDLRequest < HTTPRequest
@@ -46,12 +58,19 @@ module Savon
46
58
  def build
47
59
  configure_proxy
48
60
  configure_timeouts
61
+ configure_headers
49
62
  configure_ssl
50
63
  configure_auth
64
+ configure_redirect_handling
51
65
 
52
66
  @http_request
53
67
  end
54
68
 
69
+ private
70
+
71
+ def configure_headers
72
+ @http_request.headers = @globals[:headers] if @globals.include? :headers
73
+ end
55
74
  end
56
75
 
57
76
  class SOAPRequest < HTTPRequest
@@ -63,11 +82,12 @@ module Savon
63
82
 
64
83
  def build(options = {})
65
84
  configure_proxy
66
- configure_cookies options[:cookies]
67
85
  configure_timeouts
68
- configure_headers options[:soap_action]
86
+ configure_headers options[:soap_action], options[:headers]
87
+ configure_cookies options[:cookies]
69
88
  configure_ssl
70
89
  configure_auth
90
+ configure_redirect_handling
71
91
 
72
92
  @http_request
73
93
  end
@@ -78,11 +98,11 @@ module Savon
78
98
  @http_request.set_cookies(cookies) if cookies
79
99
  end
80
100
 
81
- def configure_headers(soap_action)
101
+ def configure_headers(soap_action, headers)
82
102
  @http_request.headers = @globals[:headers] if @globals.include? :headers
103
+ @http_request.headers.merge!(headers) if headers
83
104
  @http_request.headers["SOAPAction"] ||= %{"#{soap_action}"} if soap_action
84
105
  @http_request.headers["Content-Type"] ||= CONTENT_TYPE[@globals[:soap_version]] % @globals[:encoding]
85
106
  end
86
-
87
107
  end
88
108
  end
@@ -0,0 +1,48 @@
1
+ require "savon/log_message"
2
+
3
+ module Savon
4
+ class RequestLogger
5
+
6
+ def initialize(globals)
7
+ @globals = globals
8
+ end
9
+
10
+ def log(request, &http_request)
11
+ log_request(request) if log?
12
+ response = http_request.call
13
+ log_response(response) if log?
14
+
15
+ response
16
+ end
17
+
18
+ def logger
19
+ @globals[:logger]
20
+ end
21
+
22
+ def log?
23
+ @globals[:log]
24
+ end
25
+
26
+ private
27
+
28
+ def log_request(request)
29
+ logger.info { "SOAP request: #{request.url}" }
30
+ logger.info { headers_to_log(request.headers) }
31
+ logger.debug { body_to_log(request.body) }
32
+ end
33
+
34
+ def log_response(response)
35
+ logger.info { "SOAP response (status #{response.code})" }
36
+ logger.debug { body_to_log(response.body) }
37
+ end
38
+
39
+ def headers_to_log(headers)
40
+ headers.map { |key, value| "#{key}: #{value}" }.join(", ")
41
+ end
42
+
43
+ def body_to_log(body)
44
+ LogMessage.new(body, @globals[:filters], @globals[:pretty_print_xml]).to_s
45
+ end
46
+
47
+ end
48
+ end
@@ -10,18 +10,19 @@ module Savon
10
10
  @globals = globals
11
11
  @locals = locals
12
12
 
13
+ build_soap_and_http_errors!
13
14
  raise_soap_and_http_errors! if @globals[:raise_errors]
14
15
  end
15
16
 
16
- attr_reader :http, :globals, :locals
17
+ attr_reader :http, :globals, :locals, :soap_fault, :http_error
17
18
 
18
19
  def success?
19
20
  !soap_fault? && !http_error?
20
21
  end
21
- alias successful? success?
22
+ alias_method :successful?, :success?
22
23
 
23
24
  def soap_fault?
24
- SOAPFault.present? @http
25
+ SOAPFault.present?(@http, xml)
25
26
  end
26
27
 
27
28
  def http_error?
@@ -29,15 +30,14 @@ module Savon
29
30
  end
30
31
 
31
32
  def header
32
- raise_invalid_response_error! unless hash.key? :envelope
33
- hash[:envelope][:header]
33
+ find('Header')
34
34
  end
35
35
 
36
36
  def body
37
- raise_invalid_response_error! unless hash.key? :envelope
38
- hash[:envelope][:body]
37
+ find('Body')
39
38
  end
40
- alias to_hash body
39
+
40
+ alias_method :to_hash, :body
41
41
 
42
42
  def to_array(*path)
43
43
  result = path.inject body do |memo, key|
@@ -49,30 +49,45 @@ module Savon
49
49
  end
50
50
 
51
51
  def hash
52
- @hash ||= nori.parse(to_xml)
52
+ @hash ||= nori.parse(xml)
53
53
  end
54
54
 
55
- def to_xml
55
+ def xml
56
56
  @http.body
57
57
  end
58
58
 
59
+ alias_method :to_xml, :xml
60
+ alias_method :to_s, :xml
61
+
59
62
  def doc
60
- @doc ||= Nokogiri.XML(to_xml)
63
+ @doc ||= Nokogiri.XML(xml)
61
64
  end
62
65
 
63
66
  def xpath(path, namespaces = nil)
64
67
  doc.xpath(path, namespaces || xml_namespaces)
65
68
  end
66
69
 
70
+ def find(*path)
71
+ envelope = nori.find(hash, 'Envelope')
72
+ raise_invalid_response_error! unless envelope.is_a?(Hash)
73
+
74
+ nori.find(envelope, *path)
75
+ end
76
+
67
77
  private
68
78
 
79
+ def build_soap_and_http_errors!
80
+ @soap_fault = SOAPFault.new(@http, nori, xml) if soap_fault?
81
+ @http_error = HTTPError.new(@http) if http_error?
82
+ end
83
+
69
84
  def raise_soap_and_http_errors!
70
- raise SOAPFault.new(@http, nori) if soap_fault?
71
- raise HTTPError.new(@http) if http_error?
85
+ raise soap_fault if soap_fault?
86
+ raise http_error if http_error?
72
87
  end
73
88
 
74
89
  def raise_invalid_response_error!
75
- raise InvalidResponseError, "Unable to parse response body:\n" + to_xml.inspect
90
+ raise InvalidResponseError, "Unable to parse response body:\n" + xml.inspect
76
91
  end
77
92
 
78
93
  def xml_namespaces
@@ -83,10 +98,12 @@ module Savon
83
98
  return @nori if @nori
84
99
 
85
100
  nori_options = {
86
- :strip_namespaces => @globals[:strip_namespaces],
87
- :convert_tags_to => @globals[:convert_response_tags_to],
88
- :advanced_typecasting => @locals[:advanced_typecasting],
89
- :parser => @locals[:response_parser]
101
+ :delete_namespace_attributes => @globals[:delete_namespace_attributes],
102
+ :strip_namespaces => @globals[:strip_namespaces],
103
+ :convert_tags_to => @globals[:convert_response_tags_to],
104
+ :convert_attributes_to => @globals[:convert_attributes_to],
105
+ :advanced_typecasting => @locals[:advanced_typecasting],
106
+ :parser => @locals[:response_parser]
90
107
  }
91
108
 
92
109
  non_nil_nori_options = nori_options.reject { |_, value| value.nil? }
@@ -1,31 +1,31 @@
1
- require "savon"
2
-
3
1
  module Savon
4
2
  class SOAPFault < Error
5
3
 
6
- def self.present?(http)
7
- fault_node = http.body.include?("Fault>")
8
- soap1_fault = http.body.include?("faultcode>") && http.body.include?("faultstring>")
9
- soap2_fault = http.body.include?("Code>") && http.body.include?("Reason>")
4
+ def self.present?(http, xml = nil)
5
+ xml ||= http.body
6
+ fault_node = xml.include?("Fault>")
7
+ soap1_fault = xml.match(/faultcode\/?\>/) && xml.match(/faultstring\/?\>/)
8
+ soap2_fault = xml.include?("Code>") && xml.include?("Reason>")
10
9
 
11
10
  fault_node && (soap1_fault || soap2_fault)
12
11
  end
13
12
 
14
- def initialize(http, nori)
13
+ def initialize(http, nori, xml = nil)
14
+ @xml = xml
15
15
  @http = http
16
16
  @nori = nori
17
17
  end
18
18
 
19
- attr_reader :http, :nori
19
+ attr_reader :http, :nori, :xml
20
20
 
21
21
  def to_s
22
- fault = nori.find(to_hash, 'Fault')
22
+ fault = nori.find(to_hash, 'Fault') || nori.find(to_hash, 'ServiceFault')
23
23
  message_by_version(fault)
24
24
  end
25
25
 
26
26
  def to_hash
27
- parsed = nori.parse(@http.body)
28
- nori.find(parsed, 'Envelope', 'Body')
27
+ parsed = nori.parse(xml || http.body)
28
+ nori.find(parsed, 'Envelope', 'Body') || {}
29
29
  end
30
30
 
31
31
  private
@@ -1,5 +1,3 @@
1
1
  module Savon
2
-
3
- VERSION = "2.2.0"
4
-
2
+ VERSION = '2.12.1'
5
3
  end
@@ -12,24 +12,25 @@ Gem::Specification.new do |s|
12
12
  s.homepage = "http://savonrb.com"
13
13
  s.summary = "Heavy metal SOAP client"
14
14
  s.description = s.summary
15
+ s.required_ruby_version = '>= 1.9.2'
15
16
 
16
17
  s.rubyforge_project = s.name
18
+ s.license = 'MIT'
17
19
 
18
- s.add_dependency "nori", "~> 2.1.0"
19
- s.add_dependency "httpi", "~> 2.0.2"
20
- s.add_dependency "wasabi", "~> 3.1.0"
21
- s.add_dependency "akami", "~> 1.2.0"
22
- s.add_dependency "gyoku", "~> 1.0.0"
23
-
20
+ s.add_dependency "nori", "~> 2.4"
21
+ s.add_dependency "httpi", "~> 2.3"
22
+ s.add_dependency "wasabi", "~> 3.4"
23
+ s.add_dependency "akami", "~> 1.2"
24
+ s.add_dependency "gyoku", "~> 1.2"
24
25
  s.add_dependency "builder", ">= 2.1.2"
25
- s.add_dependency "nokogiri", ">= 1.4.0"
26
+ s.add_dependency "nokogiri", ">= 1.8.1"
26
27
 
27
28
  s.add_development_dependency "rack"
28
- s.add_development_dependency "puma", "2.0.0.b4"
29
+ s.add_development_dependency "puma", "~> 3.0"
29
30
 
30
- s.add_development_dependency "rake", "~> 0.9"
31
- s.add_development_dependency "rspec", "~> 2.10"
32
- s.add_development_dependency "mocha", "~> 0.11"
31
+ s.add_development_dependency "rake", "~> 10.1"
32
+ s.add_development_dependency "rspec", "~> 2.14"
33
+ s.add_development_dependency "mocha", "~> 0.14"
33
34
  s.add_development_dependency "json", "~> 1.7"
34
35
 
35
36
  ignores = File.readlines(".gitignore").grep(/\S+/).map(&:chomp)
@@ -0,0 +1,13 @@
1
+ <SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
2
+ xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
3
+ xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
4
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema">
6
+ <SOAP-ENV:Body>
7
+ <SOAP-ENV:Fault>
8
+ <faultcode/>
9
+ <faultstring/>
10
+ <detail><soapVal><ERRNO xsi:type="xsd:string">80:1289245853:55</ERRNO></soapVal></detail>
11
+ </SOAP-ENV:Fault>
12
+ </SOAP-ENV:Body>
13
+ </SOAP-ENV:Envelope>
@@ -0,0 +1,39 @@
1
+ <E:Envelope
2
+ xmlns:E="http://schemas.xmlsoap.org/soap/envelope/"
3
+ xmlns:A="http://schemas.xmlsoap.org/soap/encoding/"
4
+ xmlns:s="http://www.w3.org/2001/XMLSchema-instance"
5
+ xmlns:y="http://www.w3.org/2001/XMLSchema"
6
+ xmlns:iControl="urn:iControl"
7
+ E:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
8
+ <E:Body>
9
+ <m:get_agent_listen_addressResponse
10
+ xmlns:m="urn:iControl:Management/SNMPConfiguration">
11
+ <return
12
+ s:type="A:Array"
13
+ A:arrayType="iControl:Management.SNMPConfiguration.AgentListenAddressPort[2]">
14
+ <item>
15
+ <transport
16
+ s:type="iControl:Management.SNMPConfiguration.TransportType">TRANSPORT_TCP6</transport>
17
+ <ipport
18
+ s:type="iControl:Common.IPPortDefinition">
19
+ <address
20
+ s:type="y:string"></address>
21
+ <port
22
+ s:type="y:long">161</port>
23
+ </ipport>
24
+ </item>
25
+ <item>
26
+ <transport
27
+ s:type="iControl:Management.SNMPConfiguration.TransportType">TRANSPORT_UDP6</transport>
28
+ <ipport
29
+ s:type="iControl:Common.IPPortDefinition">
30
+ <address
31
+ s:type="y:string"></address>
32
+ <port
33
+ s:type="y:long">161</port>
34
+ </ipport>
35
+ </item>
36
+ </return>
37
+ </m:get_agent_listen_addressResponse>
38
+ </E:Body>
39
+ </E:Envelope>