* 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:
parent
aa09c770e9
commit
aaea48fe98
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -264,4 +264,4 @@ task :release => [:package] do
|
|||
first_file = false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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 %>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
require 'ws/common'
|
||||
require 'ws/types'
|
||||
require 'ws/marshaling'
|
||||
require 'ws/encoding'
|
|
@ -1,8 +0,0 @@
|
|||
module WS
|
||||
class WSError < StandardError
|
||||
end
|
||||
|
||||
def self.derived_from?(ancestor, child)
|
||||
child.ancestors.include?(ancestor)
|
||||
end
|
||||
end
|
|
@ -1,3 +0,0 @@
|
|||
require 'ws/encoding/abstract'
|
||||
require 'ws/encoding/soap_rpc_encoding'
|
||||
require 'ws/encoding/xmlrpc_encoding'
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
require 'ws/marshaling/abstract'
|
||||
require 'ws/marshaling/soap_marshaling'
|
||||
require 'ws/marshaling/xmlrpc_marshaling'
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
rcov -x '.*_test\.rb,rubygems,abstract_,/run' ./run
|
|
@ -1,5 +0,0 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
Dir[File.join(File.dirname(__FILE__), '*_test.rb')].each do |f|
|
||||
require f
|
||||
end
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue