需要校验参数的controller得加上以下拦截

before_action: :apipie_validations


检索源码,发现apipie_validations方法是这样定义的

def _apipie_define_validators(description)

  # [re]define method only if validation is turned on
  if description && (Apipie.configuration.validate == true ||
                     Apipie.configuration.validate == :implicitly ||
                     Apipie.configuration.validate == :explicitly)

    _apipie_save_method_params(description.method, description.params)

    unless instance_methods.include?(:apipie_validations)
      define_method(:apipie_validations) do
        method_params = self.class._apipie_get_method_params(action_name)

        if Apipie.configuration.validate_presence?
          method_params.each do |_, param|
            # check if required parameters are present
            raise ParamMissing.new(param) if param.required && !params.has_key?(param.name)
          end
        end


于是,在方法开头加入binding.pry,得调用栈如下

# ...
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/apipie-rails-0.5.1/lib/apipie/dsl_definition.rb:216:in `_apipie_define_validators'",
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/apipie-rails-0.5.1/lib/apipie/dsl_definition.rb:412:in `method_added'",
" … addition_services_controller.rb:13:in `’",
# …


可见其跟Contracts差不多,都是通过method_added来将annotation跟方法关联起来

module Controller
  def method_added(method_name) #:doc:
    super
    return if !Apipie.active_dsl? || !_apipie_dsl_data[:api]

    return if _apipie_dsl_data[:api_args].blank? && _apipie_dsl_data[:api_from_routes].blank?

    # remove method description if exists and create new one
    Apipie.remove_method_description(self, _apipie_dsl_data[:api_versions], method_name)
    description = Apipie.define_method_description(self, method_name, _apipie_dsl_data)

    _apipie_dsl_data_clear
    _apipie_define_validators(description)
  ensure
    _apipie_dsl_data_clear
  end
end


那么这个module Controller是如何mixin到应用的controller里呢,检索源码,可得

module Apipie
  class Railtie < Rails::Railtie
    initializer 'apipie.controller_additions' do
      ActiveSupport.on_load :action_controller do
        extend Apipie::DSL::Controller
      end
    end
  end
end


在自定义的validator的validate方法加入binding.pry查看调用栈可得

# …
" … validator.rb:11:in `validate'",
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/apipie-rails-0.5.1/lib/apipie/validator.rb:32:in `valid?'",
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/apipie-rails-0.5.1/lib/apipie/param_description.rb:89:in `validate'",
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/apipie-rails-0.5.1/lib/apipie/dsl_definition.rb:233:in `block (2 levels) in _apipie_define_validators'",
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/apipie-rails-0.5.1/lib/apipie/dsl_definition.rb:231:in `each'",
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/apipie-rails-0.5.1/lib/apipie/dsl_definition.rb:231:in `block in _apipie_define_validators'",
"/Users/yuanzhipeng/.rbenv/versions/2.1.5/lib/ruby/gems/2.1.0/gems/activesupport-4.1.4/lib/active_support/callbacks.rb:424:in `block in make_lambda’",
# ...


确实就是从apipie_validations中进入

validator的查找远离如下:继承Validator的校验类,会被inherited保存起来,当请求到来时,用find -> build找回符合的校验类。(注意inherited保存时是置于队列开头的,所以后来的继承定义会被优先匹配)

module Apipie

  module Validator

    # to create new validator, inherit from Apipie::Validator::Base
    # and implement class method build and instance method validate
    class BaseValidator

      attr_accessor :param_description

      def initialize(param_description)
        @param_description = param_description
      end

      def self.inherited(subclass)
        @validators ||= []
        @validators.insert 0, subclass
      end

      # find the right validator for given options
      def self.find(param_description, argument, options, block)
        @validators.each do |validator_type|
          validator = validator_type.build(param_description, argument, options, block)
          return validator if validator
        end
        return nil
      end

      # check if value is valid
      def valid?(value)
        if self.validate(value)
          @error_value = nil
          true
        else
          @error_value = value
          false
        end
      end


其实Check if parameter value is included in the given array也是用(自带的)自定义validator,如下

class EnumValidator < BaseValidator
  def initialize(param_description, argument)
    super(param_description)
    @array = argument
  end

  def validate(value)
    @array.include?(value)
  end

  def self.build(param_description, argument, options, proc)
    self.new(param_description, argument) if argument.is_a?(Array)
  end

  def description
    "Must be one of: #{@array.join(', ')}."
  end
end