Permalink
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...
leonbreedt committed Mar 28, 2005
1 parent 439a216 commit 594063f23cf8e7cecd24329e801992784f420b55
@@ -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.
@@ -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
@@ -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)
@@ -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
Oops, something went wrong.

0 comments on commit 594063f

Please sign in to comment.