Went through the specs and updated them to reflect the application
[lyrix.git] / vendor / rails / actionwebservice / lib / action_web_service / api.rb
blob73fb886e6d7ec361b9267e7ed99ebbfd730fb537
1 module ActionWebService # :nodoc:
2   module API # :nodoc:
3     # A web service API class specifies the methods that will be available for
4     # invocation for an API. It also contains metadata such as the method type
5     # signature hints.
6     #
7     # It is not intended to be instantiated.
8     #
9     # It is attached to web service implementation classes like
10     # ActionWebService::Base and ActionController::Base derivatives by using
11     # <tt>container.web_service_api</tt>, where <tt>container</tt> is an
12     # ActionController::Base or a ActionWebService::Base.
13     #
14     # See ActionWebService::Container::Direct::ClassMethods for an example
15     # of use.
16     class Base
17       # Action WebService API subclasses should be reloaded by the dispatcher in Rails
18       # when Dependencies.mechanism = :load.
19       include Reloadable::Deprecated
20       
21       # Whether to transform the public API method names into camel-cased names 
22       class_inheritable_option :inflect_names, true
24       # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>.
25       # The default is +false+; you should be aware of the security implications
26       # of allowing this, and ensure that you don't allow remote callers to
27       # easily overwrite data they should not have access to.
28       class_inheritable_option :allow_active_record_expects, false
30       # If present, the name of a method to call when the remote caller
31       # tried to call a nonexistent method. Semantically equivalent to
32       # +method_missing+.
33       class_inheritable_option :default_api_method
35       # Disallow instantiation
36       private_class_method :new, :allocate
37       
38       class << self
39         include ActionWebService::SignatureTypes
41         # API methods have a +name+, which must be the Ruby method name to use when
42         # performing the invocation on the web service object.
43         #
44         # The signatures for the method input parameters and return value can
45         # by specified in +options+.
46         #
47         # A signature is an array of one or more parameter specifiers. 
48         # A parameter specifier can be one of the following:
49         #
50         # * A symbol or string representing one of the Action Web Service base types.
51         #   See ActionWebService::SignatureTypes for a canonical list of the base types.
52         # * The Class object of the parameter type
53         # * A single-element Array containing one of the two preceding items. This
54         #   will cause Action Web Service to treat the parameter at that position
55         #   as an array containing only values of the given type.
56         # * A Hash containing as key the name of the parameter, and as value
57         #   one of the three preceding items
58         # 
59         # If no method input parameter or method return value signatures are given,
60         # the method is assumed to take no parameters and/or return no values of
61         # interest, and any values that are received by the server will be
62         # discarded and ignored.
63         #
64         # Valid options:
65         # [<tt>:expects</tt>]             Signature for the method input parameters
66         # [<tt>:returns</tt>]             Signature for the method return value
67         # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
68         def api_method(name, options={})
69           unless options.is_a?(Hash)
70             raise(ActionWebServiceError, "Expected a Hash for options")
71           end
72           validate_options([:expects, :returns, :expects_and_returns], options.keys)
73           if options[:expects_and_returns]
74             expects = options[:expects_and_returns]
75             returns = options[:expects_and_returns]
76           else
77             expects = options[:expects]
78             returns = options[:returns]
79           end
80           expects = canonical_signature(expects)
81           returns = canonical_signature(returns)
82           if expects
83             expects.each do |type|
84               type = type.element_type if type.is_a?(ArrayType)
85               if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
86                 raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
87               end
88             end
89           end
90           name = name.to_sym
91           public_name = public_api_method_name(name)
92           method = Method.new(name, public_name, expects, returns)
93           write_inheritable_hash("api_methods", name => method)
94           write_inheritable_hash("api_public_method_names", public_name => name)
95         end
97         # Whether the given method name is a service method on this API
98         def has_api_method?(name)
99           api_methods.has_key?(name)
100         end
101   
102         # Whether the given public method name has a corresponding service method
103         # on this API
104         def has_public_api_method?(public_name)
105           api_public_method_names.has_key?(public_name)
106         end
107   
108         # The corresponding public method name for the given service method name
109         def public_api_method_name(name)
110           if inflect_names
111             name.to_s.camelize
112           else
113             name.to_s
114           end
115         end
116   
117         # The corresponding service method name for the given public method name
118         def api_method_name(public_name)
119           api_public_method_names[public_name]
120         end
121   
122         # A Hash containing all service methods on this API, and their
123         # associated metadata.
124         def api_methods
125           read_inheritable_attribute("api_methods") || {}
126         end
128         # The Method instance for the given public API method name, if any
129         def public_api_method_instance(public_method_name)
130           api_method_instance(api_method_name(public_method_name))
131         end
133         # The Method instance for the given API method name, if any
134         def api_method_instance(method_name)
135           api_methods[method_name]
136         end
138         # The Method instance for the default API method, if any
139         def default_api_method_instance
140           return nil unless name = default_api_method
141           instance = read_inheritable_attribute("default_api_method_instance")
142           if instance && instance.name == name
143             return instance
144           end
145           instance = Method.new(name, public_api_method_name(name), nil, nil)
146           write_inheritable_attribute("default_api_method_instance", instance)
147           instance
148         end
150         private
151           def api_public_method_names
152             read_inheritable_attribute("api_public_method_names") || {}
153           end
154   
155           def validate_options(valid_option_keys, supplied_option_keys)
156             unknown_option_keys = supplied_option_keys - valid_option_keys
157             unless unknown_option_keys.empty?
158               raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
159             end
160           end
161       end
162     end
164     # Represents an API method and its associated metadata, and provides functionality
165     # to assist in commonly performed API method tasks.
166     class Method
167       attr :name
168       attr :public_name
169       attr :expects
170       attr :returns
172       def initialize(name, public_name, expects, returns)
173         @name = name
174         @public_name = public_name
175         @expects = expects
176         @returns = returns
177         @caster = ActionWebService::Casting::BaseCaster.new(self)
178       end
179       
180       # The list of parameter names for this method
181       def param_names
182         return [] unless @expects
183         @expects.map{ |type| type.name }
184       end
186       # Casts a set of Ruby values into the expected Ruby values
187       def cast_expects(params)
188         @caster.cast_expects(params)
189       end
191       # Cast a Ruby return value into the expected Ruby value
192       def cast_returns(return_value)
193         @caster.cast_returns(return_value)
194       end
196       # Returns the index of the first expected parameter
197       # with the given name
198       def expects_index_of(param_name)
199         return -1 if @expects.nil?
200         (0..(@expects.length-1)).each do |i|
201           return i if @expects[i].name.to_s == param_name.to_s
202         end
203         -1
204       end
206       # Returns a hash keyed by parameter name for the given
207       # parameter list
208       def expects_to_hash(params)
209         return {} if @expects.nil?
210         h = {}
211         @expects.zip(params){ |type, param| h[type.name] = param }
212         h
213       end
215       # Backwards compatibility with previous API
216       def [](sig_type)
217         case sig_type
218         when :expects
219           @expects.map{|x| compat_signature_entry(x)}
220         when :returns
221           @returns.map{|x| compat_signature_entry(x)}
222         end
223       end
225       # String representation of this method
226       def to_s
227         fqn = ""
228         fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ")
229         fqn << "#{@public_name}("
230         fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects
231         fqn << ")"
232         fqn
233       end
235       private
236         def compat_signature_entry(entry)
237           if entry.array?
238             [compat_signature_entry(entry.element_type)]
239           else
240             if entry.spec.is_a?(Hash)
241               {entry.spec.keys.first => entry.type_class}
242             else
243               entry.type_class
244             end
245           end
246         end
247     end
248   end