removing log dir from .gitignore
[monkeycharger.git] / vendor / rails / actionwebservice / lib / action_web_service / api.rb
blobccc4b7d656c1e454406a4c7479f0b7b535ff7c10
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       # By default only HTTP POST requests are processed
25       class_inheritable_option :allowed_http_methods, [ :post ]
26       
27       # Whether to allow ActiveRecord::Base models in <tt>:expects</tt>.
28       # The default is +false+; you should be aware of the security implications
29       # of allowing this, and ensure that you don't allow remote callers to
30       # easily overwrite data they should not have access to.
31       class_inheritable_option :allow_active_record_expects, false
33       # If present, the name of a method to call when the remote caller
34       # tried to call a nonexistent method. Semantically equivalent to
35       # +method_missing+.
36       class_inheritable_option :default_api_method
38       # Disallow instantiation
39       private_class_method :new, :allocate
40       
41       class << self
42         include ActionWebService::SignatureTypes
44         # API methods have a +name+, which must be the Ruby method name to use when
45         # performing the invocation on the web service object.
46         #
47         # The signatures for the method input parameters and return value can
48         # by specified in +options+.
49         #
50         # A signature is an array of one or more parameter specifiers. 
51         # A parameter specifier can be one of the following:
52         #
53         # * A symbol or string representing one of the Action Web Service base types.
54         #   See ActionWebService::SignatureTypes for a canonical list of the base types.
55         # * The Class object of the parameter type
56         # * A single-element Array containing one of the two preceding items. This
57         #   will cause Action Web Service to treat the parameter at that position
58         #   as an array containing only values of the given type.
59         # * A Hash containing as key the name of the parameter, and as value
60         #   one of the three preceding items
61         # 
62         # If no method input parameter or method return value signatures are given,
63         # the method is assumed to take no parameters and/or return no values of
64         # interest, and any values that are received by the server will be
65         # discarded and ignored.
66         #
67         # Valid options:
68         # [<tt>:expects</tt>]             Signature for the method input parameters
69         # [<tt>:returns</tt>]             Signature for the method return value
70         # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
71         def api_method(name, options={})
72           unless options.is_a?(Hash)
73             raise(ActionWebServiceError, "Expected a Hash for options")
74           end
75           validate_options([:expects, :returns, :expects_and_returns], options.keys)
76           if options[:expects_and_returns]
77             expects = options[:expects_and_returns]
78             returns = options[:expects_and_returns]
79           else
80             expects = options[:expects]
81             returns = options[:returns]
82           end
83           expects = canonical_signature(expects)
84           returns = canonical_signature(returns)
85           if expects
86             expects.each do |type|
87               type = type.element_type if type.is_a?(ArrayType)
88               if type.type_class.ancestors.include?(ActiveRecord::Base) && !allow_active_record_expects
89                 raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
90               end
91             end
92           end
93           name = name.to_sym
94           public_name = public_api_method_name(name)
95           method = Method.new(name, public_name, expects, returns)
96           write_inheritable_hash("api_methods", name => method)
97           write_inheritable_hash("api_public_method_names", public_name => name)
98         end
100         # Whether the given method name is a service method on this API
101         #
102         #   class ProjectsApi < ActionWebService::API::Base
103         #     api_method :getCount, :returns => [:int]
104         #   end
105         #
106         #   ProjectsApi.has_api_method?('GetCount')   #=> false
107         #   ProjectsApi.has_api_method?(:getCount)    #=> true
108         def has_api_method?(name)
109           api_methods.has_key?(name)
110         end
111   
112         # Whether the given public method name has a corresponding service method
113         # on this API
114         #
115         #   class ProjectsApi < ActionWebService::API::Base
116         #     api_method :getCount, :returns => [:int]
117         #   end
118         #
119         #   ProjectsApi.has_api_method?(:getCount)    #=> false
120         #   ProjectsApi.has_api_method?('GetCount')   #=> true
121         def has_public_api_method?(public_name)
122           api_public_method_names.has_key?(public_name)
123         end
124   
125         # The corresponding public method name for the given service method name
126         #
127         #   ProjectsApi.public_api_method_name('GetCount')  #=> "GetCount"
128         #   ProjectsApi.public_api_method_name(:getCount)   #=> "GetCount"
129         def public_api_method_name(name)
130           if inflect_names
131             name.to_s.camelize
132           else
133             name.to_s
134           end
135         end
136   
137         # The corresponding service method name for the given public method name
138         #
139         #   class ProjectsApi < ActionWebService::API::Base
140         #     api_method :getCount, :returns => [:int]
141         #   end
142         #        
143         #   ProjectsApi.api_method_name('GetCount') #=> :getCount
144         def api_method_name(public_name)
145           api_public_method_names[public_name]
146         end
147   
148         # A Hash containing all service methods on this API, and their
149         # associated metadata.
150         #
151         #   class ProjectsApi < ActionWebService::API::Base
152         #     api_method :getCount,          :returns => [:int]
153         #     api_method :getCompletedCount, :returns => [:int]
154         #   end
155         #
156         #   ProjectsApi.api_methods #=> 
157         #     {:getCount=>#<ActionWebService::API::Method:0x24379d8 ...>,
158         #      :getCompletedCount=>#<ActionWebService::API::Method:0x2437794 ...>}
159         #   ProjectsApi.api_methods[:getCount].public_name #=> "GetCount"
160         def api_methods
161           read_inheritable_attribute("api_methods") || {}
162         end
164         # The Method instance for the given public API method name, if any
165         #
166         #   class ProjectsApi < ActionWebService::API::Base
167         #     api_method :getCount,          :returns => [:int]
168         #     api_method :getCompletedCount, :returns => [:int]
169         #   end
170         #
171         #   ProjectsApi.public_api_method_instance('GetCount')  #=> <#<ActionWebService::API::Method:0x24379d8 ...>
172         #   ProjectsApi.public_api_method_instance(:getCount)   #=> nil
173         def public_api_method_instance(public_method_name)
174           api_method_instance(api_method_name(public_method_name))
175         end
177         # The Method instance for the given API method name, if any
178         #
179         #   class ProjectsApi < ActionWebService::API::Base
180         #     api_method :getCount,          :returns => [:int]
181         #     api_method :getCompletedCount, :returns => [:int]
182         #   end
183         #
184         #   ProjectsApi.api_method_instance(:getCount) #=> <ActionWebService::API::Method:0x24379d8 ...>
185         #   ProjectsApi.api_method_instance('GetCount') #=> <ActionWebService::API::Method:0x24379d8 ...>
186         def api_method_instance(method_name)
187           api_methods[method_name]
188         end
190         # The Method instance for the default API method, if any
191         def default_api_method_instance
192           return nil unless name = default_api_method
193           instance = read_inheritable_attribute("default_api_method_instance")
194           if instance && instance.name == name
195             return instance
196           end
197           instance = Method.new(name, public_api_method_name(name), nil, nil)
198           write_inheritable_attribute("default_api_method_instance", instance)
199           instance
200         end
202         private
203           def api_public_method_names
204             read_inheritable_attribute("api_public_method_names") || {}
205           end
206   
207           def validate_options(valid_option_keys, supplied_option_keys)
208             unknown_option_keys = supplied_option_keys - valid_option_keys
209             unless unknown_option_keys.empty?
210               raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
211             end
212           end
213       end
214     end
216     # Represents an API method and its associated metadata, and provides functionality
217     # to assist in commonly performed API method tasks.
218     class Method
219       attr :name
220       attr :public_name
221       attr :expects
222       attr :returns
224       def initialize(name, public_name, expects, returns)
225         @name = name
226         @public_name = public_name
227         @expects = expects
228         @returns = returns
229         @caster = ActionWebService::Casting::BaseCaster.new(self)
230       end
231       
232       # The list of parameter names for this method
233       def param_names
234         return [] unless @expects
235         @expects.map{ |type| type.name }
236       end
238       # Casts a set of Ruby values into the expected Ruby values
239       def cast_expects(params)
240         @caster.cast_expects(params)
241       end
243       # Cast a Ruby return value into the expected Ruby value
244       def cast_returns(return_value)
245         @caster.cast_returns(return_value)
246       end
248       # Returns the index of the first expected parameter
249       # with the given name
250       def expects_index_of(param_name)
251         return -1 if @expects.nil?
252         (0..(@expects.length-1)).each do |i|
253           return i if @expects[i].name.to_s == param_name.to_s
254         end
255         -1
256       end
258       # Returns a hash keyed by parameter name for the given
259       # parameter list
260       def expects_to_hash(params)
261         return {} if @expects.nil?
262         h = {}
263         @expects.zip(params){ |type, param| h[type.name] = param }
264         h
265       end
267       # Backwards compatibility with previous API
268       def [](sig_type)
269         case sig_type
270         when :expects
271           @expects.map{|x| compat_signature_entry(x)}
272         when :returns
273           @returns.map{|x| compat_signature_entry(x)}
274         end
275       end
277       # String representation of this method
278       def to_s
279         fqn = ""
280         fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ")
281         fqn << "#{@public_name}("
282         fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects
283         fqn << ")"
284         fqn
285       end
287       private
288         def compat_signature_entry(entry)
289           if entry.array?
290             [compat_signature_entry(entry.element_type)]
291           else
292             if entry.spec.is_a?(Hash)
293               {entry.spec.keys.first => entry.type_class}
294             else
295               entry.type_class
296             end
297           end
298         end
299     end
300   end