12/08/2018, 13:51

Grape API validation

Việc sử dụng gem Grape trong rails đôi khi cần đến việc validation params, một vài tip nhỏ hi vọng giúp bạn chủ động hơn trong việc xử lý validation. Link về gem Grape: https://github.com/ruby-grape/grape Validation trong Grape có nhiều phần khác nhau, validation params là việc bắt validation ...

Việc sử dụng gem Grape trong rails đôi khi cần đến việc validation params, một vài tip nhỏ hi vọng giúp bạn chủ động hơn trong việc xử lý validation.

Link về gem Grape: https://github.com/ruby-grape/grape

Validation trong Grape có nhiều phần khác nhau, validation params là việc bắt validation tại đầu vào params của API.

Ta thường có:

resources :order do
  params do
    requires :mode, type: Integer, values: [0, 1]
    optional :okan_info, type: Hash do
      optional :okan_id, type: String, max_length: 255, allow_blank: false
      optional :nominate_flg, type: Boolean
    end
    requires :start_time, type: DateTime, coerce_with: ->(val){Time.zone.parse(val).to_datetime.utc}
    requires :job_kind, type: Array[String]
    requires :absence_flg, type: Boolean
    optional :repeat_pattern, type: Integer
    optional :coupon_id, type: Integer
    optional :notice_time, type: Integer, values: Settings.orders.notice_time
    requires :work_time, type: Integer,
      values: [*Settings.orders.min_work_time..Settings.orders.max_work_time]
  end

  ...
end

Muốn hạn chế gía trị của params ta dùng values. (https://github.com/ruby-grape/grape#parameter-validation-and-coercion)

Muốn convert gía trị của params ta dùng coerce_with. (https://github.com/ruby-grape/grape#custom-types-and-coercions)

Tại ví dụ ta thấy có requires :start_time, type: DateTime, coerce_with: ->(val){Time.zone.parse(val).to_datetime.utc}, ý nghĩa của đọa code này là parse gía trị của start_time về gía trị utc.

Việc sử dụng coerce_with cần được catch bằng cách check message_key của error

rescue_from Grape::Exceptions::ValidationErrors do |e|
  e.each do |_param, errors|
    case errors[:message_key]
    ...
    when :coerce
      ...
    else
      ...
    end
  end
end

Muốn tạo thêm 1 dạng validation mới, chỉ cần địng nghĩa thêm 1 class

module APIValidation
  class MaxLength < Grape::Validations::Base
    def validate_param! attr_name, params
      return if params[attr_name].nil? || params[attr_name].try(:length).try(:<=, (@option))
      raise Grape::Exceptions::Validation,
        params: [@scope.full_name(attr_name)],
        headers: Settings.api_validation.bad_digit_params
    end
  end
end

Đây là validation max_length cho độ dài params là string, sử dụng như những validation khác: optional :okan_id, type: String, max_length: 255, allow_blank: false.

validate_param! là method của Grape::Validations::Base:

module Grape
  ...

  module Validations
    class CoerceValidator < Base
      def initialize(*_args)
        super
        (@converter = Types.build_coercer(type, (@option[:method])))
      end

      def validate_param!(attr_name, params)
        raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless params.is_a? Hash
        new_value = coerce_value(params[attr_name])
        raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:coerce) unless valid_type?(new_value)
        params[attr_name] = new_value
      end
    end
  end
end

Ở đây ta kế thừa lại class Grape::Validations::Base và override lại hàm validate_param!.

Đoạn code headers: Settings.api_validation.bad_digit_params dùng để mark lại dạng error và lưu tại headers, khi catch lại thì lấy ra như đối với message_key.

Muốn thêm validation có liên hệ giữa các params với nhau, cũng làm tương tự cách trên, gỉa sử params đầu vào có search_start_time, search_start_time, và điều kiện là search_end_time - search_start_time >= 100.days.

  params do
    optional :search_start_time, type: Date
    optional :search_end_time, type: Date, greater_than: [:search_start_time, 100]
    all_or_none_of :search_start_time, :search_end_time
  end

Tại module định nghĩa error

  class GreaterThan < Grape::Validations::Base
    def validate_param! attr_name, params
      return if params[attr_name].nil? ||
        params[attr_name].try( :>=, params[@option[0]] + (@option[1].days))
      raise Grape::Exceptions::Validation,
        params: [@scope.full_name(attr_name)],
        headers: Settings.api_validation.bad_format_params
    end
  end

Đoạn code greater_than: [:search_start_time, 100] truyền các gía trị cần so sánh vào @option khi kiểm tra validate tại hàm validate_params: return if params[attr_name].nil? || params[attr_name].try( :>=, params[@option[0]] + (@option[1].days))

Ta có thể custom lại các validation như all_or_none_of, at_least_one_of ... bằng cách định nghĩa lại:

class AllOrNoneOfValidator < Grape::Validations::MultipleParamsBase
  def validate! params
    ...
  end
end

Chú ý Grape::Validations::MultipleParamsBase là class base cho việc validate multiple params, tuy nhiên cách này không nên sử dụng tùy tiện vì ảnh hưởng đến hàm chuẩn trong Grape.

Cảm ơn và hi vọng bài viết gíup ích phần nào trong công việc của bạn.

0