1 module ActionWebService # :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
7 # It is not intended to be instantiated.
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.
14 # See ActionWebService::Container::Direct::ClassMethods for an example
17 # Action WebService API subclasses should be reloaded by the dispatcher in Rails
18 # when Dependencies.mechanism = :load.
19 include Reloadable::Deprecated
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 ]
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
36 class_inheritable_option :default_api_method
38 # Disallow instantiation
39 private_class_method :new, :allocate
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.
47 # The signatures for the method input parameters and return value can
48 # by specified in +options+.
50 # A signature is an array of one or more parameter specifiers.
51 # A parameter specifier can be one of the following:
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
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.
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")
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]
80 expects = options[:expects]
81 returns = options[:returns]
83 expects = canonical_signature(expects)
84 returns = canonical_signature(returns)
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")
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)
100 # Whether the given method name is a service method on this API
102 # class ProjectsApi < ActionWebService::API::Base
103 # api_method :getCount, :returns => [:int]
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)
112 # Whether the given public method name has a corresponding service method
115 # class ProjectsApi < ActionWebService::API::Base
116 # api_method :getCount, :returns => [:int]
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)
125 # The corresponding public method name for the given service method name
127 # ProjectsApi.public_api_method_name('GetCount') #=> "GetCount"
128 # ProjectsApi.public_api_method_name(:getCount) #=> "GetCount"
129 def public_api_method_name(name)
137 # The corresponding service method name for the given public method name
139 # class ProjectsApi < ActionWebService::API::Base
140 # api_method :getCount, :returns => [:int]
143 # ProjectsApi.api_method_name('GetCount') #=> :getCount
144 def api_method_name(public_name)
145 api_public_method_names[public_name]
148 # A Hash containing all service methods on this API, and their
149 # associated metadata.
151 # class ProjectsApi < ActionWebService::API::Base
152 # api_method :getCount, :returns => [:int]
153 # api_method :getCompletedCount, :returns => [:int]
156 # ProjectsApi.api_methods #=>
157 # {:getCount=>#<ActionWebService::API::Method:0x24379d8 ...>,
158 # :getCompletedCount=>#<ActionWebService::API::Method:0x2437794 ...>}
159 # ProjectsApi.api_methods[:getCount].public_name #=> "GetCount"
161 read_inheritable_attribute("api_methods") || {}
164 # The Method instance for the given public API method name, if any
166 # class ProjectsApi < ActionWebService::API::Base
167 # api_method :getCount, :returns => [:int]
168 # api_method :getCompletedCount, :returns => [:int]
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))
177 # The Method instance for the given API method name, if any
179 # class ProjectsApi < ActionWebService::API::Base
180 # api_method :getCount, :returns => [:int]
181 # api_method :getCompletedCount, :returns => [:int]
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]
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
197 instance = Method.new(name, public_api_method_name(name), nil, nil)
198 write_inheritable_attribute("default_api_method_instance", instance)
203 def api_public_method_names
204 read_inheritable_attribute("api_public_method_names") || {}
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}")
216 # Represents an API method and its associated metadata, and provides functionality
217 # to assist in commonly performed API method tasks.
224 def initialize(name, public_name, expects, returns)
226 @public_name = public_name
229 @caster = ActionWebService::Casting::BaseCaster.new(self)
232 # The list of parameter names for this method
234 return [] unless @expects
235 @expects.map{ |type| type.name }
238 # Casts a set of Ruby values into the expected Ruby values
239 def cast_expects(params)
240 @caster.cast_expects(params)
243 # Cast a Ruby return value into the expected Ruby value
244 def cast_returns(return_value)
245 @caster.cast_returns(return_value)
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
258 # Returns a hash keyed by parameter name for the given
260 def expects_to_hash(params)
261 return {} if @expects.nil?
263 @expects.zip(params){ |type, param| h[type.name] = param }
267 # Backwards compatibility with previous API
271 @expects.map{|x| compat_signature_entry(x)}
273 @returns.map{|x| compat_signature_entry(x)}
277 # String representation of this method
280 fqn << (@returns ? (@returns[0].human_name(false) + " ") : "void ")
281 fqn << "#{@public_name}("
282 fqn << @expects.map{ |p| p.human_name }.join(", ") if @expects
288 def compat_signature_entry(entry)
290 [compat_signature_entry(entry.element_type)]
292 if entry.spec.is_a?(Hash)
293 {entry.spec.keys.first => entry.type_class}