* collapse 'ws' back into protocols, it just added complexity and indirection, and was hard to extend.

* extract casting into seperate support file
* ensure casting always does the right thing for return values, should fix interoperability issues with Ecto and possibly other XML-RPC clients
* add functional unit tests for scaffolding
* represent signature items with classes instead of symbols/Class objects, much more flexible
* tweak logging to always show casted versions of parameters and return values, if possible.


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1072 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Leon Breedt 2005-04-02 21:03:36 +00:00
parent aa09c770e9
commit aaea48fe98
50 changed files with 1091 additions and 1512 deletions

View File

@ -1,11 +1,13 @@
*0.7.0* (Unreleased)
* Remove ActiveRecordSoapMarshallable workaround, see #912 for details
* Add scaffolding via ActionController::Base.web_service_scaffold for quick testing using a web browser
* Remove ActiveRecordSoapMarshallable workaround, see #912 for details
* Generalize casting code to be used by both SOAP and XML-RPC (previously, it was only XML-RPC)
* Ensure return value is properly cast as well, fixes XML-RPC interoperability with Ecto and possibly other clients
* Include backtraces in 500 error responses for failed request parsing, and remove "rescue nil" statements obscuring real errors for XML-RPC

View File

@ -264,4 +264,4 @@ task :release => [:package] do
first_file = false
end
end
end
end

View File

@ -2,14 +2,9 @@
- WS Dynamic Scaffolding
* add protocol selection ability
* test with XML-RPC (namespaced method name support)
* support structured types as input parameters with the input field helper
- update manual for scaffolding and functional testing
= 0.8.0
- Consumption of WSDL services
= Refactoring
- Port dispatcher tests to use test_invoke
- Don't have clean way to go from SOAP Class object to the xsd:NAME type
string -- NaHi possibly looking at remedying this situation

View File

@ -35,12 +35,12 @@ end
$:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/")
require 'action_web_service/support/class_inheritable_options'
require 'action_web_service/vendor/ws'
require 'action_web_service/support/signature_types'
require 'action_web_service/base'
require 'action_web_service/client'
require 'action_web_service/invocation'
require 'action_web_service/api'
require 'action_web_service/casting'
require 'action_web_service/struct'
require 'action_web_service/container'
require 'action_web_service/protocol'

View File

@ -1,8 +1,5 @@
module ActionWebService # :nodoc:
module API # :nodoc:
class CastingError < ActionWebService::ActionWebServiceError
end
# A web service API class specifies the methods that will be available for
# invocation for an API. It also contains metadata such as the method type
# signature hints.
@ -31,6 +28,8 @@ module ActionWebService # :nodoc:
private_class_method :new, :allocate
class << self
include ActionWebService::SignatureTypes
# API methods have a +name+, which must be the Ruby method name to use when
# performing the invocation on the web service object.
#
@ -70,10 +69,9 @@ module ActionWebService # :nodoc:
expects = canonical_signature(expects)
returns = canonical_signature(returns)
if expects
expects.each do |param|
klass = WS::BaseTypes.canonical_param_type_class(param)
klass = klass[0] if klass.is_a?(Array)
if klass.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
expects.each do |type|
type = type.element_type if type.is_a?(ArrayType)
if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
end
end
@ -138,16 +136,6 @@ module ActionWebService # :nodoc:
instance
end
# Creates a dummy API Method instance for the given public method name
def dummy_public_api_method_instance(public_method_name)
Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil)
end
# Creates a dummy API Method instance for the given method name
def dummy_api_method_instance(method_name)
Method.new(method_name, public_api_method_name(method_name), nil, nil)
end
private
def api_public_method_names
read_inheritable_attribute("api_public_method_names") || {}
@ -159,11 +147,6 @@ module ActionWebService # :nodoc:
raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
end
end
def canonical_signature(signature)
return nil if signature.nil?
signature.map{|spec| WS::BaseTypes.canonical_param_type_spec(spec)}
end
end
end
@ -180,134 +163,41 @@ module ActionWebService # :nodoc:
@public_name = public_name
@expects = expects
@returns = returns
@caster = ActionWebService::Casting::BaseCaster.new(self)
end
# The list of parameter names for this method
def param_names
return [] unless @expects
i = 0
@expects.map{ |spec| param_name(spec, i += 1) }
@expects.map{ |type| type.name }
end
# The name for the given parameter
def param_name(spec, i=1)
spec.is_a?(Hash) ? spec.keys.first.to_s : "p#{i}"
end
# The type of the parameter declared in +spec+. Is either
# the Class of the parameter, or its canonical name (if its a
# base type). Typed array specifications will return the type of
# their elements.
def param_type(spec)
spec = spec.values.first if spec.is_a?(Hash)
param_type = spec.is_a?(Array) ? spec[0] : spec
WS::BaseTypes::class_to_type_name(param_type) rescue param_type
end
# The Class of the parameter declared in +spec+.
def param_class(spec)
type = param_type(spec)
type.is_a?(Symbol) ? WS::BaseTypes.type_name_to_class(type) : type
end
# Registers all types known to this method with the given marshaler
def register_types(marshaler)
@expects.each{ |x| marshaler.register_type(x) } if @expects
@returns.each{ |x| marshaler.register_type(x) } if @returns
end
# Encodes an RPC call for this method. Casting is performed if
# the <tt>:strict</tt> option is given.
def encode_rpc_call(marshaler, encoder, params, options={})
name = options[:method_name] || @public_name
expects = @expects || []
returns = @returns || []
(expects + returns).each { |spec| marshaler.register_type spec }
(0..(params.length-1)).each do |i|
spec = expects[i] || params[i].class
type_binding = marshaler.lookup_type(spec)
param_info = WS::ParamInfo.create(spec, type_binding, i)
if options[:strict]
value = marshaler.cast_outbound_recursive(params[i], spec)
else
value = params[i]
end
param = WS::Param.new(value, param_info)
params[i] = marshaler.marshal(param)
end
encoder.encode_rpc_call(name, params)
end
# Encodes an RPC response for this method. Casting is performed if
# the <tt>:strict</tt> option is given.
def encode_rpc_response(marshaler, encoder, return_value, options={})
if !return_value.nil? && @returns
return_type = @returns[0]
type_binding = marshaler.register_type(return_type)
param_info = WS::ParamInfo.create(return_type, type_binding, 0)
if options[:strict]
return_value = marshaler.cast_inbound_recursive(return_value, return_type)
end
return_value = marshaler.marshal(WS::Param.new(return_value, param_info))
else
return_value = nil
end
encoder.encode_rpc_response(response_name(encoder), return_value)
end
# Casts a set of WS::Param values into the appropriate
# Ruby values
def cast_expects_ws2ruby(marshaler, params)
return [] if @expects.nil?
i = 0
@expects.map do |spec|
value = marshaler.cast_inbound_recursive(params[i].value, spec)
i += 1
value
end
end
# Casts a set of Ruby values into the expected Ruby values
def cast_expects(marshaler, params)
return [] if @expects.nil?
i = 0
@expects.map do |spec|
value = marshaler.cast_outbound_recursive(params[i], spec)
i += 1
value
end
def cast_expects(params)
@caster.cast_expects(params)
end
# Cast a Ruby return value into the expected Ruby value
def cast_returns(marshaler, return_value)
return nil if @returns.nil?
marshaler.cast_inbound_recursive(return_value, @returns[0])
def cast_returns(return_value)
@caster.cast_returns(return_value)
end
# String representation of this method
def to_s
fqn = ""
fqn << (@returns ? (friendly_param(@returns[0], nil) + " ") : "void ")
fqn << (@returns ? (friendly_param(@returns[0], false) + " ") : "void ")
fqn << "#{@public_name}("
if @expects
i = 0
fqn << @expects.map{ |p| friendly_param(p, i+= 1) }.join(", ")
end
fqn << @expects.map{ |p| friendly_param(p) }.join(", ") if @expects
fqn << ")"
fqn
end
private
def response_name(encoder)
encoder.is_a?(WS::Encoding::SoapRpcEncoding) ? (@public_name + "Response") : @public_name
end
def friendly_param(spec, i)
name = param_name(spec, i)
type = param_type(spec)
spec = spec.values.first if spec.is_a?(Hash)
type = spec.is_a?(Array) ? (type.to_s + "[]") : type.to_s
i ? (type + " " + name) : type
def friendly_param(type, show_name=true)
name = type.name.to_s
type_type = type.type.to_s
str = type.array?? (type_type + '[]') : type_type
show_name ? (str + " " + name) : str
end
end
end

View File

@ -0,0 +1,105 @@
require 'time'
require 'date'
require 'generator'
module ActionWebService # :nodoc:
module Casting # :nodoc:
class CastingError < ActionWebServiceError # :nodoc:
end
# Performs casting of arbitrary values into the correct types for the signature
class BaseCaster
def initialize(api_method)
@api_method = api_method
end
# Coerces the parameters in +params+ (an Enumerable) into the types
# this method expects
def cast_expects(params)
self.class.cast_expects(@api_method, params)
end
# Coerces the given +return_value+ into the the type returned by this
# method
def cast_returns(return_value)
self.class.cast_returns(@api_method, return_value)
end
class << self
include ActionWebService::SignatureTypes
def cast_expects(api_method, params) # :nodoc:
return [] if api_method.expects.nil?
SyncEnumerator.new(params, api_method.expects).map{ |r| cast(r[0], r[1]) }
end
def cast_returns(api_method, return_value) # :nodoc:
return nil if api_method.returns.nil?
cast(return_value, api_method.returns[0])
end
def cast(value, signature_type) # :nodoc:
return value if signature_type.nil? # signature.length != params.length
unless signature_type.array?
return value if canonical_type(value.class) == signature_type.type
end
if signature_type.array?
unless value.respond_to?(:entries) && !value.is_a?(String)
raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}"
end
value.entries.map do |entry|
cast(entry, signature_type.element_type)
end
elsif signature_type.structured?
cast_to_structured_type(value, signature_type)
elsif !signature_type.custom?
cast_base_type(value, signature_type)
end
end
def cast_base_type(value, signature_type) # :nodoc:
case signature_type.type
when :int
Integer(value)
when :string
value.to_s
when :bool
return false if value.nil?
return value if value == true || value == false
case value.to_s.downcase
when '1', 'true', 'y', 'yes'
true
when '0', 'false', 'n', 'no'
false
else
raise CastingError, "Don't know how to cast #{value.class} into Boolean"
end
when :float
Float(value)
when :time
Time.parse(value.to_s)
when :date
Date.parse(value.to_s)
when :datetime
DateTime.parse(value.to_s)
end
end
def cast_to_structured_type(value, signature_type) # :nodoc:
obj = signature_type.type_class.new
if value.respond_to?(:each_pair)
klass = signature_type.type_class
value.each_pair do |name, val|
type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil
val = cast(val, type) if type
obj.send("#{name}=", val)
end
else
raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
end
obj
end
end
end
end
end

View File

@ -46,8 +46,7 @@ module ActionWebService # :nodoc:
@type_namespace = options[:type_namespace] || 'urn:ActionWebService'
@method_namespace = options[:method_namespace] || 'urn:ActionWebService'
@driver_options = options[:driver_options] || {}
@marshaler = WS::Marshaling::SoapMarshaler.new @type_namespace
@encoder = WS::Encoding::SoapRpcEncoding.new @method_namespace
@protocol = ActionWebService::Protocol::Soap::SoapProtocol.new
@soap_action_base = options[:soap_action_base]
@soap_action_base ||= URI.parse(endpoint_uri).path
@driver = create_soap_rpc_driver(api, endpoint_uri)
@ -59,9 +58,9 @@ module ActionWebService # :nodoc:
protected
def perform_invocation(method_name, args)
method = @api.api_methods[method_name.to_sym]
args = method.cast_expects(@marshaler, args)
args = method.cast_expects(args.dup) rescue args
return_value = @driver.send(method_name, *args)
method.cast_returns(@marshaler, return_value)
method.cast_returns(return_value.dup) rescue return_value
end
def soap_action(method_name)
@ -70,9 +69,9 @@ module ActionWebService # :nodoc:
private
def create_soap_rpc_driver(api, endpoint_uri)
api.api_methods.each{ |name, method| method.register_types(@marshaler) }
@protocol.register_api(api)
driver = SoapDriver.new(endpoint_uri, nil)
driver.mapping_registry = @marshaler.registry
driver.mapping_registry = @protocol.marshaler.registry
api.api_methods.each do |name, method|
qname = XSD::QName.new(@method_namespace, method.public_name)
action = soap_action(method.public_name)
@ -81,15 +80,14 @@ module ActionWebService # :nodoc:
param_def = []
i = 0
if expects
expects.each do |spec|
param_name = method.param_name(spec, i)
type_binding = @marshaler.lookup_type(spec)
param_def << ['in', param_name, type_binding.mapping]
expects.each do |type|
type_binding = @protocol.marshaler.lookup_type(type)
param_def << ['in', type.name, type_binding.mapping]
i += 1
end
end
if returns
type_binding = @marshaler.lookup_type(returns[0])
type_binding = @protocol.marshaler.lookup_type(returns[0])
param_def << ['retval', 'return', type_binding.mapping]
end
driver.add_method(qname, action, method.name.to_s, param_def)

View File

@ -30,20 +30,22 @@ module ActionWebService # :nodoc:
def initialize(api, endpoint_uri, options={})
@api = api
@handler_name = options[:handler_name]
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
@client = XMLRPC::Client.new2(endpoint_uri, options[:proxy], options[:timeout])
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
end
protected
def perform_invocation(method_name, args)
method = @api.api_methods[method_name.to_sym]
method.register_types(@marshaler)
if method.expects && method.expects.length != args.length
raise(ArgumentError, "#{method.public_name}: wrong number of arguments (#{args.length} for #{method.expects.length})")
end
args = method.cast_expects(@marshaler, args)
args = method.cast_expects(args.dup) rescue args
if method.expects
method.expects.each_with_index{ |type, i| args[i] = @protocol.value_to_xmlrpc_wire_format(args[i], type) }
end
ok, return_value = @client.call2(public_name(method_name), *args)
return method.cast_returns(@marshaler, return_value) if ok
return (method.cast_returns(return_value.dup) rescue return_value) if ok
raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
end

View File

@ -12,14 +12,6 @@ module ActionWebService # :nodoc:
base.send(:include, ActionWebService::Dispatcher::InstanceMethods)
end
def self.layered_service_name(public_method_name) # :nodoc:
if public_method_name =~ /^([^\.]+)\.(.*)$/
$1
else
nil
end
end
module InstanceMethods # :nodoc:
private
def invoke_web_service_request(protocol_request)
@ -40,13 +32,7 @@ module ActionWebService # :nodoc:
else
return_value = self.__send__(invocation.api_method.name)
end
if invocation.api.has_api_method?(invocation.api_method.name)
api_method = invocation.api_method
else
api_method = invocation.api_method.dup
api_method.instance_eval{ @returns = [ return_value.class ] }
end
invocation.protocol.marshal_response(api_method, return_value)
web_service_create_response(invocation.protocol, invocation.api, invocation.api_method, return_value)
end
def web_service_delegated_invoke(invocation)
@ -57,7 +43,7 @@ module ActionWebService # :nodoc:
if cancellation_reason
raise(DispatcherError, "request canceled: #{cancellation_reason}")
end
invocation.protocol.marshal_response(invocation.api_method, return_value)
web_service_create_response(invocation.protocol, invocation.api, invocation.api_method, return_value)
end
def web_service_invocation(request)
@ -79,6 +65,7 @@ module ActionWebService # :nodoc:
invocation.service = web_service_object(invocation.service_name)
invocation.api = invocation.service.class.web_service_api
end
invocation.protocol.register_api(invocation.api)
request.api = invocation.api
if invocation.api.has_public_api_method?(public_method_name)
invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
@ -89,15 +76,20 @@ module ActionWebService # :nodoc:
invocation.api_method = invocation.api.default_api_method_instance
end
end
if invocation.service.nil?
raise(DispatcherError, "no service available for service name #{invocation.service_name}")
end
unless invocation.service.respond_to?(invocation.api_method.name)
raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api} (#{invocation.api_method.name})")
end
request.api_method = invocation.api_method
begin
invocation.method_ordered_params = invocation.api_method.cast_expects_ws2ruby(request.protocol.marshaler, request.method_params)
invocation.method_ordered_params = invocation.api_method.cast_expects(request.method_params.dup)
rescue
invocation.method_ordered_params = request.method_params.map{ |x| x.value }
logger.warn "Casting of method parameters failed" unless logger.nil?
invocation.method_ordered_params = request.method_params
end
request.method_params = invocation.method_ordered_params
invocation.method_named_params = {}
invocation.api_method.param_names.inject(0) do |m, n|
invocation.method_named_params[n] = invocation.method_ordered_params[m]
@ -106,6 +98,16 @@ module ActionWebService # :nodoc:
invocation
end
def web_service_create_response(protocol, api, api_method, return_value)
if api.has_api_method?(api_method.name)
return_type = api_method.returns ? api_method.returns[0] : nil
return_value = api_method.cast_returns(return_value)
else
return_type = ActionWebService::SignatureTypes.canonical_signature_entry(return_value.class, 0)
end
protocol.encode_response(api_method.public_name + 'Response', return_value, return_type)
end
class Invocation # :nodoc:
attr_accessor :protocol
attr_accessor :service_name

View File

@ -45,7 +45,6 @@ module ActionWebService # :nodoc:
exception = e
end
if request
log_request(request, @request.raw_post)
response = nil
exception = nil
bm = Benchmark.measure do
@ -55,6 +54,7 @@ module ActionWebService # :nodoc:
exception = e
end
end
log_request(request, @request.raw_post)
if exception
log_error(exception) unless logger.nil?
send_web_service_error_response(request, exception)
@ -82,10 +82,10 @@ module ActionWebService # :nodoc:
unless self.class.web_service_exception_reporting
exception = DispatcherError.new("Internal server error (exception raised)")
end
api_method = request.api_method ? request.api_method.dup : nil
api_method ||= request.api.dummy_api_method_instance(request.method_name)
api_method.instance_eval{ @returns = [ exception.class ] }
response = request.protocol.marshal_response(api_method, exception)
api_method = request.api_method
public_method_name = api_method ? api_method.public_name : request.method_name
return_type = ActionWebService::SignatureTypes.canonical_signature_entry(Exception, 0)
response = request.protocol.encode_response(public_method_name + 'Response', exception, return_type)
send_web_service_response(response)
else
if self.class.web_service_exception_reporting
@ -118,7 +118,14 @@ module ActionWebService # :nodoc:
def log_request(request, body)
unless logger.nil?
name = request.method_name
params = request.method_params.map{|x| "#{x.info.name}=>#{x.value.inspect}"}
api_method = request.api_method
params = request.method_params
if api_method && api_method.expects
i = 0
params = api_method.expects.map{ |type| param = "#{type.name}=>#{params[i].inspect}"; i+= 1; param }
else
params = params.map{ |param| param.inspect }
end
service = request.service_name
logger.debug("\nWeb Service Request: #{name}(#{params.join(", ")}) Entrypoint: #{service}")
logger.debug(indent(body))
@ -127,7 +134,8 @@ module ActionWebService # :nodoc:
def log_response(response, elapsed=nil)
unless logger.nil?
logger.debug("\nWeb Service Response" + (elapsed ? " (%f):" % elapsed : ":"))
elapsed = (elapsed ? " (%f):" % elapsed : ":")
logger.debug("\nWeb Service Response" + elapsed + " => #{response.return_value.inspect}")
logger.debug(indent(response.body))
end
end
@ -171,7 +179,7 @@ module ActionWebService # :nodoc:
namespace = 'urn:ActionWebService'
soap_action_base = "/#{controller_name}"
marshaler = WS::Marshaling::SoapMarshaler.new(namespace)
marshaler = ActionWebService::Protocol::Soap::SoapMarshaler.new(namespace)
apis = {}
case dispatching_mode
when :direct
@ -208,7 +216,7 @@ module ActionWebService # :nodoc:
xm.xsd(:schema, 'xmlns' => XsdNs, 'targetNamespace' => namespace) do
custom_types.each do |binding|
case
when binding.is_typed_array?
when binding.type.array?
xm.xsd(:complexType, 'name' => binding.type_name) do
xm.xsd(:complexContent) do
xm.xsd(:restriction, 'base' => 'soapenc:Array') do
@ -217,11 +225,11 @@ module ActionWebService # :nodoc:
end
end
end
when binding.is_typed_struct?
when binding.type.structured?
xm.xsd(:complexType, 'name' => binding.type_name) do
xm.xsd(:all) do
binding.each_member do |name, spec|
b = marshaler.register_type(spec)
binding.type.each_member do |name, type|
b = marshaler.register_type(type)
xm.xsd(:element, 'name' => name, 'type' => b.qualified_type_name('typens'))
end
end
@ -249,14 +257,8 @@ module ActionWebService # :nodoc:
expects = method.expects
i = 1
expects.each do |type|
if type.is_a?(Hash)
param_name = type.keys.shift
type = type.values.shift
else
param_name = "param#{i}"
end
binding = marshaler.register_type(type)
xm.part('name' => param_name, 'type' => binding.qualified_type_name('typens'))
xm.part('name' => type.name, 'type' => binding.qualified_type_name('typens'))
i += 1
end if expects
end
@ -340,7 +342,9 @@ module ActionWebService # :nodoc:
def register_api(api, marshaler)
bindings = {}
traverse_custom_types(api, marshaler) do |binding|
bindings[binding] = nil unless bindings.has_key?(binding.type_class)
bindings[binding] = nil unless bindings.has_key?(binding)
element_binding = binding.element_binding
bindings[binding.element_binding] = nil if element_binding && !bindings.has_key?(element_binding)
end
bindings.keys
end
@ -348,21 +352,18 @@ module ActionWebService # :nodoc:
def traverse_custom_types(api, marshaler, &block)
api.api_methods.each do |name, method|
expects, returns = method.expects, method.returns
expects.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if expects
returns.each{|x| traverse_custom_type_spec(marshaler, x, &block)} if returns
expects.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if expects
returns.each{ |type| traverse_type(marshaler, type, &block) if type.custom? } if returns
end
end
def traverse_custom_type_spec(marshaler, spec, &block)
binding = marshaler.register_type(spec)
if binding.is_typed_struct?
binding.each_member do |name, member_spec|
traverse_custom_type_spec(marshaler, member_spec, &block)
end
elsif binding.is_typed_array?
traverse_custom_type_spec(marshaler, binding.element_binding.type_class, &block)
def traverse_type(marshaler, type, &block)
yield marshaler.register_type(type)
if type.array?
yield marshaler.register_type(type.element_type)
type = type.element_type
end
yield binding
type.each_member{ |name, type| traverse_type(marshaler, type, &block) } if type.structured?
end
end
end

View File

@ -3,22 +3,11 @@ module ActionWebService # :nodoc:
class ProtocolError < ActionWebServiceError # :nodoc:
end
class AbstractProtocol
attr :marshaler
attr :encoder
def unmarshal_request(ap_request)
class AbstractProtocol # :nodoc:
def decode_action_pack_request(action_pack_request)
end
def marshal_response(method, return_value)
body = method.encode_rpc_response(marshaler, encoder, return_value)
Response.new(body, 'text/xml')
end
def protocol_client(api, protocol_name, endpoint_uri, options)
end
def create_action_pack_request(service_name, public_method_name, raw_body, options={})
def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
klass = options[:request_class] || SimpleActionPackRequest
request = klass.new
request.request_parameters['action'] = service_name.to_s
@ -27,9 +16,57 @@ module ActionWebService # :nodoc:
request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
request
end
def decode_request(raw_request, service_name)
end
def encode_request(method_name, params, param_types)
end
def decode_response(raw_response)
end
def encode_response(method_name, return_value, return_type)
end
def protocol_client(api, protocol_name, endpoint_uri, options)
end
def register_api(api)
end
end
class SimpleActionPackRequest < ActionController::AbstractRequest
class Request # :nodoc:
attr :protocol
attr :method_name
attr_accessor :method_params
attr :service_name
attr_accessor :api
attr_accessor :api_method
def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil)
@protocol = protocol
@method_name = method_name
@method_params = method_params
@service_name = service_name
@api = api
@api_method = api_method
end
end
class Response # :nodoc:
attr :body
attr :content_type
attr :return_value
def initialize(body, content_type, return_value)
@body = body
@content_type = content_type
@return_value = return_value
end
end
class SimpleActionPackRequest < ActionController::AbstractRequest # :nodoc:
def initialize
@env = {}
@qparams = {}
@ -66,33 +103,5 @@ module ActionWebService # :nodoc:
@session = {}
end
end
class Request # :nodoc:
attr :protocol
attr :method_name
attr :method_params
attr :service_name
attr_accessor :api
attr_accessor :api_method
def initialize(protocol, method_name, method_params, service_name, api=nil, api_method=nil)
@protocol = protocol
@method_name = method_name
@method_params = method_params
@service_name = service_name
@api = api
@api_method = api_method
end
end
class Response # :nodoc:
attr :body
attr :content_type
def initialize(body, content_type)
@body = body
@content_type = content_type
end
end
end
end

View File

@ -14,10 +14,10 @@ module ActionWebService # :nodoc:
module InstanceMethods # :nodoc:
private
def discover_web_service_request(ap_request)
def discover_web_service_request(action_pack_request)
(self.class.read_inheritable_attribute("web_service_protocols") || []).each do |protocol|
protocol = protocol.new
request = protocol.unmarshal_request(ap_request)
request = protocol.decode_action_pack_request(action_pack_request)
return request unless request.nil?
end
nil

View File

@ -1,3 +1,5 @@
require 'action_web_service/protocol/soap_protocol/marshaler'
module ActionWebService # :nodoc:
module Protocol # :nodoc:
module Soap # :nodoc:
@ -7,28 +9,105 @@ module ActionWebService # :nodoc:
end
class SoapProtocol < AbstractProtocol # :nodoc:
def initialize
@encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
@marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
def marshaler
@marshaler ||= SoapMarshaler.new
end
def unmarshal_request(ap_request)
return nil unless has_valid_soap_action?(ap_request)
method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
params = params.map{|x| @marshaler.unmarshal(x)}
service_name = ap_request.parameters['action']
def decode_action_pack_request(action_pack_request)
return nil unless has_valid_soap_action?(action_pack_request)
service_name = action_pack_request.parameters['action']
decode_request(action_pack_request.raw_post, service_name)
end
def encode_action_pack_request(service_name, public_method_name, raw_body, options={})
request = super
request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
request
end
def decode_request(raw_request, service_name)
envelope = SOAP::Processor.unmarshal(raw_request)
unless envelope
raise ProtocolError, "Failed to parse SOAP request message"
end
request = envelope.body.request
method_name = request.elename.name
params = request.collect{ |k, v| marshaler.soap_to_ruby(request[k]) }
Request.new(self, method_name, params, service_name)
end
def encode_request(method_name, params, param_types)
param_types.each{ |type| marshaler.register_type(type) } if param_types
qname = XSD::QName.new(marshaler.type_namespace, method_name)
param_def = []
i = 0
if param_types
params = params.map do |param|
param_type = param_types[i]
param_def << ['in', param_type.name, marshaler.lookup_type(param_type).mapping]
i += 1
[param_type.name, marshaler.ruby_to_soap(param)]
end
else
params = []
end
request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
request.set_param(params)
envelope = create_soap_envelope(request)
SOAP::Processor.marshal(envelope)
end
def decode_response(raw_response)
envelope = SOAP::Processor.unmarshal(raw_response)
unless envelope
raise ProtocolError, "Failed to parse SOAP request message"
end
method_name = envelope.body.request.elename.name
return_value = envelope.body.response
return_value = marshaler.soap_to_ruby(return_value) unless return_value.nil?
[method_name, return_value]
end
def encode_response(method_name, return_value, return_type)
if return_type
return_binding = marshaler.register_type(return_type)
marshaler.annotate_arrays(return_binding, return_value)
end
qname = XSD::QName.new(marshaler.type_namespace, method_name)
if return_value.nil?
response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
else
if return_value.is_a?(Exception)
detail = SOAP::Mapping::SOAPException.new(return_value)
response = SOAP::SOAPFault.new(
SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']),
SOAP::SOAPString.new(return_value.to_s),
SOAP::SOAPString.new(self.class.name),
marshaler.ruby_to_soap(detail))
else
if return_type
param_def = [['retval', 'return', marshaler.lookup_type(return_type).mapping]]
response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
response.retval = marshaler.ruby_to_soap(return_value)
else
response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
end
end
end
envelope = create_soap_envelope(response)
Response.new(SOAP::Processor.marshal(envelope), 'text/xml', return_value)
end
def protocol_client(api, protocol_name, endpoint_uri, options={})
return nil unless protocol_name == :soap
ActionWebService::Client::Soap.new(api, endpoint_uri, options)
end
def create_action_pack_request(service_name, public_method_name, raw_body, options={})
request = super
request.env['HTTP_SOAPACTION'] = '/soap/%s/%s' % [service_name, public_method_name]
request
def register_api(api)
api.api_methods.each do |name, method|
method.expects.each{ |type| marshaler.register_type(type) } if method.expects
method.returns.each{ |type| marshaler.register_type(type) } if method.returns
end
end
private
@ -43,7 +122,13 @@ module ActionWebService # :nodoc:
return nil if soap_action.empty?
soap_action
end
end
def create_soap_envelope(body)
header = SOAP::SOAPHeader.new
body = SOAP::SOAPBody.new(body)
SOAP::SOAPEnvelope.new(header, body)
end
end
end
end
end

View File

@ -0,0 +1,197 @@
require 'soap/mapping'
module ActionWebService
module Protocol
module Soap
class SoapMarshaler
attr :type_namespace
attr :registry
def initialize(type_namespace=nil)
@type_namespace = type_namespace || 'urn:ActionWebService'
@registry = SOAP::Mapping::Registry.new
@type2binding = {}
end
def soap_to_ruby(obj)
SOAP::Mapping.soap2obj(obj, @registry)
end
def ruby_to_soap(obj)
SOAP::Mapping.obj2soap(obj, @registry)
end
def register_type(type)
return @type2binding[type] if @type2binding.has_key?(type)
type_class = type.array?? type.element_type.type_class : type.type_class
type_type = type.array?? type.element_type : type
type_binding = nil
if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil)
qname = mapping[2] ? mapping[2][:type] : nil
qname ||= soap_base_type_name(mapping[0])
type_binding = SoapBinding.new(self, qname, type_type, mapping)
else
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
@registry.add(type_class,
SOAP::SOAPStruct,
typed_struct_factory(type_class),
{ :type => qname })
mapping = @registry.find_mapped_soap_class(type_class)
type_binding = SoapBinding.new(self, qname, type_type, mapping)
end
array_binding = nil
if type.array?
array_mapping = @registry.find_mapped_soap_class(Array) rescue nil
if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil?
@registry.set(Array,
SOAP::SOAPArray,
SoapTypedArrayFactory.new)
array_mapping = @registry.find_mapped_soap_class(Array)
end
qname = XSD::QName.new(@type_namespace, soap_type_name(type.element_type.type_class.name) + 'Array')
array_binding = SoapBinding.new(self, qname, type, array_mapping, type_binding)
end
@type2binding[type] = array_binding ? array_binding : type_binding
@type2binding[type]
end
alias :lookup_type :register_type
def annotate_arrays(binding, value)
if binding.type.array?
mark_typed_array(value, binding.element_binding.qname)
if binding.element_binding.type.custom?
value.each do |element|
annotate_arrays(binding.element_binding, element)
end
end
elsif binding.type.structured?
binding.type.each_member do |name, type|
member_binding = register_type(type)
member_value = value.respond_to?('[]') ? value[name] : value.send(name)
annotate_arrays(member_binding, member_value) if type.custom?
end
end
end
private
def typed_struct_factory(type_class)
if Object.const_defined?('ActiveRecord')
if type_class.ancestors.include?(ActiveRecord::Base)
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
type_class.instance_variable_set('@qname', qname)
return SoapActiveRecordStructFactory.new
end
end
SOAP::Mapping::Registry::TypedStructFactory
end
def mark_typed_array(array, qname)
(class << array; self; end).class_eval do
define_method(:arytype) do
qname
end
end
end
def soap_base_type_name(type)
xsd_type = type.ancestors.find{ |c| c.const_defined? 'Type' }
xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
end
def soap_type_name(type_name)
type_name.gsub(/::/, '..')
end
end
class SoapBinding
attr :qname
attr :type
attr :mapping
attr :element_binding
def initialize(marshaler, qname, type, mapping, element_binding=nil)
@marshaler = marshaler
@qname = qname
@type = type
@mapping = mapping
@element_binding = element_binding
end
def type_name
@type.custom? ? @qname.name : nil
end
def qualified_type_name(ns=nil)
if @type.custom?
"#{ns ? ns : @qname.namespace}:#{@qname.name}"
else
ns = XSD::NS.new
ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
return ns.name(XSD::AnyTypeName) unless xsd_klass
ns.name(xsd_klass.const_get('Type'))
end
end
def eql?(other)
@qname == other.qname
end
alias :== :eql?
def hash
@qname.hash
end
end
class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
def obj2soap(soap_class, obj, info, map)
unless obj.is_a?(ActiveRecord::Base)
return nil
end
soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
obj.class.columns.each do |column|
key = column.name.to_s
value = obj.send(key)
soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
end
soap_obj
end
def soap2obj(obj_class, node, info, map)
unless node.type == obj_class.instance_variable_get('@qname')
return false
end
obj = obj_class.new
node.each do |key, value|
obj[key] = value.data
end
obj.instance_variable_set('@new_record', false)
return true, obj
end
end
class SoapTypedArrayFactory < SOAP::Mapping::Factory
def obj2soap(soap_class, obj, info, map)
unless obj.respond_to?(:arytype)
return nil
end
soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
mark_marshalled_obj(obj, soap_obj)
obj.each do |item|
child = SOAP::Mapping._obj2soap(item, map)
soap_obj.add(child)
end
soap_obj
end
def soap2obj(obj_class, node, info, map)
return false
end
end
end
end
end

View File

@ -1,3 +1,5 @@
require 'xmlrpc/marshal'
module ActionWebService # :nodoc:
module Protocol # :nodoc:
module XmlRpc # :nodoc:
@ -6,22 +8,61 @@ module ActionWebService # :nodoc:
end
class XmlRpcProtocol < AbstractProtocol # :nodoc:
def initialize
@encoder = WS::Encoding::XmlRpcEncoding.new
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
def decode_action_pack_request(action_pack_request)
service_name = action_pack_request.parameters['action']
decode_request(action_pack_request.raw_post, service_name)
end
def unmarshal_request(ap_request)
method_name, params = @encoder.decode_rpc_call(ap_request.raw_post)
params = params.map{|x| @marshaler.unmarshal(x)}
service_name = ap_request.parameters['action']
def decode_request(raw_request, service_name)
method_name, params = XMLRPC::Marshal.load_call(raw_request)
Request.new(self, method_name, params, service_name)
end
def encode_request(method_name, params, param_types)
if param_types
params = params.dup
param_types.each_with_index{ |type, i| params[i] = value_to_xmlrpc_wire_format(params[i], type) }
end
XMLRPC::Marshal.dump_call(method_name, *params)
end
def decode_response(raw_response)
[nil, XMLRPC::Marshal.load_response(raw_response)]
end
def encode_response(method_name, return_value, return_type)
return_value = true if return_value.nil?
if return_type
return_value = value_to_xmlrpc_wire_format(return_value, return_type)
end
raw_response = XMLRPC::Marshal.dump_response(return_value)
Response.new(raw_response, 'text/xml', return_value)
end
def protocol_client(api, protocol_name, endpoint_uri, options={})
return nil unless protocol_name == :xmlrpc
ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
end
def value_to_xmlrpc_wire_format(value, value_type)
if value_type.array?
value.map{ |val| value_to_xmlrpc_wire_format(val, value_type.element_type) }
else
if value.is_a?(ActionWebService::Struct)
struct = {}
value.class.members.each do |name, type|
struct[name.to_s] = value_to_xmlrpc_wire_format(value[name], type)
end
struct
elsif value.is_a?(ActiveRecord::Base)
value.attributes.dup
elsif value.is_a?(Exception) && !value.is_a?(XMLRPC::FaultException)
XMLRPC::FaultException.new(2, value.message)
else
value
end
end
end
end
end
end

View File

@ -1,9 +1,13 @@
require 'ostruct'
require 'uri'
require 'benchmark'
require 'pathname'
module ActionWebService
module Scaffolding # :nodoc:
class ScaffoldingError < ActionWebServiceError # :nodoc:
end
def self.append_features(base)
super
base.extend(ClassMethods)
@ -63,17 +67,32 @@ module ActionWebService
when :xmlrpc
protocol = Protocol::XmlRpc::XmlRpcProtocol.new
end
cgi = @request.cgi
cgi = @request.respond_to?(:cgi) ? @request.cgi : nil
bm = Benchmark.measure do
@method_request_xml = @scaffold_method.encode_rpc_call(protocol.marshaler, protocol.encoder, @params['method_params'].dup)
@request = protocol.create_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
protocol.register_api(@scaffold_service.api)
params = @params['method_params'] ? @params['method_params'].dup : nil
params = @scaffold_method.cast_expects(params)
@method_request_xml = protocol.encode_request(@scaffold_method.public_name, params, @scaffold_method.expects)
@request = protocol.encode_action_pack_request(@scaffold_service.name, @scaffold_method.public_name, @method_request_xml)
dispatch_web_service_request
@method_response_xml = @response.body
@method_return_value = protocol.marshaler.unmarshal(protocol.encoder.decode_rpc_response(@method_response_xml)[1]).value
method_name, obj = protocol.decode_response(@method_response_xml)
if obj.respond_to?(:detail) && obj.detail.respond_to?(:cause) && obj.detail.cause.is_a?(Exception)
raise obj.detail.cause
elsif obj.is_a?(XMLRPC::FaultException)
raise obj
end
@method_return_value = @scaffold_method.cast_returns(obj)
end
@method_elapsed = bm.real
add_instance_variables_to_assigns
@response = ::ActionController::CgiResponse.new(cgi)
template = @response.template
if cgi
@response = ::ActionController::CgiResponse.new(cgi)
else
@response = ::ActionController::TestResponse.new
end
@response.template = template
@performed_render = false
render_#{action_name}_scaffold 'result'
end
@ -99,20 +118,19 @@ module ActionWebService
end
def scaffold_path(template_name)
File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml"
Pathname.new(File.dirname(__FILE__) + "/templates/scaffolds/" + template_name + ".rhtml").realpath.to_s
end
END
end
end
module Helpers # :nodoc:
def method_parameter_input_fields(method, param_spec, i)
klass = method.param_class(param_spec)
unless WS::BaseTypes.base_type?(klass)
name = method.param_name(param_spec, i)
def method_parameter_input_fields(method, type)
name = type.name.to_s
type_name = type.type
unless type_name.is_a?(Symbol)
raise "Parameter #{name}: Structured/array types not supported in scaffolding input fields yet"
end
type_name = method.param_type(param_spec)
field_name = "method_params[]"
case type_name
when :int
@ -168,6 +186,9 @@ module ActionWebService
@name = name.to_s
@object = real_service
@api = @object.class.web_service_api
if @api.nil?
raise ScaffoldingError, "No web service API attached to #{object.class}"
end
@api_methods = {}
@api_methods_full = []
@api.api_methods.each do |name, method|

View File

@ -33,11 +33,20 @@ module ActionWebService
send(name.to_s)
end
# Iterates through each member
def each_pair(&block)
self.class.members.each do |name, type|
yield name, type
end
end
class << self
# Creates a structure member with the specified +name+ and +type+. Generates
# accessor methods for reading and writing the member value.
def member(name, type)
write_inheritable_hash("struct_members", name => WS::BaseTypes.canonical_param_type_class(type))
name = name.to_sym
type = ActionWebService::SignatureTypes.canonical_signature_entry({ name => type }, 0)
write_inheritable_hash("struct_members", name => type)
class_eval <<-END
def #{name}; @#{name}; end
def #{name}=(value); @#{name} = value; end
@ -47,6 +56,10 @@ module ActionWebService
def members # :nodoc:
read_inheritable_attribute("struct_members") || {}
end
def member_type(name) # :nodoc:
members[name.to_sym]
end
end
end
end

View File

@ -0,0 +1,191 @@
module ActionWebService # :nodoc:
module SignatureTypes # :nodoc:
def canonical_signature(signature)
return nil if signature.nil?
i = -1
signature.map{ |spec| canonical_signature_entry(spec, i += 1) }
end
def canonical_signature_entry(spec, i)
name = "param#{i}"
if spec.is_a?(Hash)
name = spec.keys.first
spec = spec.values.first
type = spec
else
type = spec
end
if spec.is_a?(Array)
ArrayType.new(canonical_signature_entry(spec[0], 0), name)
else
type = canonical_type(type)
if type.is_a?(Symbol)
BaseType.new(type, name)
else
StructuredType.new(type, name)
end
end
end
def canonical_type(type)
type_name = symbol_name(type) || class_to_type_name(type)
type = type_name || type
return canonical_type_name(type) if type.is_a?(Symbol)
type
end
def canonical_type_name(name)
name = name.to_sym
case name
when :int, :integer, :fixnum, :bignum
:int
when :string, :base64
:string
when :bool, :boolean
:bool
when :float, :double
:float
when :time, :timestamp
:time
when :datetime
:datetime
when :date
:date
else
raise(TypeError, "#{name} is not a valid base type")
end
end
def canonical_type_class(type)
type = canonical_type(type)
type.is_a?(Symbol) ? type_name_to_class(type) : type
end
def symbol_name(name)
return name.to_sym if name.is_a?(Symbol) || name.is_a?(String)
nil
end
def class_to_type_name(klass)
klass = klass.class unless klass.is_a?(Class)
if derived_from?(Integer, klass) || derived_from?(Fixnum, klass) || derived_from?(Bignum, klass)
:int
elsif klass == String
:string
elsif klass == TrueClass || klass == FalseClass
:bool
elsif derived_from?(Float, klass) || derived_from?(Precision, klass) || derived_from?(Numeric, klass)
:float
elsif klass == Time
:time
elsif klass == DateTime
:datetime
elsif klass == Date
:date
else
nil
end
end
def type_name_to_class(name)
case canonical_type_name(name)
when :int
Integer
when :string
String
when :bool
TrueClass
when :float
Float
when :time
Time
when :date
Date
when :datetime
DateTime
else
nil
end
end
def derived_from?(ancestor, child)
child.ancestors.include?(ancestor)
end
module_function :type_name_to_class
module_function :class_to_type_name
module_function :symbol_name
module_function :canonical_type_class
module_function :canonical_type_name
module_function :canonical_type
module_function :canonical_signature_entry
module_function :canonical_signature
module_function :derived_from?
end
class BaseType # :nodoc:
include SignatureTypes
attr :type
attr :type_class
attr :name
def initialize(type, name)
@type = canonical_type(type)
@type_class = canonical_type_class(@type)
@name = name
end
def custom?
false
end
def array?
false
end
def structured?
false
end
end
class ArrayType < BaseType # :nodoc:
attr :element_type
def initialize(element_type, name)
super(Array, name)
@element_type = element_type
end
def custom?
true
end
def array?
true
end
end
class StructuredType < BaseType # :nodoc:
def each_member
if @type_class.respond_to?(:members)
@type_class.members.each do |name, type|
yield name, type
end
elsif @type_class.respond_to?(:columns)
i = 0
@type_class.columns.each do |column|
yield column.name, canonical_signature_entry(column.klass, i += 1)
end
end
end
def custom?
true
end
def structured?
true
end
end
end

View File

@ -5,10 +5,10 @@
<%= hidden_field_tag "method", @scaffold_method.public_name %>
<% i = 0 %>
<% @scaffold_method.expects.each do |spec| %>
<% @scaffold_method.expects.each do |type| %>
<p>
<label for="method_params[]"><%= @scaffold_method.param_name(spec, i).camelize %></label><br />
<%= method_parameter_input_fields(@scaffold_method, spec, i) %>
<label for="method_params[]"><%= type.name.to_s.camelize %></label><br />
<%= method_parameter_input_fields(@scaffold_method, type) %>
</p>
<% i += 1 %>
<% end %>

View File

@ -21,7 +21,7 @@ module Test # :nodoc:
# invoke the specified layered API method on the correct service
def invoke_layered(service_name, method_name, *args)
if protocol == :soap
if protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
raise "SOAP protocol support for :layered dispatching mode is not available"
end
prepare_request('api', service_name, method_name, *args)
@ -37,10 +37,10 @@ module Test # :nodoc:
@request.env['HTTP_CONTENT_TYPE'] = 'text/xml'
@request.env['RAW_POST_DATA'] = encode_rpc_call(service_name, api_method_name, *args)
case protocol
when :soap
when ActionWebService::Protocol::Soap::SoapProtocol
soap_action = "/#{@controller.controller_name}/#{service_name}/#{public_method_name(service_name, api_method_name)}"
@request.env['HTTP_SOAPACTION'] = soap_action
when :xmlrpc
when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
@request.env.delete('HTTP_SOAPACTION')
end
end
@ -52,19 +52,18 @@ module Test # :nodoc:
when :delegated, :layered
api = @controller.web_service_object(service_name.to_sym).class.web_service_api
end
protocol.register_api(api)
method = api.api_methods[api_method_name.to_sym]
method.register_types(marshaler)
method.encode_rpc_call(marshaler, encoder, args.dup, :method_name => public_method_name(service_name, api_method_name))
protocol.encode_request(public_method_name(service_name, api_method_name), args.dup, method.expects)
end
def decode_rpc_response
public_method_name, return_value = encoder.decode_rpc_response(@response.body)
result = marshaler.unmarshal(return_value).value
public_method_name, return_value = protocol.decode_response(@response.body)
unless @return_exceptions
exception = is_exception?(result)
exception = is_exception?(return_value)
raise exception if exception
end
result
return_value
end
def public_method_name(service_name, api_method_name)
@ -86,25 +85,7 @@ module Test # :nodoc:
end
def protocol
@protocol ||= :soap
end
def marshaler
case protocol
when :soap
@soap_marshaler ||= WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
when :xmlrpc
@xmlrpc_marshaler ||= WS::Marshaling::XmlRpcMarshaler.new
end
end
def encoder
case protocol
when :soap
@soap_encoder ||= WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
when :xmlrpc
@xmlrpc_encoder ||= WS::Encoding::XmlRpcEncoding.new
end
@protocol ||= ActionWebService::Protocol::Soap::SoapProtocol.new
end
def is_exception?(obj)

View File

@ -1,4 +0,0 @@
require 'ws/common'
require 'ws/types'
require 'ws/marshaling'
require 'ws/encoding'

View File

@ -1,8 +0,0 @@
module WS
class WSError < StandardError
end
def self.derived_from?(ancestor, child)
child.ancestors.include?(ancestor)
end
end

View File

@ -1,3 +0,0 @@
require 'ws/encoding/abstract'
require 'ws/encoding/soap_rpc_encoding'
require 'ws/encoding/xmlrpc_encoding'

View File

@ -1,26 +0,0 @@
module WS
module Encoding
# Encoders operate on _foreign_ objects. That is, Ruby object
# instances that are the _marshaling format specific_ representation
# of objects. In other words, objects that have not yet been marshaled, but
# are in protocol-specific form (such as an AST or DOM element), and not
# native Ruby form.
class AbstractEncoding
def encode_rpc_call(method_name, params)
raise NotImplementedError
end
def decode_rpc_call(obj)
raise NotImplementedError
end
def encode_rpc_response(method_name, return_value)
raise NotImplementedError
end
def decode_rpc_response(obj)
raise NotImplementedError
end
end
end
end

View File

@ -1,90 +0,0 @@
require 'soap/processor'
require 'soap/mapping'
require 'soap/rpc/element'
module WS
module Encoding
class SoapRpcError < WSError
end
class SoapRpcEncoding < AbstractEncoding
attr_accessor :method_namespace
def initialize(method_namespace='')
@method_namespace = method_namespace
end
def encode_rpc_call(method_name, foreign_params)
qname = create_method_qname(method_name)
param_def = []
params = foreign_params.map do |p|
param_def << ['in', p.param.info.name, p.param.info.data.mapping]
[p.param.info.name, p.soap_object]
end
request = SOAP::RPC::SOAPMethodRequest.new(qname, param_def)
request.set_param(params)
envelope = create_soap_envelope(request)
SOAP::Processor.marshal(envelope)
end
def decode_rpc_call(obj)
envelope = SOAP::Processor.unmarshal(obj)
unless envelope
raise(SoapRpcError, "Malformed SOAP request")
end
request = envelope.body.request
method_name = request.elename.name
params = request.collect do |key, value|
info = ParamInfo.new(key, nil, nil)
param = Param.new(nil, info)
Marshaling::SoapForeignObject.new(param, request[key])
end
[method_name, params]
end
def encode_rpc_response(method_name, return_value)
response = nil
qname = create_method_qname(method_name)
if return_value.nil?
response = SOAP::RPC::SOAPMethodResponse.new(qname, nil)
else
param = return_value.param
soap_object = return_value.soap_object
param_def = [['retval', 'return', param.info.data.mapping]]
if soap_object.is_a?(SOAP::SOAPFault)
response = soap_object
else
response = SOAP::RPC::SOAPMethodResponse.new(qname, param_def)
response.retval = soap_object
end
end
envelope = create_soap_envelope(response)
SOAP::Processor.marshal(envelope)
end
def decode_rpc_response(obj)
envelope = SOAP::Processor.unmarshal(obj)
unless envelope
raise(SoapRpcError, "Malformed SOAP response")
end
method_name = envelope.body.request.elename.name
return_value = envelope.body.response
info = ParamInfo.new('return', nil, nil)
param = Param.new(nil, info)
return_value = Marshaling::SoapForeignObject.new(param, return_value)
[method_name, return_value]
end
private
def create_soap_envelope(body)
header = SOAP::SOAPHeader.new
body = SOAP::SOAPBody.new(body)
SOAP::SOAPEnvelope.new(header, body)
end
def create_method_qname(method_name)
XSD::QName.new(@method_namespace, method_name)
end
end
end
end

View File

@ -1,44 +0,0 @@
require 'xmlrpc/marshal'
module WS
module Encoding
class XmlRpcEncoding < AbstractEncoding
def encode_rpc_call(method_name, params)
XMLRPC::Marshal.dump_call(method_name, *params)
end
def decode_rpc_call(obj)
method_name, params = XMLRPC::Marshal.load_call(obj)
i = 0
params = params.map do |value|
param = XmlRpcDecodedParam.new("param#{i}", value)
i += 1
param
end
[method_name, params]
end
def encode_rpc_response(method_name, return_value)
if return_value.nil?
XMLRPC::Marshal.dump_response(true)
else
XMLRPC::Marshal.dump_response(return_value)
end
end
def decode_rpc_response(obj)
return_value = XMLRPC::Marshal.load_response(obj)
[nil, XmlRpcDecodedParam.new('return', return_value)]
end
end
class XmlRpcDecodedParam
attr :param
def initialize(name, value)
info = ParamInfo.new(name, value.class)
@param = Param.new(value, info)
end
end
end
end

View File

@ -1,3 +0,0 @@
require 'ws/marshaling/abstract'
require 'ws/marshaling/soap_marshaling'
require 'ws/marshaling/xmlrpc_marshaling'

View File

@ -1,33 +0,0 @@
module WS
module Marshaling
class AbstractMarshaler
def initialize
@base_type_caster = BaseTypeCaster.new
end
def marshal(param)
raise NotImplementedError
end
def unmarshal(param)
raise NotImplementedError
end
def register_type(type)
nil
end
alias :lookup_type :register_type
def cast_inbound_recursive(value, spec)
raise NotImplementedError
end
def cast_outbound_recursive(value, spec)
raise NotImplementedError
end
attr :base_type_caster
protected :base_type_caster
end
end
end

View File

@ -1,283 +0,0 @@
require 'soap/mapping'
require 'xsd/ns'
module WS
module Marshaling
SoapEncodingNS = 'http://schemas.xmlsoap.org/soap/encoding/'
class SoapError < WSError
end
class SoapMarshaler < AbstractMarshaler
attr :registry
attr_accessor :type_namespace
def initialize(type_namespace='')
super()
@type_namespace = type_namespace
@registry = SOAP::Mapping::Registry.new
@spec2binding = {}
end
def marshal(param)
annotate_arrays(param.info.data, param.value)
if param.value.is_a?(Exception)
detail = SOAP::Mapping::SOAPException.new(param.value)
soap_obj = SOAP::SOAPFault.new(
SOAP::SOAPQName.new('%s:%s' % [SOAP::SOAPNamespaceTag, 'Server']),
SOAP::SOAPString.new(param.value.to_s),
SOAP::SOAPString.new(self.class.name),
SOAP::Mapping.obj2soap(detail))
else
soap_obj = SOAP::Mapping.obj2soap(param.value, @registry)
end
SoapForeignObject.new(param, soap_obj)
end
def unmarshal(obj)
param = obj.param
soap_object = obj.soap_object
soap_type = soap_object ? soap_object.type : nil
value = soap_object ? SOAP::Mapping.soap2obj(soap_object, @registry) : nil
param.value = value
param.info.type = value.class
mapping = @registry.find_mapped_soap_class(param.info.type) rescue nil
if soap_type && soap_type.name == 'Array' && soap_type.namespace == SoapEncodingNS
param.info.data = SoapBinding.new(self, soap_object.arytype, Array, mapping)
else
param.info.data = SoapBinding.new(self, soap_type, value.class, mapping)
end
param
end
def register_type(spec)
if @spec2binding.has_key?(spec)
return @spec2binding[spec]
end
klass = BaseTypes.canonical_param_type_class(spec)
if klass.is_a?(Array)
type_class = klass[0]
else
type_class = klass
end
type_binding = nil
if (mapping = @registry.find_mapped_soap_class(type_class) rescue nil)
qname = mapping[2] ? mapping[2][:type] : nil
qname ||= soap_base_type_name(mapping[0])
type_binding = SoapBinding.new(self, qname, type_class, mapping)
else
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
@registry.add(type_class,
SOAP::SOAPStruct,
typed_struct_factory(type_class),
{ :type => qname })
mapping = @registry.find_mapped_soap_class(type_class)
type_binding = SoapBinding.new(self, qname, type_class, mapping)
end
array_binding = nil
if klass.is_a?(Array)
array_mapping = @registry.find_mapped_soap_class(Array) rescue nil
if (array_mapping && !array_mapping[1].is_a?(SoapTypedArrayFactory)) || array_mapping.nil?
@registry.set(Array,
SOAP::SOAPArray,
SoapTypedArrayFactory.new)
array_mapping = @registry.find_mapped_soap_class(Array)
end
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name) + 'Array')
array_binding = SoapBinding.new(self, qname, Array, array_mapping, type_binding)
end
@spec2binding[spec] = array_binding ? array_binding : type_binding
@spec2binding[spec]
end
alias :lookup_type :register_type
def cast_inbound_recursive(value, spec)
binding = lookup_type(spec)
if binding.is_custom_type?
value
else
base_type_caster.cast(value, binding.type_class)
end
end
def cast_outbound_recursive(value, spec)
binding = lookup_type(spec)
if binding.is_custom_type?
value
else
base_type_caster.cast(value, binding.type_class)
end
end
protected
def annotate_arrays(binding, value)
if binding.is_typed_array?
mark_typed_array(value, binding.element_binding.qname)
if binding.element_binding.is_custom_type?
value.each do |element|
annotate_arrays(register_type(element.class), element)
end
end
elsif binding.is_typed_struct?
if binding.type_class.respond_to?(:members)
binding.type_class.members.each do |name, spec|
member_binding = register_type(spec)
member_value = value.respond_to?('[]') ? value[name] : value.send(name)
if member_binding.is_custom_type?
annotate_arrays(member_binding, member_value)
end
end
end
end
end
def mark_typed_array(array, qname)
(class << array; self; end).class_eval do
define_method(:arytype) do
qname
end
end
end
def typed_struct_factory(type_class)
if Object.const_defined?('ActiveRecord')
if WS.derived_from?(ActiveRecord::Base, type_class)
qname = XSD::QName.new(@type_namespace, soap_type_name(type_class.name))
type_class.instance_variable_set('@qname', qname)
return SoapActiveRecordStructFactory.new
end
end
SOAP::Mapping::Registry::TypedStructFactory
end
def soap_type_name(type_name)
type_name.gsub(/::/, '..')
end
def soap_base_type_name(type)
xsd_type = type.ancestors.find{|c| c.const_defined? 'Type'}
xsd_type ? xsd_type.const_get('Type') : XSD::XSDAnySimpleType::Type
end
end
class SoapForeignObject
attr_accessor :param
attr_accessor :soap_object
def initialize(param, soap_object)
@param = param
@soap_object = soap_object
end
end
class SoapBinding
attr :qname
attr :type_class
attr :mapping
attr :element_binding
def initialize(marshaler, qname, type_class, mapping, element_binding=nil)
@marshaler = marshaler
@qname = qname
@type_class = type_class
@mapping = mapping
@element_binding = element_binding
end
def is_custom_type?
is_typed_array? || is_typed_struct?
end
def is_typed_array?
@mapping[1].is_a?(WS::Marshaling::SoapTypedArrayFactory)
end
def is_typed_struct?
@mapping[1] == SOAP::Mapping::Registry::TypedStructFactory || \
@mapping[1].is_a?(WS::Marshaling::SoapActiveRecordStructFactory)
end
def each_member(&block)
if is_typed_struct?
if @mapping[1] == SOAP::Mapping::Registry::TypedStructFactory
if @type_class.respond_to?(:members)
@type_class.members.each do |name, spec|
yield name, spec
end
end
elsif @mapping[1].is_a?(WS::Marshaling::SoapActiveRecordStructFactory)
@type_class.columns.each do |column|
yield column.name, column.klass
end
end
end
end
def type_name
is_custom_type? ? @qname.name : nil
end
def qualified_type_name(ns=nil)
if is_custom_type?
"#{ns ? ns : @qname.namespace}:#{@qname.name}"
else
ns = XSD::NS.new
ns.assign(XSD::Namespace, SOAP::XSDNamespaceTag)
xsd_klass = mapping[0].ancestors.find{|c| c.const_defined?('Type')}
return ns.name(XSD::AnyTypeName) unless xsd_klass
ns.name(xsd_klass.const_get('Type'))
end
end
end
class SoapActiveRecordStructFactory < SOAP::Mapping::Factory
def obj2soap(soap_class, obj, info, map)
unless obj.is_a?(ActiveRecord::Base)
return nil
end
soap_obj = soap_class.new(obj.class.instance_variable_get('@qname'))
obj.class.columns.each do |column|
key = column.name.to_s
value = obj.send(key)
soap_obj[key] = SOAP::Mapping._obj2soap(value, map)
end
soap_obj
end
def soap2obj(obj_class, node, info, map)
unless node.type == obj_class.instance_variable_get('@qname')
return false
end
obj = obj_class.new
node.each do |key, value|
obj[key] = value.data
end
obj.instance_variable_set('@new_record', false)
return true, obj
end
end
class SoapTypedArrayFactory < SOAP::Mapping::Factory
def obj2soap(soap_class, obj, info, map)
unless obj.respond_to?(:arytype)
return nil
end
soap_obj = soap_class.new(SOAP::ValueArrayName, 1, obj.arytype)
mark_marshalled_obj(obj, soap_obj)
obj.each do |item|
child = SOAP::Mapping._obj2soap(item, map)
soap_obj.add(child)
end
soap_obj
end
def soap2obj(obj_class, node, info, map)
return false
end
end
end
end

View File

@ -1,143 +0,0 @@
module WS
module Marshaling
class XmlRpcError < WSError
end
class XmlRpcMarshaler < AbstractMarshaler
def initialize
super()
@spec2binding = {}
end
def marshal(param)
value = param.value
cast_outbound_recursive(param.value, spec_for(param)) rescue value
end
def unmarshal(obj)
obj.param
end
def typed_unmarshal(obj, spec)
obj.param.info.data = lookup_type(spec)
value = obj.param.value
obj.param.value = cast_inbound_recursive(value, spec) rescue value
obj.param
end
def register_type(spec)
if @spec2binding.has_key?(spec)
return @spec2binding[spec]
end
klass = BaseTypes.canonical_param_type_class(spec)
type_binding = nil
if klass.is_a?(Array)
type_binding = XmlRpcArrayBinding.new(klass[0])
else
type_binding = XmlRpcBinding.new(klass)
end
@spec2binding[spec] = type_binding
end
alias :lookup_type :register_type
def cast_inbound_recursive(value, spec)
binding = lookup_type(spec)
case binding
when XmlRpcArrayBinding
value.map{ |x| cast_inbound(x, binding.element_klass) }
when XmlRpcBinding
cast_inbound(value, binding.klass)
end
end
def cast_outbound_recursive(value, spec)
binding = lookup_type(spec)
case binding
when XmlRpcArrayBinding
value.map{ |x| cast_outbound(x, binding.element_klass) }
when XmlRpcBinding
cast_outbound(value, binding.klass)
end
end
private
def spec_for(param)
binding = param.info.data
binding.is_a?(XmlRpcArrayBinding) ? [binding.element_klass] : binding.klass
end
def cast_inbound(value, klass)
if BaseTypes.base_type?(klass)
value = value.to_time if value.is_a?(XMLRPC::DateTime)
base_type_caster.cast(value, klass)
elsif value.is_a?(XMLRPC::FaultException)
value
elsif klass.ancestors.include?(ActionWebService::Struct)
obj = klass.new
klass.members.each do |name, klass|
name = name.to_s
obj.send('%s=' % name, cast_inbound_recursive(value[name], klass))
end
obj
else
obj = klass.new
if obj.respond_to?(:update)
obj.update(value)
else
value.each do |name, val|
obj.send('%s=' % name.to_s, val)
end
end
obj
end
end
def cast_outbound(value, klass)
if BaseTypes.base_type?(klass)
base_type_caster.cast(value, klass)
elsif value.is_a?(Exception)
XMLRPC::FaultException.new(2, value.message)
elsif Object.const_defined?('ActiveRecord') && value.is_a?(ActiveRecord::Base)
value.attributes
elsif value.is_a?(ActionWebService::Struct)
struct = {}
value.class.members.each do |name, klass|
name = name.to_s
struct[name] = cast_outbound_recursive(value[name], klass)
end
struct
else
struct = {}
if value.respond_to?(:each_pair)
value.each_pair{ |key, value| struct[key] = value }
else
value.instance_variables.each do |name|
key = name.sub(/^@/, '')
struct[key] = value.instance_variable_get(name)
end
end
struct
end
end
end
class XmlRpcBinding
attr :klass
def initialize(klass)
@klass = klass
end
end
class XmlRpcArrayBinding < XmlRpcBinding
attr :element_klass
def initialize(element_klass)
super(Array)
@element_klass = element_klass
end
end
end
end

View File

@ -1,165 +0,0 @@
require 'time'
require 'date'
module WS
module BaseTypes
class << self
def type_name_to_class(name)
case canonical_type_name(name)
when :int
Integer
when :string
String
when :bool
TrueClass
when :float
Float
when :time
Time
when :date
Date
end
end
def class_to_type_name(klass)
if WS.derived_from?(Integer, klass) || WS.derived_from?(Fixnum, klass) || WS.derived_from?(Bignum, klass)
:int
elsif klass == String
:string
elsif klass == TrueClass || klass == FalseClass
:bool
elsif WS.derived_from?(Float, klass) || WS.derived_from?(Precision, klass) || WS.derived_from?(Numeric, klass)
:float
elsif klass == Time || klass == DateTime
:time
elsif klass == Date
:date
else
raise(TypeError, "#{klass} is not a valid base type")
end
end
def base_type?(klass)
!(canonical_type_class(klass) rescue nil).nil?
end
def canonical_type_class(klass)
type_name_to_class(class_to_type_name(klass))
end
def canonical_param_type_class(spec)
klass = spec.is_a?(Hash) ? spec.values[0] : spec
array_element_class = klass.is_a?(Array) ? klass[0] : nil
klass = array_element_class ? array_element_class : klass
klass = type_name_to_class(klass) if klass.is_a?(Symbol) || klass.is_a?(String)
base_class = canonical_type_class(klass) rescue nil
klass = base_class unless base_class.nil?
array_element_class ? [klass] : klass
end
def canonical_param_type_spec(spec)
klass = canonical_param_type_class(spec)
spec.is_a?(Hash) ? {spec.keys[0]=>klass} : klass
end
def canonical_type_name(name)
name = name.to_sym
case name
when :int, :integer, :fixnum, :bignum
:int
when :string, :base64
:string
when :bool, :boolean
:bool
when :float, :double
:float
when :time, :datetime, :timestamp
:time
when :date
:date
else
raise(TypeError, "#{name} is not a valid base type")
end
end
end
end
class Param
attr_accessor :value
attr_accessor :info
def initialize(value, info)
@value = value
@info = info
end
end
class ParamInfo
attr_accessor :name
attr_accessor :type
attr_accessor :data
def initialize(name, type, data=nil)
@name = name
@type = type
@data = data
end
def self.create(spec, data, index=nil)
name = spec.is_a?(Hash) ? spec.keys[0].to_s : (index ? "param#{index}" : nil)
type = BaseTypes.canonical_param_type_class(spec)
ParamInfo.new(name, type, data)
end
end
class BaseTypeCaster
def initialize
@handlers = {}
install_handlers
end
def cast(value, klass)
type_class = BaseTypes.canonical_type_class(klass)
return value unless type_class
@handlers[type_class].call(value, type_class)
end
protected
def install_handlers
handler = method(:cast_base_type)
[:int, :string, :bool, :float, :time, :date].each do |name|
type = BaseTypes.type_name_to_class(name)
@handlers[type] = handler
end
@handlers[Fixnum] = handler
end
def cast_base_type(value, type_class)
desired_class = BaseTypes.canonical_type_class(type_class)
value_class = BaseTypes.canonical_type_class(value.class)
return value if desired_class == value_class
desired_name = BaseTypes.class_to_type_name(desired_class)
case desired_name
when :int
Integer(value)
when :string
value.to_s
when :bool
return false if value.nil?
int_value = Integer(value) rescue nil
return true if int_value == 1
return false if int_value == 0
value = value.to_s
return true if value == 'true'
return false if value == 'false'
raise(TypeError, "can't convert #{value} to boolean")
when :float
Float(value)
when :time
Time.parse(value.to_s)
when :date
Date.parse(value.to_s)
end
end
end
end

View File

@ -1,4 +1,5 @@
require File.dirname(__FILE__) + '/abstract_unit'
require 'stringio'
class ActionController::Base; def rescue_action(e) raise e end; end
@ -50,8 +51,9 @@ module DispatcherTest
api_method :before_filtered
api_method :after_filtered, :returns => [[:int]]
api_method :struct_return, :returns => [[Node]]
api_method :struct_pass, :expects => [Person]
api_method :struct_pass, :expects => [{:person => Person}]
api_method :base_struct_return, :returns => [[Person]]
api_method :hash_struct_return, :returns => [[Person]]
api_method :thrower
api_method :void
end
@ -202,6 +204,12 @@ module DispatcherTest
p2 = Person.new('id' => 2, 'name' => 'person2')
[p1, p2]
end
def hash_struct_return
p1 = { :id => '1', 'name' => 'test' }
p2 = { 'id' => '2', :name => 'person2' }
[p1, p2]
end
def void
@void_called = @method_params
@ -234,22 +242,11 @@ module DispatcherCommonTests
assert_equal(50, do_method_call(@direct_controller, 'Add2', 25, 25))
assert_equal(50, @direct_controller.added2)
assert(@direct_controller.void_called == false)
case @encoder
when WS::Encoding::SoapRpcEncoding
assert(do_method_call(@direct_controller, 'Void', 3, 4, 5).nil?)
when WS::Encoding::XmlRpcEncoding
assert(do_method_call(@direct_controller, 'Void', 3, 4, 5) == true)
end
assert(do_method_call(@direct_controller, 'Void', 3, 4, 5).nil?)
assert(@direct_controller.void_called == [])
result = do_method_call(@direct_controller, 'BaseStructReturn')
case @encoder
when WS::Encoding::SoapRpcEncoding
assert(result[0].is_a?(DispatcherTest::Person))
assert(result[1].is_a?(DispatcherTest::Person))
when WS::Encoding::XmlRpcEncoding
assert(result[0].is_a?(Hash))
assert(result[1].is_a?(Hash))
end
assert(result[0].is_a?(DispatcherTest::Person))
assert(result[1].is_a?(DispatcherTest::Person))
end
def test_direct_entrypoint
@ -288,12 +285,7 @@ module DispatcherCommonTests
assert(is_exception?(result))
assert_match(/NonExistentMethod/, exception_message(result))
assert(service.void_called == false)
case @encoder
when WS::Encoding::SoapRpcEncoding
assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5).nil?)
when WS::Encoding::XmlRpcEncoding
assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5) == true)
end
assert(do_method_call(@delegated_controller, 'Void', 3, 4, 5).nil?)
assert(service.void_called == [])
end
@ -302,7 +294,7 @@ module DispatcherCommonTests
controller.class.web_service_exception_reporting = true
send_garbage_request = lambda do
service_name = service_name(controller)
request = @protocol.create_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest)
request = @protocol.encode_action_pack_request(service_name, 'broken, method, name!', 'broken request body', :request_class => ActionController::TestRequest)
response = ActionController::TestResponse.new
controller.process(request, response)
# puts response.body
@ -327,18 +319,10 @@ module DispatcherCommonTests
def test_ar_struct_return
[@direct_controller, @delegated_controller].each do |controller|
result = do_method_call(controller, 'StructReturn')
case @encoder
when WS::Encoding::SoapRpcEncoding
assert(result[0].is_a?(DispatcherTest::Node))
assert(result[1].is_a?(DispatcherTest::Node))
assert_equal('node1', result[0].name)
assert_equal('node2', result[1].name)
when WS::Encoding::XmlRpcEncoding
assert(result[0].is_a?(Hash))
assert(result[1].is_a?(Hash))
assert_equal('node1', result[0]['name'])
assert_equal('node2', result[1]['name'])
end
assert(result[0].is_a?(DispatcherTest::Node))
assert(result[1].is_a?(DispatcherTest::Node))
assert_equal('node1', result[0].name)
assert_equal('node2', result[1].name)
end
end
@ -351,15 +335,26 @@ module DispatcherCommonTests
assert_equal person, @direct_controller.struct_pass_value
assert !person.equal?(@direct_controller.struct_pass_value)
result = do_method_call(@direct_controller, 'StructPass', {'id' => '1', 'name' => 'test'})
case @encoder
when WS::Encoding::SoapRpcEncoding
# We don't cast complex types for SOAP. SOAP clients should have used the WSDL to
# send the correct types.
assert_equal({'id' => '1', 'name' => 'test'}, @direct_controller.struct_pass_value)
when WS::Encoding::XmlRpcEncoding
case @protocol
when ActionWebService::Protocol::Soap::SoapProtocol
assert_equal(person, @direct_controller.struct_pass_value)
assert !person.equal?(@direct_controller.struct_pass_value)
when ActionWebService::Protocol::XmlRpc::XmlRpcProtocol
assert_equal(person, @direct_controller.struct_pass_value)
assert !person.equal?(@direct_controller.struct_pass_value)
end
assert_equal person, do_method_call(@direct_controller, 'HashStructReturn')[0]
end
def test_logging
buf = ""
ActionController::Base.logger = Logger.new(StringIO.new(buf))
test_casting
test_garbage_request
test_exception_marshaling
ActionController::Base.logger = nil
assert_match /Web Service Response/, buf
assert_match /Web Service Request/, buf
end
protected
@ -392,20 +387,27 @@ module DispatcherCommonTests
api = container.web_service_object(service_name.to_sym).class.web_service_api
service_name = self.service_name(container)
end
@protocol.register_api(api)
method = api.public_api_method_instance(public_method_name)
method ||= api.dummy_public_api_method_instance(public_method_name)
# we turn off strict so we can test our own handling of incorrectly typed parameters
body = method.encode_rpc_call(@marshaler, @encoder, params.dup, :strict => false)
virtual = false
unless method
virtual = true
method ||= ActionWebService::API::Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil)
end
body = @protocol.encode_request(public_method_name, params.dup, method.expects)
# puts body
ap_request = protocol.create_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest)
ap_request = @protocol.encode_action_pack_request(service_name, public_method_name, body, :request_class => ActionController::TestRequest)
ap_response = ActionController::TestResponse.new
container.process(ap_request, ap_response)
# puts ap_response.body
public_method_name, return_value = @encoder.decode_rpc_response(ap_response.body)
if @encoder.is_a?(WS::Encoding::SoapRpcEncoding)
public_method_name, return_value = @protocol.decode_response(ap_response.body)
unless is_exception?(return_value) || virtual
return_value = method.cast_returns(return_value)
end
if @protocol.is_a?(ActionWebService::Protocol::Soap::SoapProtocol)
# http://dev.rubyonrails.com/changeset/920
assert_match(/Response$/, public_method_name) unless public_method_name == "fault"
end
@marshaler.unmarshal(return_value).value
return_value
end
end

View File

@ -1,5 +1,5 @@
ENV["RAILS_ENV"] = "test"
$:.unshift(File.dirname(__FILE__) + '/../lib')
$:.unshift(File.dirname(__FILE__) + '/../lib/action_web_service/vendor')
require 'test/unit'
require 'action_web_service'

View File

@ -37,17 +37,17 @@ class TC_API < Test::Unit::TestCase
def test_signature_canonicalization
assert_equal(nil, API.api_methods[:void].expects)
assert_equal(nil, API.api_methods[:void].returns)
assert_equal([String], API.api_methods[:expects_and_returns].expects)
assert_equal([String], API.api_methods[:expects_and_returns].returns)
assert_equal([Integer, TrueClass], API.api_methods[:expects].expects)
assert_equal([String], API.api_methods[:expects_and_returns].expects.map{|x| x.type_class})
assert_equal([String], API.api_methods[:expects_and_returns].returns.map{|x| x.type_class})
assert_equal([Integer, TrueClass], API.api_methods[:expects].expects.map{|x| x.type_class})
assert_equal(nil, API.api_methods[:expects].returns)
assert_equal(nil, API.api_methods[:returns].expects)
assert_equal([Integer, [String]], API.api_methods[:returns].returns)
assert_equal([{:appkey=>Integer}, {:publish=>TrueClass}], API.api_methods[:named_signature].expects)
assert_equal([Integer, [String]], API.api_methods[:returns].returns.map{|x| x.array?? [x.element_type.type_class] : x.type_class})
assert_equal([[:appkey, Integer], [:publish, TrueClass]], API.api_methods[:named_signature].expects.map{|x| [x.name, x.type_class]})
assert_equal(nil, API.api_methods[:named_signature].returns)
assert_equal([Integer, String, TrueClass], API.api_methods[:string_types].expects)
assert_equal([Integer, String, TrueClass], API.api_methods[:string_types].expects.map{|x| x.type_class})
assert_equal(nil, API.api_methods[:string_types].returns)
assert_equal([TrueClass, Integer, String], API.api_methods[:class_types].expects)
assert_equal([TrueClass, Integer, String], API.api_methods[:class_types].expects.map{|x| x.type_class})
assert_equal(nil, API.api_methods[:class_types].returns)
end
@ -75,6 +75,6 @@ class TC_API < Test::Unit::TestCase
end
def test_to_s
assert_equal 'void Expects(int p1, bool p2)', APITest::API.api_methods[:expects].to_s
assert_equal 'void Expects(int param0, bool param1)', APITest::API.api_methods[:expects].to_s
end
end

View File

@ -0,0 +1,82 @@
require File.dirname(__FILE__) + '/abstract_unit'
module CastingTest
class API < ActionWebService::API::Base
api_method :int, :expects => [:int]
api_method :str, :expects => [:string]
api_method :bool, :expects => [:bool]
api_method :float, :expects => [:float]
api_method :time, :expects => [:time]
api_method :datetime, :expects => [:datetime]
api_method :date, :expects => [:date]
api_method :int_array, :expects => [[:int]]
api_method :str_array, :expects => [[:string]]
api_method :bool_array, :expects => [[:bool]]
end
end
class TC_Casting < Test::Unit::TestCase
include CastingTest
def test_base_type_casting_valid
assert_equal 10000, cast_expects(:int, '10000')[0]
assert_equal '10000', cast_expects(:str, 10000)[0]
[1, '1', 'true', 'y', 'yes'].each do |val|
assert_equal true, cast_expects(:bool, val)[0]
end
[0, '0', 'false', 'n', 'no'].each do |val|
assert_equal false, cast_expects(:bool, val)[0]
end
assert_equal 3.14159, cast_expects(:float, '3.14159')[0]
now = Time.at(Time.now.tv_sec)
casted = cast_expects(:time, now.to_s)[0]
assert_equal now, casted
now = DateTime.now
assert_equal now.to_s, cast_expects(:datetime, now.to_s)[0].to_s
today = Date.today
assert_equal today, cast_expects(:date, today.to_s)[0]
end
def test_base_type_casting_invalid
assert_raises ArgumentError do
cast_expects(:int, 'this is not a number')
end
assert_raises ActionWebService::Casting::CastingError do
# neither true or false ;)
cast_expects(:bool, 'i always lie')
end
assert_raises ArgumentError do
cast_expects(:float, 'not a float')
end
assert_raises ArgumentError do
cast_expects(:time, '111111111111111111111111111111111')
end
assert_raises ArgumentError do
cast_expects(:datetime, '-1')
end
assert_raises ArgumentError do
cast_expects(:date, '')
end
end
def test_array_type_casting
assert_equal [1, 2, 3213992, 4], cast_expects(:int_array, ['1', '2', '3213992', '4'])[0]
assert_equal ['one', 'two', '5.0', '200', '', 'true'], cast_expects(:str_array, [:one, 'two', 5.0, 200, nil, true])[0]
assert_equal [true, false, true, true, false], cast_expects(:bool_array, ['1', nil, 'y', true, 'false'])[0]
end
def test_array_type_casting_failure
assert_raises ActionWebService::Casting::CastingError do
cast_expects(:bool_array, ['false', 'blahblah'])
end
assert_raises ArgumentError do
cast_expects(:int_array, ['1', '2.021', '4'])
end
end
private
def cast_expects(method_name, *args)
API.api_method_instance(method_name.to_sym).cast_expects([*args])
end
end

View File

@ -68,9 +68,7 @@ class TC_ClientSoap < Test::Unit::TestCase
assert_equal([5, 6], @container.value_normal)
assert_equal(5, @client.normal("7", "8"))
assert_equal([7, 8], @container.value_normal)
assert_raises(TypeError) do
assert_equal(5, @client.normal(true, false))
end
assert_equal(5, @client.normal(true, false))
end
def test_array_return

View File

@ -15,6 +15,7 @@ module ClientXmlRpcTest
@controller.process(test_request, response)
res.header['content-type'] = 'text/xml'
res.body = response.body
# puts res.body
rescue Exception => e
$stderr.puts e.message
$stderr.puts e.backtrace.join("\n")
@ -62,9 +63,7 @@ class TC_ClientXmlRpc < Test::Unit::TestCase
assert_equal([5, 6], @container.value_normal)
assert_equal(5, @client.normal("7", "8"))
assert_equal([7, 8], @container.value_normal)
assert_raises(TypeError) do
assert_equal(5, @client.normal(true, false))
end
assert_equal(5, @client.normal(true, false))
end
def test_array_return
@ -91,7 +90,7 @@ class TC_ClientXmlRpc < Test::Unit::TestCase
def test_named_parameters
assert(@container.value_named_parameters.nil?)
assert_equal(nil, @client.named_parameters("xxx", 7))
assert_equal(true, @client.named_parameters("xxx", 7))
assert_equal(["xxx", 7], @container.value_named_parameters)
end

View File

@ -23,8 +23,6 @@ class TC_DispatcherActionControllerSoap < Test::Unit::TestCase
include DispatcherCommonTests
def setup
@encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
@marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
@direct_controller = DirectController.new
@delegated_controller = DelegatedController.new
@virtual_controller = VirtualController.new

View File

@ -6,8 +6,6 @@ class TC_DispatcherActionControllerXmlRpc < Test::Unit::TestCase
def setup
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
@encoder = WS::Encoding::XmlRpcEncoding.new
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
@direct_controller = DirectController.new
@delegated_controller = DelegatedController.new
@layered_controller = LayeredController.new

View File

@ -0,0 +1,67 @@
require File.dirname(__FILE__) + '/abstract_unit'
ActionController::Routing::Routes.draw do |map|
map.connect '', :controller => 'scaffolded'
end
class ScaffoldPerson < ActionWebService::Struct
member :id, :int
member :name, :string
def ==(other)
self.id == other.id && self.name == other.name
end
end
class ScaffoldedControllerTestAPI < ActionWebService::API::Base
api_method :hello, :expects => [{:integer=>:int}, :string], :returns => [:bool]
api_method :bye, :returns => [[ScaffoldPerson]]
end
class ScaffoldedController < ActionController::Base
web_service_api ScaffoldedControllerTestAPI
web_service_scaffold :scaffold_invoke
def hello(int, string)
0
end
def bye
[ScaffoldPerson.new(:id => 1, :name => "leon"), ScaffoldPerson.new(:id => 2, :name => "paul")]
end
def rescue_action(e)
raise e
end
end
class ScaffoldedControllerTest < Test::Unit::TestCase
def setup
@controller = ScaffoldedController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_scaffold_invoke
get :scaffold_invoke
assert_rendered_file 'methods.rhtml'
end
def test_scaffold_invoke_method_params
get :scaffold_invoke_method_params, :service => 'scaffolded', :method => 'Hello'
assert_rendered_file 'parameters.rhtml'
end
def test_scaffold_invoke_submit_hello
post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Hello', :method_params => ['5', 'hello world']
assert_rendered_file 'result.rhtml'
assert_equal false, @controller.instance_eval{ @method_return_value }
end
def test_scaffold_invoke_submit_bye
post :scaffold_invoke_submit, :service => 'scaffolded', :method => 'Bye'
assert_rendered_file 'result.rhtml'
persons = [ScaffoldPerson.new(:id => 1, :name => "leon"), ScaffoldPerson.new(:id => 2, :name => "paul")]
assert_equal persons, @controller.instance_eval{ @method_return_value }
end
end

View File

@ -11,30 +11,44 @@ module StructTest
end
class TC_Struct < Test::Unit::TestCase
include StructTest
def setup
@struct = Struct.new(:id => 5,
:name => 'hello',
:items => ['one', 'two'],
:deleted => true,
:emails => ['test@test.com'])
end
def test_members
assert_equal(5, StructTest::Struct.members.size)
assert_equal(Integer, StructTest::Struct.members[:id])
assert_equal(String, StructTest::Struct.members[:name])
assert_equal([String], StructTest::Struct.members[:items])
assert_equal(TrueClass, StructTest::Struct.members[:deleted])
assert_equal([String], StructTest::Struct.members[:emails])
assert_equal(5, Struct.members.size)
assert_equal(Integer, Struct.members[:id].type_class)
assert_equal(String, Struct.members[:name].type_class)
assert_equal(String, Struct.members[:items].element_type.type_class)
assert_equal(TrueClass, Struct.members[:deleted].type_class)
assert_equal(String, Struct.members[:emails].element_type.type_class)
end
def test_initializer_and_lookup
s = StructTest::Struct.new(:id => 5,
:name => 'hello',
:items => ['one', 'two'],
:deleted => true,
:emails => ['test@test.com'])
assert_equal(5, s.id)
assert_equal('hello', s.name)
assert_equal(['one', 'two'], s.items)
assert_equal(true, s.deleted)
assert_equal(['test@test.com'], s.emails)
assert_equal(5, s['id'])
assert_equal('hello', s['name'])
assert_equal(['one', 'two'], s['items'])
assert_equal(true, s['deleted'])
assert_equal(['test@test.com'], s['emails'])
assert_equal(5, @struct.id)
assert_equal('hello', @struct.name)
assert_equal(['one', 'two'], @struct.items)
assert_equal(true, @struct.deleted)
assert_equal(['test@test.com'], @struct.emails)
assert_equal(5, @struct['id'])
assert_equal('hello', @struct['name'])
assert_equal(['one', 'two'], @struct['items'])
assert_equal(true, @struct['deleted'])
assert_equal(['test@test.com'], @struct['emails'])
end
def test_each_pair
members = {}
@struct.each_pair do |name, type|
members[name] = type
assert ActionWebService::BaseType === type
end
assert_equal members, Struct.members
end
end

View File

@ -65,7 +65,7 @@ class TestInvokeTest < Test::Unit::TestCase
end
def test_layered_add
@protocol = :xmlrpc
@protocol = ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.new
@controller = TestInvokeLayeredController.new
[:one, :two].each do |service|
assert_equal nil, @controller.web_service_object(service).invoked

View File

@ -1,68 +0,0 @@
require File.dirname(__FILE__) + '/abstract_unit'
module Nested
class StructClass
attr_accessor :name
attr_accessor :version
def initialize
@name = 5
@version = "1.0"
end
def ==(other)
@name == other.name && @version == other.version
end
end
end
module EncodingTest
def setup
@call_signature = [:int, :bool, :string, :float, [:time], Nested::StructClass]
@call_params = [1, true, "string", 5.0, [Time.now], Nested::StructClass.new]
@response_signature = [:string]
@response_param = "hello world"
test_setup
end
def test_abstract
obj = WS::Encoding::AbstractEncoding.new
assert_raises(NotImplementedError) do
obj.encode_rpc_call(nil, nil)
end
assert_raises(NotImplementedError) do
obj.decode_rpc_call(nil)
end
assert_raises(NotImplementedError) do
obj.encode_rpc_response(nil, nil)
end
assert_raises(NotImplementedError) do
obj.decode_rpc_response(nil)
end
end
def encode_rpc_call(method_name, signature, params)
params = params.dup
(0..(signature.length-1)).each do |i|
type_binding = @marshaler.register_type(signature[i])
info = WS::ParamInfo.create(signature[i], type_binding, i)
params[i] = @marshaler.marshal(WS::Param.new(params[i], info))
end
@encoder.encode_rpc_call(method_name, params)
end
def decode_rpc_call(obj)
@encoder.decode_rpc_call(obj)
end
def encode_rpc_response(method_name, signature, param)
type_binding = @marshaler.register_type(signature[0])
info = WS::ParamInfo.create(signature[0], type_binding, 0)
param = @marshaler.marshal(WS::Param.new(param, info))
@encoder.encode_rpc_response(method_name, param)
end
def decode_rpc_response(obj)
@encoder.decode_rpc_response(obj)
end
end

View File

@ -1,13 +0,0 @@
require 'pathname'
$:.unshift(Pathname.new(File.dirname(__FILE__)).realpath.to_s + '/../../lib/action_web_service/vendor')
require 'test/unit'
require 'ws'
begin
require 'active_record'
rescue LoadError
begin
require 'rubygems'
require_gem 'activerecord', '>= 1.6.0'
rescue LoadError
end
end

View File

@ -1,3 +0,0 @@
#!/bin/sh
rcov -x '.*_test\.rb,rubygems,abstract_,/run' ./run

View File

@ -1,5 +0,0 @@
#!/usr/bin/env ruby
Dir[File.join(File.dirname(__FILE__), '*_test.rb')].each do |f|
require f
end

View File

@ -1,97 +0,0 @@
require File.dirname(__FILE__) + '/abstract_unit'
module Nested
class MyClass
attr_accessor :id
attr_accessor :name
def initialize(id, name)
@id = id
@name = name
end
def ==(other)
@id == other.id && @name == other.name
end
end
end
class SoapMarshalingTest < Test::Unit::TestCase
def setup
@marshaler = WS::Marshaling::SoapMarshaler.new
end
def test_abstract
marshaler = WS::Marshaling::AbstractMarshaler.new
assert_raises(NotImplementedError) do
marshaler.marshal(nil)
end
assert_raises(NotImplementedError) do
marshaler.unmarshal(nil)
end
assert_equal(nil, marshaler.register_type(nil))
assert_raises(NotImplementedError) do
marshaler.cast_inbound_recursive(nil, nil)
end
assert_raises(NotImplementedError) do
marshaler.cast_outbound_recursive(nil, nil)
end
end
def test_marshaling
info = WS::ParamInfo.create(Nested::MyClass, @marshaler.register_type(Nested::MyClass))
param = WS::Param.new(Nested::MyClass.new(2, "name"), info)
new_param = @marshaler.unmarshal(@marshaler.marshal(param))
assert(param == new_param)
end
def test_exception_marshaling
info = WS::ParamInfo.create(RuntimeError, @marshaler.register_type(RuntimeError))
param = WS::Param.new(RuntimeError.new("hello, world"), info)
new_param = @marshaler.unmarshal(@marshaler.marshal(param))
assert_equal("hello, world", new_param.value.detail.cause.message)
end
def test_registration
type_binding1 = @marshaler.register_type(:int)
type_binding2 = @marshaler.register_type(:int)
assert(type_binding1.equal?(type_binding2))
end
def test_active_record
if Object.const_defined?('ActiveRecord')
node_class = Class.new(ActiveRecord::Base) do
def initialize(*args)
super(*args)
@new_record = false
end
class << self
def name
"Node"
end
def columns(*args)
[
ActiveRecord::ConnectionAdapters::Column.new('id', 0, 'int'),
ActiveRecord::ConnectionAdapters::Column.new('name', nil, 'string'),
ActiveRecord::ConnectionAdapters::Column.new('email', nil, 'string'),
]
end
def connection
self
end
end
end
info = WS::ParamInfo.create(node_class, @marshaler.register_type(node_class), 0)
ar_obj = node_class.new('name' => 'hello', 'email' => 'test@test.com')
param = WS::Param.new(ar_obj, info)
obj = @marshaler.marshal(param)
param = @marshaler.unmarshal(obj)
new_ar_obj = param.value
assert_equal(ar_obj, new_ar_obj)
assert(!ar_obj.equal?(new_ar_obj))
end
end
end

View File

@ -1,47 +0,0 @@
require File.dirname(__FILE__) + '/abstract_encoding'
require 'time'
class SoapRpcEncodingTest < Test::Unit::TestCase
include EncodingTest
def test_setup
@encoder = WS::Encoding::SoapRpcEncoding.new
@marshaler = WS::Marshaling::SoapMarshaler.new
end
def test_call_encoding_and_decoding
obj = encode_rpc_call('DecodeMe', @call_signature, @call_params)
method_name, decoded_params = decode_rpc_call(obj)
params = decoded_params.map{|x| @marshaler.unmarshal(x).value}
assert_equal(method_name, 'DecodeMe')
assert_equal(@call_params[0..3], params[0..3])
# XXX: DateTime not marshaled correctly yet
assert_equal(@call_params[5..-1], params[5..-1])
end
def test_response_encoding_and_decoding_simple
obj = encode_rpc_response('DecodeMe', @response_signature, @response_param)
method_name, return_value = decode_rpc_response(obj)
return_value = @marshaler.unmarshal(return_value).value
assert_equal('DecodeMe', method_name)
assert_equal(@response_param, return_value)
end
def test_response_encoding_and_decoding_struct
struct = Nested::StructClass.new
obj = encode_rpc_response('DecodeMe', [Nested::StructClass], struct)
method_name, return_value = decode_rpc_response(obj)
return_value = @marshaler.unmarshal(return_value).value
assert_equal('DecodeMe', method_name)
assert_equal(struct, return_value)
end
def test_response_encoding_and_decoding_array
struct = Nested::StructClass.new
obj = encode_rpc_response('DecodeMe', [[Nested::StructClass]], [struct])
method_name, return_value = decode_rpc_response(obj)
return_value = @marshaler.unmarshal(return_value).value
assert_equal('DecodeMe', method_name)
assert_equal([struct], return_value)
end
end

View File

@ -1,43 +0,0 @@
require File.dirname(__FILE__) + '/abstract_unit'
class TypesTest < Test::Unit::TestCase
include WS
def setup
@caster = BaseTypeCaster.new
end
def test_base_types
assert_equal(:int, BaseTypes.canonical_type_name(:integer))
assert_equal(:int, BaseTypes.canonical_type_name(:fixnum))
assert_equal(Integer, BaseTypes.type_name_to_class(:bignum))
assert_equal(Date, BaseTypes.type_name_to_class(:date))
assert_equal(Time, BaseTypes.type_name_to_class(:timestamp))
assert_equal(TrueClass, BaseTypes.type_name_to_class(:bool))
assert_equal(:int, BaseTypes.class_to_type_name(Bignum))
assert_equal(:bool, BaseTypes.class_to_type_name(FalseClass))
assert_equal(Integer, BaseTypes.canonical_type_class(Fixnum))
assert_raises(TypeError) do
BaseTypes.canonical_type_name(:fake)
end
end
def test_casting
assert_equal(5, @caster.cast("5", Fixnum))
assert_equal('50.0', @caster.cast(50.0, String))
assert_equal(true, @caster.cast('true', FalseClass))
assert_equal(false, @caster.cast('false', TrueClass))
assert_equal(true, @caster.cast(1, FalseClass))
assert_equal(false, @caster.cast(0, TrueClass))
assert_raises(TypeError) do
@caster.cast('yes', FalseClass)
end
assert_equal(3.14159, @caster.cast('3.14159', Float))
now1 = Time.new
now2 = @caster.cast("#{now1}", Time)
assert_equal(now1.tv_sec, now2.tv_sec)
date1 = Date.parse('2004-01-01')
date2 = @caster.cast("#{date1}", Date)
assert_equal(date1, date2)
end
end

View File

@ -1,34 +0,0 @@
require File.dirname(__FILE__) + '/abstract_encoding'
require 'time'
class XmlRpcEncodingTest < Test::Unit::TestCase
include EncodingTest
def test_setup
@encoder = WS::Encoding::XmlRpcEncoding.new
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
end
def test_typed_call_encoding_and_decoding
obj = encode_rpc_call('DecodeMe', @call_signature, @call_params)
method_name, params = decode_rpc_call(obj)
(0..(@call_signature.length-1)).each do |i|
params[i] = @marshaler.typed_unmarshal(params[i], @call_signature[i]).value
end
assert_equal(method_name, 'DecodeMe')
assert_equal(@call_params[0..3], params[0..3])
assert_equal(@call_params[5..-1], params[5..-1])
end
def test_untyped_call_encoding_and_decoding
obj = encode_rpc_call('DecodeMe', @call_signature, @call_params)
method_name, params = decode_rpc_call(obj)
(0..(@call_signature.length-1)).each do |i|
params[i] = @marshaler.unmarshal(params[i]).value
end
assert_equal(method_name, 'DecodeMe')
assert_equal(@call_params[0..3], params[0..3])
assert_equal(@call_params[5].name, params[5]['name'])
assert_equal(@call_params[5].version, params[5]['version'])
end
end