aboutsummaryrefslogtreecommitdiffstats
path: root/actionwebservice/lib/action_web_service/casting.rb
blob: 4fb5a8db25637c52e854e1d2452a099ee69d6a3a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
require 'time'
require 'date'
require 'xmlrpc/datetime'

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 # :nodoc:
      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 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?
          api_method.expects.zip(params).map{ |type, param| cast(param, type) }
        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
          return nil if value.nil?
          # XMLRPC protocol doesn't support nil values. It uses false instead.
          # It should never happen for SOAP.
          if signature_type.structured? && value.equal?(false)
            return nil
          end
          unless signature_type.array? || signature_type.structured?
            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:
          # This is a work-around for the fact that XML-RPC special-cases DateTime values into its own DateTime type
          # in order to support iso8601 dates. This doesn't work too well for us, so we'll convert it into a Time,
          # with the caveat that we won't be able to handle pre-1970 dates that are sent to us.
          # 
          # See http://dev.rubyonrails.com/ticket/2516
          value = value.to_time if value.is_a?(XMLRPC::DateTime)

          case signature_type.type
          when :int
            Integer(value)
          when :string
            value.to_s
          when :base64
            if value.is_a?(ActionWebService::Base64)
              value
            else
              ActionWebService::Base64.new(value.to_s)
            end
          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
            value = "#{value['2']}/#{value['3']}/#{value['1']} #{value['4']}:#{value['5']}:#{value['6']}" if value.kind_of?(Hash)
            Time.parse(value.to_s)
          when :date
            value = "#{value['2']}/#{value['3']}/#{value['1']}" if value.kind_of?(Hash)
            Date.parse(value.to_s)
          when :datetime
            value = "#{value['2']}/#{value['3']}/#{value['1']} #{value['4']}:#{value['5']}:#{value['6']}" if value.kind_of?(Hash)
            DateTime.parse(value.to_s)
          end
        end

        def cast_to_structured_type(value, signature_type) # :nodoc:
          obj = nil
          obj = value if canonical_type(value.class) == canonical_type(signature_type.type)
          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
              # See http://dev.rubyonrails.com/ticket/3567
              val = val.to_time if val.is_a?(XMLRPC::DateTime)
              obj.__send__("#{name}=", val) if obj.respond_to?(name)
            end
          elsif value.respond_to?(:attributes)
            signature_type.each_member do |name, type|
              val = value.__send__(name)
              obj.__send__("#{name}=", cast(val, type)) if obj.respond_to?(name)
            end
          else
            raise CastingError, "Don't know how to cast #{value.class} to #{signature_type.type_class}"
          end
          obj
        end
      end
    end
  end
end