smacks-savon 0.0.2

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/README.rdoc ADDED
@@ -0,0 +1,11 @@
1
+ = Savon
2
+
3
+ Savon is a lightweight SOAP client.
4
+
5
+ == Install
6
+
7
+ $ sudo gem install smacks-savon --source http://gems.github.com
8
+
9
+ == Dependencies
10
+
11
+ Hpricot 0.6.164 (also available for JRuby)
data/lib/savon.rb ADDED
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ $:.unshift(File.join(File.dirname(__FILE__), 'savon'))
3
+ require 'response'
4
+ require 'wsdl'
5
+ require 'service'
@@ -0,0 +1,29 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'apricoteatsgorilla'
4
+
5
+ module Savon
6
+
7
+ # Savon::Response represents the SOAP response and offers different methods
8
+ # to handle the response.
9
+ class Response
10
+
11
+ # Initializer to set the SOAP response.
12
+ def initialize(response)
13
+ @response = response
14
+ end
15
+
16
+ # Returns the SOAP response message as a Hash. Call with XPath expession
17
+ # (Hpricot search) as parameter to define a custom root node. The root node
18
+ # itself will not be included in the Hash.
19
+ def to_hash(root_node = "//return")
20
+ ApricotEatsGorilla(@response.body, root_node)
21
+ end
22
+
23
+ # Returns the raw XML response.
24
+ def to_s
25
+ @response.body
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,109 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'apricoteatsgorilla'
6
+
7
+ module Savon
8
+
9
+ # Savon::Service is the actual SOAP client implementation to use.
10
+ #
11
+ # Instantiate Savon::Service and pass in the WSDL of the service you would
12
+ # like to work with. Then simply call the SOAP service method on your
13
+ # instance (which will be catched via method_missing) and pass in a Hash
14
+ # of options you would like to send.
15
+ #
16
+ # Example:
17
+ # proxy = Savon::Service.new "http://example.com/ExampleService?wsdl"
18
+ # response = proxy.findExampleById(:id => "123")
19
+ #
20
+ # Get the raw response XML:
21
+ # response.to_s
22
+ #
23
+ # Get it as a Hash (offers optional XPath expression to set a custom root node):
24
+ # response.to_hash
25
+ # response.to_hash("//return")
26
+ #
27
+ # Or as a Mash object (also offers specifying a custom root node):
28
+ # response.to_mash
29
+ # response.to_mash("//user/email")
30
+ class Service
31
+
32
+ # Sets the HTTP connection instance.
33
+ attr_writer :http
34
+
35
+ # Initializer sets the endpoint URI.
36
+ def initialize(endpoint)
37
+ @uri = URI(endpoint)
38
+ end
39
+
40
+ # Returns an Wsdl instance.
41
+ def wsdl
42
+ @wsdl = Savon::Wsdl.new(@uri, http) if @wsdl.nil?
43
+ @wsdl
44
+ end
45
+
46
+ private
47
+
48
+ # Sets up the request headers and body, makes the request and returns a
49
+ # Savon::Response object.
50
+ def call_service
51
+ headers = { 'Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => @action }
52
+ body = ApricotEatsGorilla.soap_envelope("wsdl" => wsdl.namespace_uri) do
53
+ ApricotEatsGorilla("wsdl:#{@action}" => namespaced_options)
54
+ end
55
+ response = @http.request_post(@uri.path, body, headers)
56
+ Savon::Response.new(response)
57
+ end
58
+
59
+ # Returns an HTTP connection instance.
60
+ def http
61
+ if @http.nil?
62
+ raise ArgumentError, "Invalid endpoint URI" unless @uri.scheme
63
+ @http = Net::HTTP.new(@uri.host, @uri.port)
64
+ #@http.set_debug_output(STDOUT)
65
+ #@http.read_timeout = 5
66
+ end
67
+ @http
68
+ end
69
+
70
+ # Checks if the requestion SOAP action is available.
71
+ # Raises an ArgumentError in case it isn't.
72
+ def validate_action
73
+ unless wsdl.service_methods.include? @action
74
+ raise ArgumentError, "Invalid service method '#{@action}'"
75
+ end
76
+ end
77
+
78
+ # Checks if there were any choice elements found in the wsdl and namespaces
79
+ # the corresponding keys from the passed in Hash of options.
80
+ def namespaced_options
81
+ return @options if wsdl.choice_elements.empty?
82
+
83
+ options = {}
84
+ @options.each do |key, value|
85
+ key = "wsdl:#{key}" if wsdl.choice_elements.include? key.to_s
86
+
87
+ current = options[key]
88
+ case current
89
+ when Array
90
+ options[key] << value
91
+ when nil
92
+ options[key] = value
93
+ else
94
+ options[key] = [current.dup, value]
95
+ end
96
+ end
97
+ options
98
+ end
99
+
100
+ # Catches calls to SOAP service methods.
101
+ def method_missing(method, options = {})
102
+ @action = method.to_s
103
+ @options = options
104
+ validate_action
105
+ call_service
106
+ end
107
+
108
+ end
109
+ end
data/lib/savon/wsdl.rb ADDED
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'rubygems'
3
+ require 'net/http'
4
+ require 'hpricot'
5
+
6
+ module Savon
7
+
8
+ # Savon::Wsdl gets, parses and represents the SOAP-WSDL.
9
+ class Wsdl
10
+
11
+ # The namespace URI.
12
+ attr_reader :namespace_uri
13
+
14
+ # Available service methods.
15
+ attr_reader :service_methods
16
+
17
+ # Choice elements.
18
+ attr_reader :choice_elements
19
+
20
+ # Initializer expects an endpoint URI and an HTTP connection instance.
21
+ # Gets and parses the WSDL at the given URI.
22
+ def initialize(uri, http)
23
+ @uri = uri
24
+ @http = http
25
+ get_wsdl
26
+ parse_wsdl
27
+ end
28
+
29
+ # Returns the response body from the WSDL request.
30
+ def to_s
31
+ @response.body
32
+ end
33
+
34
+ private
35
+
36
+ # Gets the WSDL at the given URI.
37
+ def get_wsdl
38
+ @response = @http.get("#{@uri.path}?#{@uri.query}")
39
+ @doc = Hpricot.XML(@response.body)
40
+ end
41
+
42
+ # Parses the WSDL for the namespace URI, available service methods
43
+ # and choice elements.
44
+ def parse_wsdl
45
+ @namespace_uri = @doc.at('//wsdl:definitions').get_attribute('targetNamespace')
46
+
47
+ @service_methods = []
48
+ @doc.search('//soap:operation').each do |operation|
49
+ service_methods << operation.parent.get_attribute('name')
50
+ end
51
+
52
+ @choice_elements = []
53
+ @doc.search('//xs:choice//xs:element').each do |choice|
54
+ name = choice.get_attribute('ref').sub(/(.+):/, '')
55
+ choice_elements << name unless @choice_elements.include? name
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,127 @@
1
+ class WsdlFactory
2
+ attr_accessor :namespace_uri, :service_methods, :choice_elements
3
+
4
+ def initialize(new_options = {})
5
+ options = {
6
+ :namespace_uri => "http://some.example.com",
7
+ :service_methods => {"findById" => ["id"]},
8
+ :choice_elements => {}
9
+ }.update(new_options)
10
+
11
+ @namespace_uri = options[:namespace_uri]
12
+ @service_methods = options[:service_methods]
13
+ @choice_elements = options[:choice_elements]
14
+ end
15
+
16
+ def build
17
+ wsdl = '<wsdl:definitions name="SomeService" targetNamespace="' << namespace_uri << '"
18
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://example.com"
19
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
20
+ <wsdl:types>
21
+ <xs:schema attributeFormDefault="unqualified" elementFormDefault="unqualified"
22
+ targetNamespace="http://example.com" xmlns:tns="http://example.com"
23
+ xmlns:xs="http://www.w3.org/2001/XMLSchema">'
24
+ wsdl << build_elements
25
+ wsdl << '<xs:element name="result" type="tns:result" />'
26
+ wsdl << build_complex_types
27
+ wsdl << '<xs:complexType name="result">
28
+ <xs:sequence><xs:element name="token" type="xs:token" /></xs:sequence>
29
+ </xs:complexType>
30
+ </xs:schema>
31
+ </wsdl:types>'
32
+ wsdl << build_messages
33
+ wsdl << '<wsdl:portType name="SomeWebService">'
34
+ wsdl << build_operation_input_output
35
+ wsdl << '</wsdl:portType>
36
+ <wsdl:binding name="SomeServiceSoapBinding" type="tns:SomeService">
37
+ <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />'
38
+ wsdl << build_operation_input_output_body
39
+ wsdl << '</wsdl:binding>
40
+ <wsdl:service name="SomeService">
41
+ <wsdl:port binding="tns:SomeServiceSoapBinding" name="SomeServicePort">
42
+ <soap:address location="http://example.com/SomeService" />
43
+ </wsdl:port>
44
+ </wsdl:service>
45
+ </wsdl:definitions>'
46
+ end
47
+
48
+ def build_elements
49
+ wsdl = service_methods.keys.map { |method|
50
+ '<xs:element name="' << method << '" type="tns:' << method << '" />
51
+ <xs:element name="' << method << 'Response" type="tns:' << method << 'Response" />'
52
+ }.to_s
53
+ wsdl << choice_elements.map { |c_method, c_elements|
54
+ c_elements.map { |c_element|
55
+ '<xs:element name="' << c_element << '" type="tns:' << c_element << 'Value" />'
56
+ }.to_s
57
+ }.to_s
58
+ wsdl
59
+ end
60
+
61
+ def build_complex_types
62
+ service_methods.map { |method, inputs|
63
+ wsdl = '<xs:complexType name="' << method << '"><xs:sequence>'
64
+ inputs.each do |input|
65
+ if choice_elements.keys.include? input
66
+ wsdl << '<xs:choice>'
67
+ wsdl << choice_elements[input].map { |element|
68
+ '<xs:element ref="tns:' << element << '"/>'
69
+ }.to_s
70
+ wsdl << '</xs:choice>'
71
+ else
72
+ wsdl << '<xs:element minOccurs="0" name="' << input << '" type="xs:string" />'
73
+ end
74
+ end
75
+ wsdl << '</xs:sequence></xs:complexType>'
76
+ wsdl << build_complex_types_choice_elements
77
+ wsdl << '<xs:complexType name="' << method << 'Response"><xs:sequence>
78
+ <xs:element minOccurs="0" name="return" type="tns:result" />
79
+ </xs:sequence></xs:complexType>'
80
+ }.to_s
81
+ end
82
+
83
+ def build_complex_types_choice_elements
84
+ choice_elements.map { |c_method, c_elements|
85
+ c_elements.map { |c_element|
86
+ '<xs:complexType name="' << c_element << 'Value"><xs:sequence>
87
+ <xs:element minOccurs="0" name="' << c_element << '" type="xs:string" />
88
+ </xs:sequence></xs:complexType>'
89
+ }.to_s
90
+ }.to_s
91
+ end
92
+
93
+ def build_messages
94
+ service_methods.keys.map { |method|
95
+ '<wsdl:message name="' << method << '">
96
+ <wsdl:part element="tns:' << method << '" name="parameters"> </wsdl:part>
97
+ </wsdl:message>
98
+ <wsdl:message name="' << method << 'Response">
99
+ <wsdl:part element="tns:' << method << 'Response" name="parameters"> </wsdl:part>
100
+ </wsdl:message>'
101
+ }.to_s
102
+ end
103
+
104
+ def build_operation_input_output
105
+ service_methods.keys.map { |method|
106
+ '<wsdl:operation name="' << method << '">
107
+ <wsdl:input message="tns:' << method << '" name="' << method << '"> </wsdl:input>
108
+ <wsdl:output message="tns:' << method << 'Response" name="' << method << 'Response"> </wsdl:output>
109
+ </wsdl:operation>'
110
+ }.to_s
111
+ end
112
+
113
+ def build_operation_input_output_body
114
+ service_methods.keys.map { |method|
115
+ '<wsdl:operation name="' << method << '">
116
+ <soap:operation soapAction="" style="document" />
117
+ <wsdl:input name="' << method << '">
118
+ <soap:body use="literal" />
119
+ </wsdl:input>
120
+ <wsdl:output name="' << method << 'Response">
121
+ <soap:body use="literal" />
122
+ </wsdl:output>
123
+ </wsdl:operation>'
124
+ }.to_s
125
+ end
126
+
127
+ end
@@ -0,0 +1,15 @@
1
+ module SoapResponseFixture
2
+
3
+ def some_soap_response
4
+ '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
5
+ <soap:Body>
6
+ <ns2:result xmlns:ns2="http://example.com/">
7
+ <return>
8
+ <token>secret</token>
9
+ </return>
10
+ </ns2:result>
11
+ </soap:Body>
12
+ </soap:Envelope>'
13
+ end
14
+
15
+ end
data/tests/helper.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'mocha'
4
+ require 'shoulda'
5
+ require "apricoteatsgorilla"
6
+
7
+ ["service", "wsdl", "response"].each do |file|
8
+ require File.join(File.dirname(__FILE__), "..", "lib", "savon", file)
9
+ end
10
+
11
+ require File.join(File.dirname(__FILE__), "factories", "wsdl")
12
+ require File.join(File.dirname(__FILE__), "fixtures", "soap_response")
13
+
14
+ module TestHelper
15
+
16
+ def some_url
17
+ "http://example.com"
18
+ end
19
+
20
+ def some_uri
21
+ URI(some_url)
22
+ end
23
+
24
+ def service_http_mock(response_body)
25
+ http_mock = mock()
26
+ http_mock.expects(:get).returns(response_mock(WsdlFactory.new.build))
27
+ http_mock.expects(:request_post).returns(response_mock(response_body))
28
+ http_mock
29
+ end
30
+
31
+ def http_mock(response_body)
32
+ http_mock = mock()
33
+ http_mock.expects(:get).returns(response_mock(response_body))
34
+ http_mock
35
+ end
36
+
37
+ def response_mock(response_body)
38
+ response_mock = mock('Net::HTTPResponse')
39
+ response_mock.stubs(
40
+ :code => '200', :message => "OK", :content_type => "text/html",
41
+ :body => response_body
42
+ )
43
+ response_mock
44
+ end
45
+
46
+ end
@@ -0,0 +1,28 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), "..", "helper")
3
+
4
+ class SavonResponseTest < Test::Unit::TestCase
5
+
6
+ include TestHelper
7
+ include SoapResponseFixture
8
+
9
+ context "Savon::Response with some SOAP response" do
10
+ setup do
11
+ ApricotEatsGorilla.sort_keys = true
12
+ @response = Savon::Response.new(response_mock(some_soap_response))
13
+ end
14
+
15
+ should "return a Hash on to_hash" do
16
+ assert_kind_of Hash, @response.to_hash
17
+ end
18
+
19
+ should "return a Hash equal to the response on to_hash" do
20
+ assert_equal ApricotEatsGorilla(some_soap_response, "//return"), @response.to_hash
21
+ end
22
+
23
+ should "return the raw XML response on to_s" do
24
+ assert_equal some_soap_response, @response.to_s
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,32 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), "..", "helper")
3
+
4
+ class SavonServiceTest < Test::Unit::TestCase
5
+
6
+ include TestHelper
7
+ include SoapResponseFixture
8
+
9
+ context "Savon::Service" do
10
+ setup do
11
+ @service = Savon::Service.new(some_url)
12
+ @service.http = service_http_mock(some_soap_response)
13
+ @result = @service.findById
14
+ end
15
+
16
+ should "return a Savon::Response object containing the given response" do
17
+ assert_kind_of Savon::Response, @result
18
+ assert_equal some_soap_response, @result.to_s
19
+ end
20
+
21
+ should "return an instance of Savon::Wsdl on wsdl" do
22
+ assert_kind_of Savon::Wsdl, @service.wsdl
23
+ end
24
+
25
+ should "raise an ArgumentError when called with an invalid action" do
26
+ assert_raise ArgumentError do
27
+ @service.somethingCrazy
28
+ end
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,45 @@
1
+ # -*- coding: utf-8 -*-
2
+ require File.join(File.dirname(__FILE__), "..", "helper")
3
+
4
+ class SavonWsdlTest < Test::Unit::TestCase
5
+
6
+ include TestHelper
7
+
8
+ context "Savon::Wsdl without choice elements" do
9
+ setup do
10
+ @some_factory = WsdlFactory.new
11
+ @some_wsdl = Savon::Wsdl.new(some_uri, http_mock(@some_factory.build))
12
+ end
13
+
14
+ should "return the namespace_uri" do
15
+ assert_equal @some_factory.namespace_uri, @some_wsdl.namespace_uri
16
+ end
17
+
18
+ should "return the available service_methods" do
19
+ assert_equal @some_factory.service_methods.keys, @some_wsdl.service_methods
20
+ end
21
+
22
+ should "not find any choice elements, so choice_elements returns []" do
23
+ assert_equal @some_factory.choice_elements.keys, @some_wsdl.choice_elements
24
+ end
25
+ end
26
+
27
+ context "Savon::Wsdl with choice elements" do
28
+ setup do
29
+ @choice_factory = WsdlFactory.new(
30
+ :service_methods => {"findUser" => ["credential"]},
31
+ :choice_elements => {"credential" => ["id", "email"]}
32
+ )
33
+ @choice_wsdl = Savon::Wsdl.new(some_uri, http_mock(@choice_factory.build))
34
+ end
35
+
36
+ should "return the available choice elements" do
37
+ assert_equal @choice_factory.choice_elements["credential"], @choice_wsdl.choice_elements
38
+ end
39
+
40
+ should "return the raw SOAP response on to_s" do
41
+ assert_equal @choice_factory.build, @choice_wsdl.to_s
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,5 @@
1
+ # -*- coding: utf-8 -*-
2
+ $:.unshift(File.join(File.dirname(__FILE__), 'savon'))
3
+ require "service_test"
4
+ require "wsdl_test"
5
+ require "response_test"
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smacks-savon
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Daniel Harrington
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-16 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: hpricot
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.6.164
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: smacks-apricoteatsgorilla
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.6
34
+ version:
35
+ description: Savon is a lightweight SOAP client.
36
+ email:
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.rdoc
43
+ files:
44
+ - README.rdoc
45
+ - lib/savon.rb
46
+ - lib/savon/service.rb
47
+ - lib/savon/wsdl.rb
48
+ - lib/savon/response.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/smacks/savon
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --inline-source
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements:
70
+ - mocha for testing
71
+ - shoulda for testing
72
+ - apricoteatsgorilla for testing
73
+ rubyforge_project:
74
+ rubygems_version: 1.2.0
75
+ signing_key:
76
+ specification_version: 2
77
+ summary: Savon is a lightweight SOAP client.
78
+ test_files:
79
+ - tests/savon_test.rb
80
+ - tests/helper.rb
81
+ - tests/factories/wsdl.rb
82
+ - tests/fixtures/soap_response.rb
83
+ - tests/savon/service_test.rb
84
+ - tests/savon/wsdl_test.rb
85
+ - tests/savon/response_test.rb