aboutsummaryrefslogblamecommitdiffstats
path: root/actionwebservice/lib/action_web_service/api/abstract.rb
blob: 0ce68d10f758404e32ccf97893c55885d6381d18 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
                                 
                      
                                                                      







                                            
                                                                        
       
                                                                               
                                                                                    







                                                                              
                                                  
                                 

             
                                                   





                                                     
                                 

             
                                                   

               
                                         
                          
                                                       

                                     
                                                                                                   



                                                                                          

                                                                    


           

                                                                     


             

                                                                                         




                                                   
                                                                              




                                                                              
                                                               
                                                                            
                                   
              
                                                                                










                                                                       
                                           

                                                                                   
                                                              
         

                                                                             
         

                                                                       
         

                                                                                        

                                                                                  
                                                                                 



                                                                            

                                                                                   



                                                                          


                                                                                              










                                                                                    
                    



                                                             
                                                                                                  
















































                                                                                 
                                                                                     






               
module ActionWebService # :nodoc:
  module API # :nodoc:
    class APIError < ActionWebService::ActionWebServiceError # :nodoc:
    end

    def self.append_features(base) # :nodoc:
      super
      base.extend(ClassMethods)
    end

    module ClassMethods
      # Attaches ActionWebService API +definition+ to the calling class.
      #
      # Action Controllers can have a default associated API, removing the need
      # to call this method if you follow the Action Web Service naming conventions.
      #
      # A controller with a class name of GoogleSearchController will
      # implicitly load <tt>app/apis/google_search_api.rb</tt>, and expect the
      # API definition class to be named <tt>GoogleSearchAPI</tt> or
      # <tt>GoogleSearchApi</tt>.
      #
      # ==== Service class example
      #
      #   class MyService < ActionWebService::Base
      #     web_service_api MyAPI
      #   end
      #
      #   class MyAPI < ActionWebService::API::Base
      #     ...
      #   end
      #
      # ==== Controller class example
      #
      #   class MyController < ActionController::Base
      #     web_service_api MyAPI
      #   end
      #
      #   class MyAPI < ActionWebService::API::Base
      #     ...
      #   end
      def web_service_api(definition=nil)
        if definition.nil?
          read_inheritable_attribute("web_service_api")
        else
          if definition.is_a?(Symbol)
            raise(APIError, "symbols can only be used for #web_service_api inside of a controller")
          end
          unless definition.respond_to?(:ancestors) && definition.ancestors.include?(Base)
            raise(APIError, "#{definition.to_s} is not a valid API definition")
          end
          write_inheritable_attribute("web_service_api", definition)
          call_web_service_api_callbacks(self, definition)
        end
      end

      def add_web_service_api_callback(&block) # :nodoc:
        write_inheritable_array("web_service_api_callbacks", [block])
      end

      private
        def call_web_service_api_callbacks(container_class, definition)
          (read_inheritable_attribute("web_service_api_callbacks") || []).each do |block|
            block.call(container_class, definition)
          end
        end
    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.
    #
    # It is not intended to be instantiated.
    #
    # It is attached to web service implementation classes like
    # ActionWebService::Base and ActionController::Base derivatives by using
    # ClassMethods#web_service_api.
    class Base
      # Whether to transform the public API method names into camel-cased names 
      class_inheritable_option :inflect_names, true

      # If present, the name of a method to call when the remote caller
      # tried to call a nonexistent method. Semantically equivalent to
      # +method_missing+.
      class_inheritable_option :default_api_method

      # Disallow instantiation
      private_class_method :new, :allocate
      
      class << self
        include ActionWebService::Signature

        # API methods have a +name+, which must be the Ruby method name to use when
        # performing the invocation on the web service object.
        #
        # The signatures for the method input parameters and return value can
        # by specified in +options+.
        #
        # A signature is an array of one or more parameter specifiers. 
        # A parameter specifier can be one of the following:
        #
        # * A symbol or string of representing one of the Action Web Service base types.
        #   See ActionWebService::Signature for a canonical list of the base types.
        # * The Class object of the parameter type
        # * A single-element Array containing one of the two preceding items. This
        #   will cause Action Web Service to treat the parameter at that position
        #   as an array containing only values of the given type.
        # * A Hash containing as key the name of the parameter, and as value
        #   one of the three preceding items
        # 
        # If no method input parameter or method return value signatures are given,
        # the method is assumed to take no parameters and/or return no values of
        # interest, and any values that are received by the server will be
        # discarded and ignored.
        #
        # Valid options:
        # [<tt>:expects</tt>]             Signature for the method input parameters
        # [<tt>:returns</tt>]             Signature for the method return value
        # [<tt>:expects_and_returns</tt>] Signature for both input parameters and return value
        def api_method(name, options={})
          validate_options([:expects, :returns, :expects_and_returns], options.keys)
          if options[:expects_and_returns]
            expects = options[:expects_and_returns]
            returns = options[:expects_and_returns]
          else
            expects = options[:expects]
            returns = options[:returns]
          end
          expects = canonical_signature(expects) if expects
          returns = canonical_signature(returns) if returns
          if expects
            expects.each do |param|
              klass = signature_parameter_class(param)
              klass = klass[0] if klass.is_a?(Array)
              if klass.ancestors.include?(ActiveRecord::Base)
                raise(ActionWebServiceError, "ActiveRecord model classes not allowed in :expects")
              end
            end
          end
          name = name.to_sym
          public_name = public_api_method_name(name)
          info = { :expects => expects, :returns => returns }
          write_inheritable_hash("api_methods", name => info)
          write_inheritable_hash("api_public_method_names", public_name => name)
        end

        # Whether the given method name is a service method on this API
        def has_api_method?(name)
          api_methods.has_key?(name)
        end
  
        # Whether the given public method name has a corresponding service method
        # on this API
        def has_public_api_method?(public_name)
          api_public_method_names.has_key?(public_name)
        end
  
        # The corresponding public method name for the given service method name
        def public_api_method_name(name)
          if inflect_names
            name.to_s.camelize
          else
            name.to_s
          end
        end
  
        # The corresponding service method name for the given public method name
        def api_method_name(public_name)
          api_public_method_names[public_name]
        end
  
        # A Hash containing all service methods on this API, and their
        # associated metadata.
        def api_methods
          read_inheritable_attribute("api_methods") || {}
        end
  
        private
          def api_public_method_names
            read_inheritable_attribute("api_public_method_names") || {}
          end
  
          def validate_options(valid_option_keys, supplied_option_keys)
            unknown_option_keys = supplied_option_keys - valid_option_keys
            unless unknown_option_keys.empty?
              raise(ActionWebServiceError, "Unknown options: #{unknown_option_keys}")
            end
          end

      end
    end
  end
end