Permalink
Browse files

* collapse 'ws' back into protocols, it just added complexity and ind…

…irection, 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
  • Loading branch information...
leonbreedt committed Apr 2, 2005
1 parent aa09c77 commit aaea48fe9826b9e5d2d5b92795a297b8f238c58d
Showing with 1,090 additions and 1,511 deletions.
  1. +4 −2 actionwebservice/CHANGELOG
  2. +1 −1 actionwebservice/Rakefile
  3. +0 −5 actionwebservice/TODO
  4. +2 −2 actionwebservice/lib/action_web_service.rb
  5. +18 −128 actionwebservice/lib/action_web_service/api/base.rb
  6. +105 −0 actionwebservice/lib/action_web_service/casting.rb
  7. +9 −11 actionwebservice/lib/action_web_service/client/soap_client.rb
  8. +6 −4 actionwebservice/lib/action_web_service/client/xmlrpc_client.rb
  9. +21 −19 actionwebservice/lib/action_web_service/dispatcher/abstract.rb
  10. +32 −31 actionwebservice/lib/action_web_service/dispatcher/action_controller_dispatcher.rb
  11. +51 −42 actionwebservice/lib/action_web_service/protocol/abstract.rb
  12. +2 −2 actionwebservice/lib/action_web_service/protocol/discovery.rb
  13. +98 −13 actionwebservice/lib/action_web_service/protocol/soap_protocol.rb
  14. +197 −0 actionwebservice/lib/action_web_service/protocol/soap_protocol/marshaler.rb
  15. +48 −7 actionwebservice/lib/action_web_service/protocol/xmlrpc_protocol.rb
  16. +32 −11 actionwebservice/lib/action_web_service/scaffolding.rb
  17. +14 −1 actionwebservice/lib/action_web_service/struct.rb
  18. +191 −0 actionwebservice/lib/action_web_service/support/signature_types.rb
  19. +3 −3 actionwebservice/lib/action_web_service/templates/scaffolds/parameters.rhtml
  20. +9 −28 actionwebservice/lib/action_web_service/test_invoke.rb
  21. +0 −4 actionwebservice/lib/action_web_service/vendor/ws.rb
  22. +0 −8 actionwebservice/lib/action_web_service/vendor/ws/common.rb
  23. +0 −3 actionwebservice/lib/action_web_service/vendor/ws/encoding.rb
  24. +0 −26 actionwebservice/lib/action_web_service/vendor/ws/encoding/abstract.rb
  25. +0 −90 actionwebservice/lib/action_web_service/vendor/ws/encoding/soap_rpc_encoding.rb
  26. +0 −44 actionwebservice/lib/action_web_service/vendor/ws/encoding/xmlrpc_encoding.rb
  27. +0 −3 actionwebservice/lib/action_web_service/vendor/ws/marshaling.rb
  28. +0 −33 actionwebservice/lib/action_web_service/vendor/ws/marshaling/abstract.rb
  29. +0 −283 actionwebservice/lib/action_web_service/vendor/ws/marshaling/soap_marshaling.rb
  30. +0 −143 actionwebservice/lib/action_web_service/vendor/ws/marshaling/xmlrpc_marshaling.rb
  31. +0 −165 actionwebservice/lib/action_web_service/vendor/ws/types.rb
  32. +49 −47 actionwebservice/test/abstract_dispatcher.rb
  33. +1 −1 actionwebservice/test/abstract_unit.rb
  34. +8 −8 actionwebservice/test/api_test.rb
  35. +82 −0 actionwebservice/test/casting_test.rb
  36. +1 −3 actionwebservice/test/client_soap_test.rb
  37. +3 −4 actionwebservice/test/client_xmlrpc_test.rb
  38. +0 −2 actionwebservice/test/dispatcher_action_controller_soap_test.rb
  39. +0 −2 actionwebservice/test/dispatcher_action_controller_xmlrpc_test.rb
  40. +67 −0 actionwebservice/test/scaffolded_controller_test.rb
  41. +35 −21 actionwebservice/test/struct_test.rb
  42. +1 −1 actionwebservice/test/test_invoke_test.rb
  43. +0 −68 actionwebservice/test/ws/abstract_encoding.rb
  44. +0 −13 actionwebservice/test/ws/abstract_unit.rb
  45. +0 −3 actionwebservice/test/ws/gencov
  46. +0 −5 actionwebservice/test/ws/run
  47. +0 −97 actionwebservice/test/ws/soap_marshaling_test.rb
  48. +0 −47 actionwebservice/test/ws/soap_rpc_encoding_test.rb
  49. +0 −43 actionwebservice/test/ws/types_test.rb
  50. +0 −34 actionwebservice/test/ws/xmlrpc_encoding_test.rb
@@ -1,11 +1,13 @@
*0.7.0* (Unreleased)
-* Remove ActiveRecordSoapMarshallable workaround, see #912 for details
-
* Add scaffolding via ActionController::Base.web_service_scaffold for quick testing using a web browser
+* Remove ActiveRecordSoapMarshallable workaround, see #912 for details
+
* Generalize casting code to be used by both SOAP and XML-RPC (previously, it was only XML-RPC)
+* Ensure return value is properly cast as well, fixes XML-RPC interoperability with Ecto and possibly other clients
+
* Include backtraces in 500 error responses for failed request parsing, and remove "rescue nil" statements obscuring real errors for XML-RPC
@@ -264,4 +264,4 @@ task :release => [:package] do
first_file = false
end
end
-end
+end
View
@@ -2,14 +2,9 @@
- WS Dynamic Scaffolding
* add protocol selection ability
* test with XML-RPC (namespaced method name support)
- * support structured types as input parameters with the input field helper
- update manual for scaffolding and functional testing
-
-= 0.8.0
- - Consumption of WSDL services
= Refactoring
- - Port dispatcher tests to use test_invoke
- Don't have clean way to go from SOAP Class object to the xsd:NAME type
string -- NaHi possibly looking at remedying this situation
@@ -35,12 +35,12 @@
$:.unshift(File.dirname(__FILE__) + "/action_web_service/vendor/")
require 'action_web_service/support/class_inheritable_options'
-require 'action_web_service/vendor/ws'
-
+require 'action_web_service/support/signature_types'
require 'action_web_service/base'
require 'action_web_service/client'
require 'action_web_service/invocation'
require 'action_web_service/api'
+require 'action_web_service/casting'
require 'action_web_service/struct'
require 'action_web_service/container'
require 'action_web_service/protocol'
@@ -1,8 +1,5 @@
module ActionWebService # :nodoc:
module API # :nodoc:
- class CastingError < ActionWebService::ActionWebServiceError
- end
-
# A web service API class specifies the methods that will be available for
# invocation for an API. It also contains metadata such as the method type
# signature hints.
@@ -31,6 +28,8 @@ class Base
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 @@ def api_method(name, options={})
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 @@ def default_api_method_instance
instance
end
- # Creates a dummy API Method instance for the given public method name
- def dummy_public_api_method_instance(public_method_name)
- Method.new(public_method_name.underscore.to_sym, public_method_name, nil, nil)
- end
-
- # Creates a dummy API Method instance for the given method name
- def dummy_api_method_instance(method_name)
- Method.new(method_name, public_api_method_name(method_name), nil, nil)
- end
-
private
def api_public_method_names
read_inheritable_attribute("api_public_method_names") || {}
@@ -159,11 +147,6 @@ def validate_options(valid_option_keys, supplied_option_keys)
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 @@ def initialize(name, public_name, expects, returns)
@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 <tt>:strict</tt> option is given.
- def encode_rpc_call(marshaler, encoder, params, options={})
- name = options[:method_name] || @public_name
- expects = @expects || []
- returns = @returns || []
- (expects + returns).each { |spec| marshaler.register_type spec }
- (0..(params.length-1)).each do |i|
- spec = expects[i] || params[i].class
- type_binding = marshaler.lookup_type(spec)
- param_info = WS::ParamInfo.create(spec, type_binding, i)
- if options[:strict]
- value = marshaler.cast_outbound_recursive(params[i], spec)
- else
- value = params[i]
- end
- param = WS::Param.new(value, param_info)
- params[i] = marshaler.marshal(param)
- end
- encoder.encode_rpc_call(name, params)
- end
-
- # Encodes an RPC response for this method. Casting is performed if
- # the <tt>:strict</tt> option is given.
- def encode_rpc_response(marshaler, encoder, return_value, options={})
- if !return_value.nil? && @returns
- return_type = @returns[0]
- type_binding = marshaler.register_type(return_type)
- param_info = WS::ParamInfo.create(return_type, type_binding, 0)
- if options[:strict]
- return_value = marshaler.cast_inbound_recursive(return_value, return_type)
- end
- return_value = marshaler.marshal(WS::Param.new(return_value, param_info))
- else
- return_value = nil
- end
- encoder.encode_rpc_response(response_name(encoder), return_value)
- end
-
- # Casts a set of WS::Param values into the appropriate
- # Ruby values
- def cast_expects_ws2ruby(marshaler, params)
- return [] if @expects.nil?
- i = 0
- @expects.map do |spec|
- value = marshaler.cast_inbound_recursive(params[i].value, spec)
- i += 1
- value
- end
- end
-
# Casts a set of Ruby values into the expected Ruby values
- def cast_expects(marshaler, params)
- return [] if @expects.nil?
- i = 0
- @expects.map do |spec|
- value = marshaler.cast_outbound_recursive(params[i], spec)
- i += 1
- value
- end
+ def cast_expects(params)
+ @caster.cast_expects(params)
end
# Cast a Ruby return value into the expected Ruby value
- def cast_returns(marshaler, return_value)
- return nil if @returns.nil?
- marshaler.cast_inbound_recursive(return_value, @returns[0])
+ def cast_returns(return_value)
+ @caster.cast_returns(return_value)
end
# String representation of this method
def to_s
fqn = ""
- fqn << (@returns ? (friendly_param(@returns[0], nil) + " ") : "void ")
+ fqn << (@returns ? (friendly_param(@returns[0], false) + " ") : "void ")
fqn << "#{@public_name}("
- if @expects
- i = 0
- fqn << @expects.map{ |p| friendly_param(p, i+= 1) }.join(", ")
- end
+ fqn << @expects.map{ |p| friendly_param(p) }.join(", ") if @expects
fqn << ")"
fqn
end
private
- def response_name(encoder)
- encoder.is_a?(WS::Encoding::SoapRpcEncoding) ? (@public_name + "Response") : @public_name
- end
-
- def friendly_param(spec, i)
- name = param_name(spec, i)
- type = param_type(spec)
- spec = spec.values.first if spec.is_a?(Hash)
- type = spec.is_a?(Array) ? (type.to_s + "[]") : type.to_s
- i ? (type + " " + name) : type
+ def friendly_param(type, show_name=true)
+ name = type.name.to_s
+ type_type = type.type.to_s
+ str = type.array?? (type_type + '[]') : type_type
+ show_name ? (str + " " + name) : str
end
end
end
@@ -0,0 +1,105 @@
+require 'time'
+require 'date'
+require 'generator'
+
+module ActionWebService # :nodoc:
+ module Casting # :nodoc:
+ class CastingError < ActionWebServiceError # :nodoc:
+ end
+
+ # Performs casting of arbitrary values into the correct types for the signature
+ class BaseCaster
+ def initialize(api_method)
+ @api_method = api_method
+ end
+
+ # Coerces the parameters in +params+ (an Enumerable) into the types
+ # this method expects
+ def cast_expects(params)
+ self.class.cast_expects(@api_method, params)
+ end
+
+ # Coerces the given +return_value+ into the the type returned by this
+ # method
+ def cast_returns(return_value)
+ self.class.cast_returns(@api_method, return_value)
+ end
+
+ class << self
+ include ActionWebService::SignatureTypes
+
+ def cast_expects(api_method, params) # :nodoc:
+ return [] if api_method.expects.nil?
+ SyncEnumerator.new(params, api_method.expects).map{ |r| cast(r[0], r[1]) }
+ end
+
+ def cast_returns(api_method, return_value) # :nodoc:
+ return nil if api_method.returns.nil?
+ cast(return_value, api_method.returns[0])
+ end
+
+ def cast(value, signature_type) # :nodoc:
+ return value if signature_type.nil? # signature.length != params.length
+ unless signature_type.array?
+ return value if canonical_type(value.class) == signature_type.type
+ end
+ if signature_type.array?
+ unless value.respond_to?(:entries) && !value.is_a?(String)
+ raise CastingError, "Don't know how to cast #{value.class} into #{signature_type.type.inspect}"
+ end
+ value.entries.map do |entry|
+ cast(entry, signature_type.element_type)
+ end
+ elsif signature_type.structured?
+ cast_to_structured_type(value, signature_type)
+ elsif !signature_type.custom?
+ cast_base_type(value, signature_type)
+ end
+ end
+
+ def cast_base_type(value, signature_type) # :nodoc:
+ case signature_type.type
+ when :int
+ Integer(value)
+ when :string
+ value.to_s
+ when :bool
+ return false if value.nil?
+ return value if value == true || value == false
+ case value.to_s.downcase
+ when '1', 'true', 'y', 'yes'
+ true
+ when '0', 'false', 'n', 'no'
+ false
+ else
+ raise CastingError, "Don't know how to cast #{value.class} into Boolean"
+ end
+ when :float
+ Float(value)
+ when :time
+ Time.parse(value.to_s)
+ when :date
+ Date.parse(value.to_s)
+ when :datetime
+ DateTime.parse(value.to_s)
+ end
+ end
+
+ def cast_to_structured_type(value, signature_type) # :nodoc:
+ obj = signature_type.type_class.new
+ if value.respond_to?(:each_pair)
+ klass = signature_type.type_class
+ value.each_pair do |name, val|
+ type = klass.respond_to?(:member_type) ? klass.member_type(name) : nil
+ val = cast(val, type) if type
+ obj.send("#{name}=", val)
+ end
+ else
+ raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
+ end
+ obj
+ end
+ end
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit aaea48f

Please sign in to comment.