apipie的参数校验
需要校验参数的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