From aaea48fe9826b9e5d2d5b92795a297b8f238c58d Mon Sep 17 00:00:00 2001 From: Leon Breedt Date: Sat, 2 Apr 2005 21:03:36 +0000 Subject: * 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 --- actionwebservice/CHANGELOG | 6 +- actionwebservice/Rakefile | 2 +- actionwebservice/TODO | 5 - actionwebservice/lib/action_web_service.rb | 4 +- .../lib/action_web_service/api/base.rb | 146 ++--------- actionwebservice/lib/action_web_service/casting.rb | 105 ++++++++ .../lib/action_web_service/client/soap_client.rb | 20 +- .../lib/action_web_service/client/xmlrpc_client.rb | 10 +- .../lib/action_web_service/dispatcher/abstract.rb | 40 +-- .../dispatcher/action_controller_dispatcher.rb | 63 ++--- .../lib/action_web_service/protocol/abstract.rb | 93 ++++--- .../lib/action_web_service/protocol/discovery.rb | 4 +- .../action_web_service/protocol/soap_protocol.rb | 111 +++++++- .../protocol/soap_protocol/marshaler.rb | 197 ++++++++++++++ .../action_web_service/protocol/xmlrpc_protocol.rb | 55 +++- .../lib/action_web_service/scaffolding.rb | 43 +++- actionwebservice/lib/action_web_service/struct.rb | 15 +- .../action_web_service/support/signature_types.rb | 191 ++++++++++++++ .../templates/scaffolds/parameters.rhtml | 6 +- .../lib/action_web_service/test_invoke.rb | 37 +-- .../lib/action_web_service/vendor/ws.rb | 4 - .../lib/action_web_service/vendor/ws/common.rb | 8 - .../lib/action_web_service/vendor/ws/encoding.rb | 3 - .../vendor/ws/encoding/abstract.rb | 26 -- .../vendor/ws/encoding/soap_rpc_encoding.rb | 90 ------- .../vendor/ws/encoding/xmlrpc_encoding.rb | 44 ---- .../lib/action_web_service/vendor/ws/marshaling.rb | 3 - .../vendor/ws/marshaling/abstract.rb | 33 --- .../vendor/ws/marshaling/soap_marshaling.rb | 283 --------------------- .../vendor/ws/marshaling/xmlrpc_marshaling.rb | 143 ----------- .../lib/action_web_service/vendor/ws/types.rb | 165 ------------ actionwebservice/test/abstract_dispatcher.rb | 96 +++---- actionwebservice/test/abstract_unit.rb | 2 +- actionwebservice/test/api_test.rb | 16 +- actionwebservice/test/casting_test.rb | 82 ++++++ actionwebservice/test/client_soap_test.rb | 4 +- actionwebservice/test/client_xmlrpc_test.rb | 7 +- .../test/dispatcher_action_controller_soap_test.rb | 2 - .../dispatcher_action_controller_xmlrpc_test.rb | 2 - .../test/scaffolded_controller_test.rb | 67 +++++ actionwebservice/test/struct_test.rb | 56 ++-- actionwebservice/test/test_invoke_test.rb | 2 +- actionwebservice/test/ws/abstract_encoding.rb | 68 ----- actionwebservice/test/ws/abstract_unit.rb | 13 - actionwebservice/test/ws/gencov | 3 - actionwebservice/test/ws/run | 5 - actionwebservice/test/ws/soap_marshaling_test.rb | 97 ------- actionwebservice/test/ws/soap_rpc_encoding_test.rb | 47 ---- actionwebservice/test/ws/types_test.rb | 43 ---- actionwebservice/test/ws/xmlrpc_encoding_test.rb | 34 --- 50 files changed, 1090 insertions(+), 1511 deletions(-) create mode 100644 actionwebservice/lib/action_web_service/casting.rb create mode 100644 actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb create mode 100644 actionwebservice/lib/action_web_service/support/signature_types.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/common.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/encoding.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb delete mode 100644 actionwebservice/lib/action_web_service/vendor/ws/types.rb create mode 100644 actionwebservice/test/casting_test.rb create mode 100644 actionwebservice/test/scaffolded_controller_test.rb delete mode 100644 actionwebservice/test/ws/abstract_encoding.rb delete mode 100644 actionwebservice/test/ws/abstract_unit.rb delete mode 100755 actionwebservice/test/ws/gencov delete mode 100755 actionwebservice/test/ws/run delete mode 100644 actionwebservice/test/ws/soap_marshaling_test.rb delete mode 100644 actionwebservice/test/ws/soap_rpc_encoding_test.rb delete mode 100644 actionwebservice/test/ws/types_test.rb delete mode 100644 actionwebservice/test/ws/xmlrpc_encoding_test.rb (limited to 'actionwebservice') diff --git a/actionwebservice/CHANGELOG b/actionwebservice/CHANGELOG index fc50e0a7b9..ae94240cc3 100644 --- a/actionwebservice/CHANGELOG +++ b/actionwebservice/CHANGELOG @@ -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 diff --git a/actionwebservice/Rakefile b/actionwebservice/Rakefile index a54d72069f..57940f6b9f 100644 --- a/actionwebservice/Rakefile +++ b/actionwebservice/Rakefile @@ -264,4 +264,4 @@ task :release => [:package] do first_file = false end end -end \ No newline at end of file +end diff --git a/actionwebservice/TODO b/actionwebservice/TODO index cf5c11a795..0ec3b52166 100644 --- a/actionwebservice/TODO +++ b/actionwebservice/TODO @@ -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 diff --git a/actionwebservice/lib/action_web_service.rb b/actionwebservice/lib/action_web_service.rb index d8dc132313..82be41a327 100644 --- a/actionwebservice/lib/action_web_service.rb +++ b/actionwebservice/lib/action_web_service.rb @@ -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' diff --git a/actionwebservice/lib/action_web_service/api/base.rb b/actionwebservice/lib/action_web_service/api/base.rb index 03e406cfc3..c9fb9f967f 100644 --- a/actionwebservice/lib/action_web_service/api/base.rb +++ b/actionwebservice/lib/action_web_service/api/base.rb @@ -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) } - 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 + @expects.map{ |type| type.name } end - # Encodes an RPC call for this method. Casting is performed if - # the :strict 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 :strict 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 diff --git a/actionwebservice/lib/action_web_service/casting.rb b/actionwebservice/lib/action_web_service/casting.rb new file mode 100644 index 0000000000..ce90c463d8 --- /dev/null +++ b/actionwebservice/lib/action_web_service/casting.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/client/soap_client.rb b/actionwebservice/lib/action_web_service/client/soap_client.rb index b9eb0d11ad..c906a71331 100644 --- a/actionwebservice/lib/action_web_service/client/soap_client.rb +++ b/actionwebservice/lib/action_web_service/client/soap_client.rb @@ -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) diff --git a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb index e0b7efc864..42b5c5d4f9 100644 --- a/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb +++ b/actionwebservice/lib/action_web_service/client/xmlrpc_client.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb index 69c3b9de3b..975120212f 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/abstract.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/abstract.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb index 0140039c49..a4659e5183 100644 --- a/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb +++ b/actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/protocol/abstract.rb b/actionwebservice/lib/action_web_service/protocol/abstract.rb index 0ff4feef84..70b922ce73 100644 --- a/actionwebservice/lib/action_web_service/protocol/abstract.rb +++ b/actionwebservice/lib/action_web_service/protocol/abstract.rb @@ -3,22 +3,11 @@ module ActionWebService # :nodoc: class ProtocolError < ActionWebServiceError # :nodoc: end - class AbstractProtocol - attr :marshaler - attr :encoder - - def unmarshal_request(ap_request) - end - - def marshal_response(method, return_value) - body = method.encode_rpc_response(marshaler, encoder, return_value) - Response.new(body, 'text/xml') + class AbstractProtocol # :nodoc: + def decode_action_pack_request(action_pack_request) 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,50 +16,30 @@ module ActionWebService # :nodoc: request.env['HTTP_CONTENT_TYPE'] = 'text/xml' request end - end - class SimpleActionPackRequest < ActionController::AbstractRequest - def initialize - @env = {} - @qparams = {} - @rparams = {} - @cookies = {} - reset_session + def decode_request(raw_request, service_name) end - def query_parameters - @qparams + def encode_request(method_name, params, param_types) end - def request_parameters - @rparams + def decode_response(raw_response) end - def env - @env - end - - def host - '' - end - - def cookies - @cookies + def encode_response(method_name, return_value, return_type) end - def session - @session + def protocol_client(api, protocol_name, endpoint_uri, options) end - def reset_session - @session = {} + def register_api(api) end end class Request # :nodoc: attr :protocol attr :method_name - attr :method_params + attr_accessor :method_params attr :service_name attr_accessor :api attr_accessor :api_method @@ -88,10 +57,50 @@ module ActionWebService # :nodoc: class Response # :nodoc: attr :body attr :content_type + attr :return_value - def initialize(body, content_type) + 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 = {} + @rparams = {} + @cookies = {} + reset_session + end + + def query_parameters + @qparams + end + + def request_parameters + @rparams + end + + def env + @env + end + + def host + '' + end + + def cookies + @cookies + end + + def session + @session + end + + def reset_session + @session = {} end end end diff --git a/actionwebservice/lib/action_web_service/protocol/discovery.rb b/actionwebservice/lib/action_web_service/protocol/discovery.rb index 40875975bf..a911c7d017 100644 --- a/actionwebservice/lib/action_web_service/protocol/discovery.rb +++ b/actionwebservice/lib/action_web_service/protocol/discovery.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb index 5e56748ae3..cc3e90a4cc 100644 --- a/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol.rb @@ -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 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 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_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 diff --git a/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb new file mode 100644 index 0000000000..5d9a80b007 --- /dev/null +++ b/actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb index f8ff12cfa3..de6c8c8a30 100644 --- a/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb +++ b/actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/scaffolding.rb b/actionwebservice/lib/action_web_service/scaffolding.rb index e311515fba..0fab01eced 100644 --- a/actionwebservice/lib/action_web_service/scaffolding.rb +++ b/actionwebservice/lib/action_web_service/scaffolding.rb @@ -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| diff --git a/actionwebservice/lib/action_web_service/struct.rb b/actionwebservice/lib/action_web_service/struct.rb index d4e2ba9ce6..c5e6346bfa 100644 --- a/actionwebservice/lib/action_web_service/struct.rb +++ b/actionwebservice/lib/action_web_service/struct.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/support/signature_types.rb b/actionwebservice/lib/action_web_service/support/signature_types.rb new file mode 100644 index 0000000000..5c57254bc3 --- /dev/null +++ b/actionwebservice/lib/action_web_service/support/signature_types.rb @@ -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 diff --git a/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml b/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml index 0516738da6..e62d234c1a 100644 --- a/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml +++ b/actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml @@ -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| %>

-
- <%= method_parameter_input_fields(@scaffold_method, spec, i) %> +
+ <%= method_parameter_input_fields(@scaffold_method, type) %>

<% i += 1 %> <% end %> diff --git a/actionwebservice/lib/action_web_service/test_invoke.rb b/actionwebservice/lib/action_web_service/test_invoke.rb index 5d364e4225..c22ca618dc 100644 --- a/actionwebservice/lib/action_web_service/test_invoke.rb +++ b/actionwebservice/lib/action_web_service/test_invoke.rb @@ -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) diff --git a/actionwebservice/lib/action_web_service/vendor/ws.rb b/actionwebservice/lib/action_web_service/vendor/ws.rb deleted file mode 100644 index 18a32a555e..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'ws/common' -require 'ws/types' -require 'ws/marshaling' -require 'ws/encoding' diff --git a/actionwebservice/lib/action_web_service/vendor/ws/common.rb b/actionwebservice/lib/action_web_service/vendor/ws/common.rb deleted file mode 100644 index 4266a7141d..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/common.rb +++ /dev/null @@ -1,8 +0,0 @@ -module WS - class WSError < StandardError - end - - def self.derived_from?(ancestor, child) - child.ancestors.include?(ancestor) - end -end diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb deleted file mode 100644 index 790317639b..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'ws/encoding/abstract' -require 'ws/encoding/soap_rpc_encoding' -require 'ws/encoding/xmlrpc_encoding' diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb deleted file mode 100644 index 257c7d0993..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb deleted file mode 100644 index f4d2f5a7d6..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb b/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb deleted file mode 100644 index 4876c08705..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb deleted file mode 100644 index 3a0a2e8cc1..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb +++ /dev/null @@ -1,3 +0,0 @@ -require 'ws/marshaling/abstract' -require 'ws/marshaling/soap_marshaling' -require 'ws/marshaling/xmlrpc_marshaling' diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb deleted file mode 100644 index e897f62297..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb deleted file mode 100644 index 14c8d8401d..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb b/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb deleted file mode 100644 index 56cc7597fb..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/lib/action_web_service/vendor/ws/types.rb b/actionwebservice/lib/action_web_service/vendor/ws/types.rb deleted file mode 100644 index 88098b5bce..0000000000 --- a/actionwebservice/lib/action_web_service/vendor/ws/types.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/test/abstract_dispatcher.rb b/actionwebservice/test/abstract_dispatcher.rb index 53be05f9f2..4784180518 100644 --- a/actionwebservice/test/abstract_dispatcher.rb +++ b/actionwebservice/test/abstract_dispatcher.rb @@ -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 diff --git a/actionwebservice/test/abstract_unit.rb b/actionwebservice/test/abstract_unit.rb index e8304e3790..cbeda66e90 100644 --- a/actionwebservice/test/abstract_unit.rb +++ b/actionwebservice/test/abstract_unit.rb @@ -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' diff --git a/actionwebservice/test/api_test.rb b/actionwebservice/test/api_test.rb index 4a61ae8de7..f6d73866b7 100644 --- a/actionwebservice/test/api_test.rb +++ b/actionwebservice/test/api_test.rb @@ -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 diff --git a/actionwebservice/test/casting_test.rb b/actionwebservice/test/casting_test.rb new file mode 100644 index 0000000000..223313564b --- /dev/null +++ b/actionwebservice/test/casting_test.rb @@ -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 diff --git a/actionwebservice/test/client_soap_test.rb b/actionwebservice/test/client_soap_test.rb index 941a642554..e118b4956e 100644 --- a/actionwebservice/test/client_soap_test.rb +++ b/actionwebservice/test/client_soap_test.rb @@ -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 diff --git a/actionwebservice/test/client_xmlrpc_test.rb b/actionwebservice/test/client_xmlrpc_test.rb index 3301113d95..de24d9a975 100644 --- a/actionwebservice/test/client_xmlrpc_test.rb +++ b/actionwebservice/test/client_xmlrpc_test.rb @@ -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 diff --git a/actionwebservice/test/dispatcher_action_controller_soap_test.rb b/actionwebservice/test/dispatcher_action_controller_soap_test.rb index 76fc6094c2..3d7327be02 100644 --- a/actionwebservice/test/dispatcher_action_controller_soap_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_soap_test.rb @@ -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 diff --git a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb index c92f270a1b..f1dc992818 100644 --- a/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb +++ b/actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb @@ -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 diff --git a/actionwebservice/test/scaffolded_controller_test.rb b/actionwebservice/test/scaffolded_controller_test.rb new file mode 100644 index 0000000000..2fa3a6030c --- /dev/null +++ b/actionwebservice/test/scaffolded_controller_test.rb @@ -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 diff --git a/actionwebservice/test/struct_test.rb b/actionwebservice/test/struct_test.rb index e6f1603c73..838cc2569c 100644 --- a/actionwebservice/test/struct_test.rb +++ b/actionwebservice/test/struct_test.rb @@ -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 diff --git a/actionwebservice/test/test_invoke_test.rb b/actionwebservice/test/test_invoke_test.rb index cbfde9c3df..fb992472f4 100644 --- a/actionwebservice/test/test_invoke_test.rb +++ b/actionwebservice/test/test_invoke_test.rb @@ -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 diff --git a/actionwebservice/test/ws/abstract_encoding.rb b/actionwebservice/test/ws/abstract_encoding.rb deleted file mode 100644 index 6032d94c48..0000000000 --- a/actionwebservice/test/ws/abstract_encoding.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/test/ws/abstract_unit.rb b/actionwebservice/test/ws/abstract_unit.rb deleted file mode 100644 index 5d4f5ce856..0000000000 --- a/actionwebservice/test/ws/abstract_unit.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/test/ws/gencov b/actionwebservice/test/ws/gencov deleted file mode 100755 index 144233107a..0000000000 --- a/actionwebservice/test/ws/gencov +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -rcov -x '.*_test\.rb,rubygems,abstract_,/run' ./run diff --git a/actionwebservice/test/ws/run b/actionwebservice/test/ws/run deleted file mode 100755 index 5c6f8b2bc2..0000000000 --- a/actionwebservice/test/ws/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env ruby - -Dir[File.join(File.dirname(__FILE__), '*_test.rb')].each do |f| - require f -end diff --git a/actionwebservice/test/ws/soap_marshaling_test.rb b/actionwebservice/test/ws/soap_marshaling_test.rb deleted file mode 100644 index f350ad1124..0000000000 --- a/actionwebservice/test/ws/soap_marshaling_test.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/test/ws/soap_rpc_encoding_test.rb b/actionwebservice/test/ws/soap_rpc_encoding_test.rb deleted file mode 100644 index e5dcbfeb1b..0000000000 --- a/actionwebservice/test/ws/soap_rpc_encoding_test.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/test/ws/types_test.rb b/actionwebservice/test/ws/types_test.rb deleted file mode 100644 index e66ae65945..0000000000 --- a/actionwebservice/test/ws/types_test.rb +++ /dev/null @@ -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 diff --git a/actionwebservice/test/ws/xmlrpc_encoding_test.rb b/actionwebservice/test/ws/xmlrpc_encoding_test.rb deleted file mode 100644 index 45aaa901bd..0000000000 --- a/actionwebservice/test/ws/xmlrpc_encoding_test.rb +++ /dev/null @@ -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 -- cgit v1.2.3