Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

generalize casting code to be used by both SOAP and XML-RPC (previous…

…ly only XML-RPC). switch

to better model for API methods, and improve the ability to generate protocol requests/response,
will be required by upcoming scaffolding.


git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1030 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 594063f23cf8e7cecd24329e801992784f420b55 1 parent 439a216
Leon Breedt bitserf authored
Showing with 432 additions and 249 deletions.
  1. +5 −0 actionwebservice/CHANGELOG
  2. +161 −3 actionwebservice/lib/action_web_service/api/base.rb
  3. +14 −26 actionwebservice/lib/action_web_service/client/soap_client.rb
  4. +7 −33 actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
  5. +25 −55 actionwebservice/lib/action_web_service/dispatcher/abstract.rb
  6. +19 −18 actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
  7. +15 −1 actionwebservice/lib/action_web_service/protocol/abstract.rb
  8. +1 −17 actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
  9. +1 −19 actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
  10. +3 −12 actionwebservice/lib/action_web_service/test_invoke.rb
  11. +16 −0 actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb
  12. +21 −1 actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb
  13. +70 −43 actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb
  14. +41 −12 actionwebservice/test/abstract_dispatcher.rb
  15. +14 −7 actionwebservice/test/api_test.rb
  16. +5 −0 actionwebservice/test/client_soap_test.rb
  17. +7 −2 actionwebservice/test/client_xmlrpc_test.rb
  18. +1 −0  actionwebservice/test/run
  19. +6 −0 actionwebservice/test/ws/soap_marshaling_test.rb
5 actionwebservice/CHANGELOG
View
@@ -1,3 +1,8 @@
+*0.7.0* (Unreleased)
+
+* Generalize casting code to be used by both SOAP and XML-RPC (previously, it was only XML-RPC)
+
+
*0.6.2* (27th March, 2005)
* Allow method declarations for direct dispatching to declare parameters as well. We treat an arity of < 0 or > 0 as an indication that we should send through parameters. Closes #939.
164 actionwebservice/lib/action_web_service/api/base.rb
View
@@ -1,5 +1,8 @@
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.
@@ -77,8 +80,8 @@ def api_method(name, options={})
end
name = name.to_sym
public_name = public_api_method_name(name)
- info = { :expects => expects, :returns => returns }
- write_inheritable_hash("api_methods", name => info)
+ method = Method.new(name, public_name, expects, returns)
+ write_inheritable_hash("api_methods", name => method)
write_inheritable_hash("api_public_method_names", public_name => name)
end
@@ -112,7 +115,39 @@ def api_method_name(public_name)
def api_methods
read_inheritable_attribute("api_methods") || {}
end
-
+
+ # The Method instance for the given public API method name, if any
+ def public_api_method_instance(public_method_name)
+ api_method_instance(api_method_name(public_method_name))
+ end
+
+ # The Method instance for the given API method name, if any
+ def api_method_instance(method_name)
+ api_methods[method_name]
+ end
+
+ # The Method instance for the default API method, if any
+ def default_api_method_instance
+ return nil unless name = default_api_method
+ instance = read_inheritable_attribute("default_api_method_instance")
+ if instance && instance.name == name
+ return instance
+ end
+ instance = Method.new(name, public_api_method_name(name), nil, nil)
+ write_inheritable_attribute("default_api_method_instance", instance)
+ 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") || {}
@@ -131,5 +166,128 @@ def canonical_signature(signature)
end
end
end
+
+ # Represents an API method and its associated metadata, and provides functionality
+ # to assist in commonly performed API method tasks.
+ class Method
+ attr :name
+ attr :public_name
+ attr :expects
+ attr :returns
+
+ def initialize(name, public_name, expects, returns)
+ @name = name
+ @public_name = public_name
+ @expects = expects
+ @returns = returns
+ 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) }
+ 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
+ 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])
+ end
+
+ private
+ def response_name(encoder)
+ encoder.is_a?(WS::Encoding::SoapRpcEncoding) ? (@public_name + "Response") : @public_name
+ end
+ end
end
end
40 actionwebservice/lib/action_web_service/client/soap_client.rb
View
@@ -58,7 +58,10 @@ def initialize(api, endpoint_uri, options={})
protected
def perform_invocation(method_name, args)
- @driver.send(method_name, *args)
+ method = @api.api_methods[method_name.to_sym]
+ args = method.cast_expects(@marshaler, args)
+ return_value = @driver.send(method_name, *args)
+ method.cast_returns(@marshaler, return_value)
end
def soap_action(method_name)
@@ -67,48 +70,33 @@ def soap_action(method_name)
private
def create_soap_rpc_driver(api, endpoint_uri)
- register_api(@marshaler, api)
+ api.api_methods.each{ |name, method| method.register_types(@marshaler) }
driver = SoapDriver.new(endpoint_uri, nil)
driver.mapping_registry = @marshaler.registry
- api.api_methods.each do |name, info|
- public_name = api.public_api_method_name(name)
- qname = XSD::QName.new(@method_namespace, public_name)
- action = soap_action(public_name)
- expects = info[:expects]
- returns = info[:returns]
+ api.api_methods.each do |name, method|
+ qname = XSD::QName.new(@method_namespace, method.public_name)
+ action = soap_action(method.public_name)
+ expects = method.expects
+ returns = method.returns
param_def = []
i = 0
if expects
expects.each do |spec|
- param_name = spec.is_a?(Hash) ? spec.keys[0].to_s : "param#{i}"
- type_binding = @marshaler.register_type(spec)
+ param_name = method.param_name(spec, i)
+ type_binding = @marshaler.lookup_type(spec)
param_def << ['in', param_name, type_binding.mapping]
i += 1
end
end
if returns
- type_binding = @marshaler.register_type(returns[0])
+ type_binding = @marshaler.lookup_type(returns[0])
param_def << ['retval', 'return', type_binding.mapping]
end
- driver.add_method(qname, action, name.to_s, param_def)
+ driver.add_method(qname, action, method.name.to_s, param_def)
end
driver
end
- def register_api(marshaler, api)
- type_bindings = []
- api.api_methods.each do |name, info|
- expects, returns = info[:expects], info[:returns]
- if expects
- expects.each{|type| type_bindings << marshaler.register_type(type)}
- end
- if returns
- returns.each{|type| type_bindings << marshaler.register_type(type)}
- end
- end
- type_bindings
- end
-
class SoapDriver < SOAP::RPC::Driver # :nodoc:
def add_method(qname, soapaction, name, param_def)
@proxy.add_rpc_method(qname, soapaction, name, param_def)
40 actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
View
@@ -36,43 +36,17 @@ def initialize(api, endpoint_uri, options={})
protected
def perform_invocation(method_name, args)
- args = transform_outgoing_method_params(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)
ok, return_value = @client.call2(public_name(method_name), *args)
- return transform_return_value(method_name, return_value) if ok
+ return method.cast_returns(@marshaler, return_value) if ok
raise(ClientError, "#{return_value.faultCode}: #{return_value.faultString}")
end
- def transform_outgoing_method_params(method_name, params)
- info = @api.api_methods[method_name.to_sym]
- expects = info[:expects]
- expects_length = expects.nil?? 0 : expects.length
- if expects_length != params.length
- raise(ClientError, "API declares #{public_name(method_name)} to accept " +
- "#{expects_length} parameters, but #{params.length} parameters " +
- "were supplied")
- end
- params = params.dup
- if expects_length > 0
- i = 0
- expects.each do |spec|
- type_binding = @marshaler.register_type(spec)
- info = WS::ParamInfo.create(spec, type_binding, i)
- params[i] = @marshaler.marshal(WS::Param.new(params[i], info))
- i += 1
- end
- end
- params
- end
-
- def transform_return_value(method_name, return_value)
- info = @api.api_methods[method_name.to_sym]
- return true unless returns = info[:returns]
- type_binding = @marshaler.register_type(returns[0])
- info = WS::ParamInfo.create(returns[0], type_binding, 0)
- info.name = 'return'
- @marshaler.transform_inbound(WS::Param.new(return_value, info))
- end
-
def public_name(method_name)
public_name = @api.public_api_method_name(method_name)
@handler_name ? "#{@handler_name}.#{public_name}" : public_name
80 actionwebservice/lib/action_web_service/dispatcher/abstract.rb
View
@@ -34,30 +34,30 @@ def invoke_web_service_request(protocol_request)
def web_service_direct_invoke(invocation)
@method_params = invocation.method_ordered_params
- arity = method(invocation.api_method_name).arity rescue 0
+ arity = method(invocation.api_method.name).arity rescue 0
if arity < 0 || arity > 0
- return_value = self.__send__(invocation.api_method_name, *@method_params)
+ return_value = self.__send__(invocation.api_method.name, *@method_params)
else
- return_value = self.__send__(invocation.api_method_name)
+ return_value = self.__send__(invocation.api_method.name)
end
- if invocation.api.has_api_method?(invocation.api_method_name)
- returns = invocation.returns ? invocation.returns[0] : nil
+ if invocation.api.has_api_method?(invocation.api_method.name)
+ api_method = invocation.api_method
else
- returns = return_value.class
+ api_method = invocation.api_method.dup
+ api_method.instance_eval{ @returns = [ return_value.class ] }
end
- invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns)
+ invocation.protocol.marshal_response(api_method, return_value)
end
def web_service_delegated_invoke(invocation)
cancellation_reason = nil
- return_value = invocation.service.perform_invocation(invocation.api_method_name, invocation.method_ordered_params) do |x|
+ return_value = invocation.service.perform_invocation(invocation.api_method.name, invocation.method_ordered_params) do |x|
cancellation_reason = x
end
if cancellation_reason
raise(DispatcherError, "request canceled: #{cancellation_reason}")
end
- returns = invocation.returns ? invocation.returns[0] : nil
- invocation.protocol.marshal_response(invocation.public_method_name, return_value, returns)
+ invocation.protocol.marshal_response(invocation.api_method, return_value)
end
def web_service_invocation(request)
@@ -71,7 +71,6 @@ def web_service_invocation(request)
invocation.service_name = $1
end
end
- invocation.public_method_name = public_method_name
case web_service_dispatching_mode
when :direct
invocation.api = self.class.web_service_api
@@ -83,54 +82,29 @@ def web_service_invocation(request)
end
invocation.api = invocation.service.class.web_service_api
end
+ request.api = invocation.api
if invocation.api.has_public_api_method?(public_method_name)
- invocation.api_method_name = invocation.api.api_method_name(public_method_name)
+ invocation.api_method = invocation.api.public_api_method_instance(public_method_name)
else
if invocation.api.default_api_method.nil?
raise(DispatcherError, "no such method '#{public_method_name}' on API #{invocation.api}")
else
- invocation.api_method_name = invocation.api.default_api_method.to_s.to_sym
+ invocation.api_method = invocation.api.default_api_method_instance
end
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})")
+ 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})")
end
- info = invocation.api.api_methods[invocation.api_method_name]
- invocation.expects = info ? info[:expects] : nil
- invocation.returns = info ? info[:returns] : nil
- if invocation.expects
- i = 0
- invocation.method_ordered_params = request.method_params.map do |param|
- if invocation.protocol.is_a?(Protocol::XmlRpc::XmlRpcProtocol)
- marshaler = invocation.protocol.marshaler
- decoded_param = WS::Encoding::XmlRpcDecodedParam.new(param.info.name, param.value)
- marshaled_param = marshaler.typed_unmarshal(decoded_param, invocation.expects[i]) rescue nil
- param = marshaled_param ? marshaled_param : param
- end
- i += 1
- param.value
- end
- i = 0
- params = []
- invocation.expects.each do |spec|
- type_binding = invocation.protocol.register_signature_type(spec)
- info = WS::ParamInfo.create(spec, type_binding, i)
- params << WS::Param.new(invocation.method_ordered_params[i], info)
- i += 1
- end
- invocation.method_ws_params = params
- invocation.method_named_params = {}
- invocation.method_ws_params.each do |param|
- invocation.method_named_params[param.info.name] = param.value
- end
- else
- invocation.method_ordered_params = []
- invocation.method_named_params = {}
+ request.api_method = invocation.api_method
+ begin
+ invocation.method_ordered_params = invocation.api_method.cast_expects_ws2ruby(request.protocol.marshaler, request.method_params)
+ rescue
+ invocation.method_ordered_params = request.method_params.map{ |x| x.value }
end
- if invocation.returns
- invocation.returns.each do |spec|
- invocation.protocol.register_signature_type(spec)
- end
+ invocation.method_named_params = {}
+ invocation.api_method.param_names.inject(0) do |m, n|
+ invocation.method_named_params[n] = invocation.method_ordered_params[m]
+ m + 1
end
invocation
end
@@ -139,13 +113,9 @@ class Invocation # :nodoc:
attr_accessor :protocol
attr_accessor :service_name
attr_accessor :api
- attr_accessor :public_method_name
- attr_accessor :api_method_name
+ attr_accessor :api_method
attr_accessor :method_ordered_params
attr_accessor :method_named_params
- attr_accessor :method_ws_params
- attr_accessor :expects
- attr_accessor :returns
attr_accessor :service
end
end
37 actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
View
@@ -76,7 +76,10 @@ def send_web_service_error_response(request, exception)
unless self.class.web_service_exception_reporting
exception = DispatcherError.new("Internal server error (exception raised)")
end
- response = request.protocol.marshal_response(request.method_name, exception, exception.class)
+ 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)
send_web_service_response(response)
else
if self.class.web_service_exception_reporting
@@ -95,7 +98,7 @@ def web_service_direct_invoke(invocation)
end
@session ||= {}
@assigns ||= {}
- @params['action'] = invocation.api_method_name.to_s
+ @params['action'] = invocation.api_method.name.to_s
if before_action == false
raise(DispatcherError, "Method filtered")
end
@@ -224,18 +227,18 @@ def to_wsdl
# APIs
apis.each do |api_name, values|
api = values[0]
- api.api_methods.each do |name, info|
+ api.api_methods.each do |name, method|
gen = lambda do |msg_name, direction|
xm.message('name' => msg_name) do
sym = nil
if direction == :out
- returns = info[:returns]
+ returns = method.returns
if returns
binding = marshaler.register_type(returns[0])
xm.part('name' => 'return', 'type' => binding.qualified_type_name('typens'))
end
else
- expects = info[:expects]
+ expects = method.expects
i = 1
expects.each do |type|
if type.is_a?(Hash)
@@ -251,7 +254,7 @@ def to_wsdl
end
end
end
- public_name = api.public_api_method_name(name)
+ public_name = method.public_name
gen.call(public_name, :in)
gen.call("#{public_name}Response", :out)
end
@@ -259,11 +262,10 @@ def to_wsdl
# Port
port_name = port_name_for(global_service_name, api_name)
xm.portType('name' => port_name) do
- api.api_methods.each do |name, info|
- public_name = api.public_api_method_name(name)
- xm.operation('name' => public_name) do
- xm.input('message' => "typens:#{public_name}")
- xm.output('message' => "typens:#{public_name}Response")
+ api.api_methods.each do |name, method|
+ xm.operation('name' => method.public_name) do
+ xm.input('message' => "typens:#{method.public_name}")
+ xm.output('message' => "typens:#{method.public_name}Response")
end
end
end
@@ -272,16 +274,15 @@ def to_wsdl
binding_name = binding_name_for(global_service_name, api_name)
xm.binding('name' => binding_name, 'type' => "typens:#{port_name}") do
xm.soap(:binding, 'style' => 'rpc', 'transport' => SoapHttpTransport)
- api.api_methods.each do |name, info|
- public_name = api.public_api_method_name(name)
- xm.operation('name' => public_name) do
+ api.api_methods.each do |name, method|
+ xm.operation('name' => method.public_name) do
case web_service_dispatching_mode
when :direct, :layered
- soap_action = soap_action_base + "/api/" + public_name
+ soap_action = soap_action_base + "/api/" + method.public_name
when :delegated
soap_action = soap_action_base \
+ "/" + api_name.to_s \
- + "/" + public_name
+ + "/" + method.public_name
end
xm.soap(:operation, 'soapAction' => soap_action)
xm.input do
@@ -337,8 +338,8 @@ def register_api(api, marshaler)
end
def traverse_custom_types(api, marshaler, &block)
- api.api_methods.each do |name, info|
- expects, returns = info[:expects], info[:returns]
+ 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
end
16 actionwebservice/lib/action_web_service/protocol/abstract.rb
View
@@ -3,17 +3,31 @@ module Protocol # :nodoc:
class ProtocolError < ActionWebServiceError # :nodoc:
end
+ class AbstractProtocol
+ attr :marshaler
+ attr :encoder
+
+ def marshal_response(method, return_value)
+ body = method.encode_rpc_response(marshaler, encoder, return_value)
+ Response.new(body, 'text/xml')
+ 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)
+ 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
18 actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
View
@@ -6,7 +6,7 @@ def self.included(base)
base.class_inheritable_option(:wsdl_service_name)
end
- class SoapProtocol # :nodoc:
+ class SoapProtocol < AbstractProtocol # :nodoc:
def initialize
@encoder = WS::Encoding::SoapRpcEncoding.new 'urn:ActionWebService'
@marshaler = WS::Marshaling::SoapMarshaler.new 'urn:ActionWebService'
@@ -20,22 +20,6 @@ def unmarshal_request(ap_request)
Request.new(self, method_name, params, service_name)
end
- def marshal_response(method_name, return_value, signature_type)
- if !return_value.nil? && signature_type
- type_binding = @marshaler.register_type(signature_type)
- info = WS::ParamInfo.create(signature_type, type_binding, 0)
- return_value = @marshaler.marshal(WS::Param.new(return_value, info))
- else
- return_value = nil
- end
- body = @encoder.encode_rpc_response(method_name + 'Response', return_value)
- Response.new(body, 'text/xml')
- end
-
- def register_signature_type(spec)
- @marshaler.register_type(spec)
- end
-
def protocol_client(api, protocol_name, endpoint_uri, options)
return nil unless protocol_name == :soap
ActionWebService::Client::Soap.new(api, endpoint_uri, options)
20 actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
View
@@ -5,9 +5,7 @@ def self.included(base)
base.register_protocol(XmlRpcProtocol)
end
- class XmlRpcProtocol # :nodoc:
- attr :marshaler
-
+ class XmlRpcProtocol < AbstractProtocol # :nodoc:
def initialize
@encoder = WS::Encoding::XmlRpcEncoding.new
@marshaler = WS::Marshaling::XmlRpcMarshaler.new
@@ -22,22 +20,6 @@ def unmarshal_request(ap_request)
nil
end
- def marshal_response(method_name, return_value, signature_type)
- if !return_value.nil? && signature_type
- type_binding = @marshaler.register_type(signature_type)
- info = WS::ParamInfo.create(signature_type, type_binding, 0)
- return_value = @marshaler.marshal(WS::Param.new(return_value, info))
- else
- return_value = nil
- end
- body = @encoder.encode_rpc_response(method_name, return_value)
- Response.new(body, 'text/xml')
- end
-
- def register_signature_type(spec)
- nil
- end
-
def protocol_client(api, protocol_name, endpoint_uri, options)
return nil unless protocol_name == :xmlrpc
ActionWebService::Client::XmlRpc.new(api, endpoint_uri, options)
15 actionwebservice/lib/action_web_service/test_invoke.rb
View
@@ -52,18 +52,9 @@ def encode_rpc_call(service_name, api_method_name, *args)
when :delegated, :layered
api = @controller.web_service_object(service_name.to_sym).class.web_service_api
end
- info = api.api_methods[api_method_name.to_sym]
- ((info[:expects] || []) + (info[:returns] || [])).each do |spec|
- marshaler.register_type spec
- end
- expects = info[:expects]
- args = args.dup
- (0..(args.length-1)).each do |i|
- type_binding = marshaler.register_type(expects ? expects[i] : args[i].class)
- info = WS::ParamInfo.create(expects ? expects[i] : args[i].class, type_binding, i)
- args[i] = marshaler.marshal(WS::Param.new(args[i], info))
- end
- encoder.encode_rpc_call(public_method_name(service_name, api_method_name), args)
+ 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))
end
def decode_rpc_response
16 actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb
View
@@ -1,6 +1,10 @@
module WS
module Marshaling
class AbstractMarshaler
+ def initialize
+ @base_type_caster = BaseTypeCaster.new
+ end
+
def marshal(param)
raise NotImplementedError
end
@@ -12,6 +16,18 @@ def unmarshal(param)
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
22 actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb
View
@@ -13,6 +13,7 @@ class SoapMarshaler < AbstractMarshaler
attr_accessor :type_namespace
def initialize(type_namespace='')
+ super()
@type_namespace = type_namespace
@registry = SOAP::Mapping::Registry.new
@spec2binding = {}
@@ -92,6 +93,25 @@ def register_type(spec)
@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)
@@ -106,7 +126,7 @@ def annotate_arrays(binding, value)
if binding.type_class.respond_to?(:members)
binding.type_class.members.each do |name, spec|
member_binding = register_type(spec)
- member_value = value.send(name)
+ member_value = value.respond_to?('[]') ? value[name] : value.send(name)
if member_binding.is_custom_type?
annotate_arrays(member_binding, member_value)
end
113 actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb
View
@@ -5,24 +5,24 @@ class XmlRpcError < WSError
class XmlRpcMarshaler < AbstractMarshaler
def initialize
- @caster = BaseTypeCaster.new
+ super()
@spec2binding = {}
end
def marshal(param)
- transform_outbound(param)
+ value = param.value
+ cast_outbound_recursive(param.value, spec_for(param)) rescue value
end
def unmarshal(obj)
- obj.param.value = transform_inbound(obj.param)
obj.param
end
def typed_unmarshal(obj, spec)
- param = obj.param
- param.info.data = register_type(spec)
- param.value = transform_inbound(param)
- param
+ 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)
@@ -40,60 +40,87 @@ def register_type(spec)
@spec2binding[spec] = type_binding
end
+ alias :lookup_type :register_type
- def transform_outbound(param)
- binding = param.info.data
+ def cast_inbound_recursive(value, spec)
+ binding = lookup_type(spec)
case binding
when XmlRpcArrayBinding
- param.value.map{|x| cast_outbound(x, binding.element_klass)}
+ value.map{ |x| cast_inbound(x, binding.element_klass) }
when XmlRpcBinding
- cast_outbound(param.value, param.info.type)
+ cast_inbound(value, binding.klass)
end
end
- def transform_inbound(param)
- return param.value if param.info.data.nil?
- binding = param.info.data
- param.info.type = binding.klass
+ def cast_outbound_recursive(value, spec)
+ binding = lookup_type(spec)
case binding
when XmlRpcArrayBinding
- param.value.map{|x| cast_inbound(x, binding.element_klass)}
+ value.map{ |x| cast_outbound(x, binding.element_klass) }
when XmlRpcBinding
- cast_inbound(param.value, param.info.type)
+ cast_outbound(value, binding.klass)
end
end
- def cast_outbound(value, klass)
- if BaseTypes.base_type?(klass)
- @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
- else
- struct = {}
- value.instance_variables.each do |name|
- key = name.sub(/^@/, '')
- struct[key] = value.instance_variable_get(name)
+ 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
- struct
end
- end
- def cast_inbound(value, klass)
- if BaseTypes.base_type?(klass)
- value = value.to_time if value.is_a?(XMLRPC::DateTime)
- @caster.cast(value, klass)
- elsif value.is_a?(XMLRPC::FaultException)
- value
- else
- obj = klass.new
- value.each do |name, val|
- obj.send('%s=' % name.to_s, val)
+ 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
- obj
end
- end
end
class XmlRpcBinding
53 actionwebservice/test/abstract_dispatcher.rb
View
@@ -1,5 +1,7 @@
require File.dirname(__FILE__) + '/abstract_unit'
+class ActionController::Base; def rescue_action(e) raise e end; end
+
module DispatcherTest
class Node < ActiveRecord::Base
def initialize(*args)
@@ -29,6 +31,10 @@ def connection
class Person < ActionWebService::Struct
member :id, :int
member :name, :string
+
+ def ==(other)
+ self.id == other.id && self.name == other.name
+ end
end
class API < ActionWebService::API::Base
@@ -44,6 +50,7 @@ class DirectAPI < ActionWebService::API::Base
api_method :before_filtered
api_method :after_filtered, :returns => [[:int]]
api_method :struct_return, :returns => [[Node]]
+ api_method :struct_pass, :expects => [Person]
api_method :base_struct_return, :returns => [[Person]]
api_method :thrower
api_method :void
@@ -148,6 +155,7 @@ class DirectController < AbstractController
attr :after_filter_called
attr :after_filter_target_called
attr :void_called
+ attr :struct_pass_value
def initialize
@before_filter_called = false
@@ -155,6 +163,7 @@ def initialize
@after_filter_called = false
@after_filter_target_called = false
@void_called = false
+ @struct_pass_value = false
end
def add
@@ -184,6 +193,10 @@ def struct_return
[n1, n2]
end
+ def struct_pass(person)
+ @struct_pass_value = person
+ end
+
def base_struct_return
p1 = Person.new('id' => 1, 'name' => 'person1')
p2 = Person.new('id' => 2, 'name' => 'person2')
@@ -328,6 +341,26 @@ def test_ar_struct_return
end
end
+ def test_casting
+ assert_equal 70, do_method_call(@direct_controller, 'Add', "50", "20")
+ assert_equal false, @direct_controller.struct_pass_value
+ person = DispatcherTest::Person.new(:id => 1, :name => 'test')
+ result = do_method_call(@direct_controller, 'StructPass', person)
+ assert(nil == result || true == result)
+ 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
+ assert_equal(person, @direct_controller.struct_pass_value)
+ assert !person.equal?(@direct_controller.struct_pass_value)
+ end
+ end
+
protected
def service_name(container)
raise NotImplementedError
@@ -355,24 +388,20 @@ def do_method_call(container, public_method_name, *params)
end
api = container.web_service_object(service_name.to_sym).class.web_service_api
end
- info = api.api_methods[method_name] || {}
- params = params.dup
- ((info[:expects] || []) + (info[:returns] || [])).each do |spec|
- @marshaler.register_type(spec)
- end
- expects = info[:expects]
- (0..(params.length-1)).each do |i|
- type_binding = @marshaler.register_type(expects ? expects[i] : params[i].class)
- info = WS::ParamInfo.create(expects ? expects[i] : params[i].class, type_binding, i)
- params[i] = @marshaler.marshal(WS::Param.new(params[i], info))
- end
- body = @encoder.encode_rpc_call(public_method_name, params)
+ 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)
# puts body
ap_request = create_ap_request(container, body, public_method_name, *params)
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)
+ # http://dev.rubyonrails.com/changeset/920
+ assert_match(/Response$/, public_method_name) unless public_method_name == "fault"
+ end
@marshaler.unmarshal(return_value).value
end
end
21 actionwebservice/test/api_test.rb
View
@@ -35,13 +35,20 @@ def test_api_method_declaration
end
def test_signature_canonicalization
- assert_equal({:expects=>nil, :returns=>nil}, API.api_methods[:void])
- assert_equal({:expects=>[String], :returns=>[String]}, API.api_methods[:expects_and_returns])
- assert_equal({:expects=>[Integer, TrueClass], :returns=>nil}, API.api_methods[:expects])
- assert_equal({:expects=>nil, :returns=>[Integer, [String]]}, API.api_methods[:returns])
- assert_equal({:expects=>[{:appkey=>Integer}, {:publish=>TrueClass}], :returns=>nil}, API.api_methods[:named_signature])
- assert_equal({:expects=>[Integer, String, TrueClass], :returns=>nil}, API.api_methods[:string_types])
- assert_equal({:expects=>[TrueClass, Integer, String], :returns=>nil}, API.api_methods[:class_types])
+ 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(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(nil, API.api_methods[:named_signature].returns)
+ assert_equal([Integer, String, TrueClass], API.api_methods[:string_types].expects)
+ assert_equal(nil, API.api_methods[:string_types].returns)
+ assert_equal([TrueClass, Integer, String], API.api_methods[:class_types].expects)
+ assert_equal(nil, API.api_methods[:class_types].returns)
end
def test_not_instantiable
5 actionwebservice/test/client_soap_test.rb
View
@@ -66,6 +66,11 @@ def test_normal
assert(@container.value_normal.nil?)
assert_equal(5, @client.normal(5, 6))
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
end
def test_array_return
9 actionwebservice/test/client_xmlrpc_test.rb
View
@@ -60,6 +60,11 @@ def test_normal
assert(@container.value_normal.nil?)
assert_equal(5, @client.normal(5, 6))
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
end
def test_array_return
@@ -86,7 +91,7 @@ def test_client_container
def test_named_parameters
assert(@container.value_named_parameters.nil?)
- assert_equal(true, @client.named_parameters("xxx", 7))
+ assert_equal(nil, @client.named_parameters("xxx", 7))
assert_equal(["xxx", 7], @container.value_named_parameters)
end
@@ -97,7 +102,7 @@ def test_exception
end
def test_invalid_signature
- assert_raises(ActionWebService::Client::ClientError) do
+ assert_raises(ArgumentError) do
@client.normal
end
end
1  actionwebservice/test/run
View
@@ -1,5 +1,6 @@
#!/usr/bin/env ruby
require 'test/unit'
+$:.unshift(File.dirname(__FILE__) + '/../lib')
args = Dir[File.join(File.dirname(__FILE__), '*_test.rb')] + Dir[File.join(File.dirname(__FILE__), 'ws/*_test.rb')]
(r = Test::Unit::AutoRunner.new(true)).process_args(args)
exit r.run
6 actionwebservice/test/ws/soap_marshaling_test.rb
View
@@ -30,6 +30,12 @@ def test_abstract
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
Please sign in to comment.
Something went wrong with that request. Please try again.