lib/qyu/workers/concerns/payload_validator.rb
# frozen_string_literal: true
module Qyu
module Workers
module Concerns
# Qyu::Workers::Concerns::PayloadValidator
#
# Adds ability to workers to perform validations on
# params for the task to be processed
#
# Usage:
#
# Qyu::Worker.new do
# validates :user_id, presence: true, type: :integer, unless: :no_user
# validates :name, presence: true, type: :string
# validates :hotels, presence: true, type: :array
# validates :account_id, absence: true, if: :customer_id
# validates :account_id, presence: true, unless: :customer_id
# end
#
module PayloadValidator
# Adds a validation option to worker
#
# validates :user_id, presence: true, type: :integer, unless: :no_user
# validates :name, presence: true, type: :string
# validates :account_id, absence: true, if: :customer_id
# validates :account_id, presence: true, unless: :customer_id
#
# @param [Symbol] payload key to validate
# @param [Hash] options for validation
# @return [Hash] registereds services
def validates(parameter, opts = {})
@_validations ||= {}
@_validations[parameter.to_s] = Qyu::Utils.stringify_hash_keys(opts)
end
# Validates payload in processing task and raises a Qyu::Errors::PayloadValidationError
# if any of the conditions do not hold up
#
# @param [Task] task with payload
# @return [nil]
# @raise [Qyu::Errors::PayloadValidationError]
def validate_payload!(model)
return unless @_validations
payload = Qyu::Utils.stringify_hash_keys(model.payload || {})
validation_errors = {}
@_validations.each do |attribute, opts|
# example: attribute :name
# example opts { presence: true, type: integer }
next unless if_validation(payload, opts['if'])
next unless unless_validation(payload, opts['unless'])
opts.map do |option, value|
error = run_validation(option, payload[attribute.to_s], value)
# next if error is nil
next unless error
validation_errors["#{attribute}.#{option}"] = error
end
end
if validation_errors.size.positive?
fail Qyu::Errors::PayloadValidationError, validation_errors
end
nil
end
private
# :nodoc:
def run_validation(option, param, value)
# Skip if and unless conditionals (return nil)
return if option.eql?('if')
return if option.eql?('unless')
# supported options are presence, absence and type
__send__(option, param, value)
rescue NoMethodError
raise Qyu::Errors::UnknownValidationOption, option
end
def presence(param, value)
return unless value
return unless param.nil?
{ expected: 'present' }
end
def absence(param, value)
return unless value
return if param.nil?
{ expected: 'absent' }
end
def type(param, data_type)
__send__("#{data_type}_type_validator", param)
end
# DataType validators
def integer_type_validator(param)
type_validator('integer', [Integer], param.class)
end
def string_type_validator(param)
type_validator('string', [String, Symbol], param.class)
end
def number_type_validator(param)
type_validator('number', [Integer, Float], param.class)
end
def boolean_type_validator(param)
type_validator('boolean', [TrueClass, FalseClass], param.class)
end
def hash_type_validator(param)
type_validator('hash', [Hash], param.class)
end
def array_type_validator(param)
type_validator('array', [Array], param.class)
end
def type_validator(type_name, data_types, param_class)
return if data_types.include?(param_class)
{ expected: type_name, got: param_class.name.downcase }
end
# Conditonal validation
def if_validation(payload, key)
# TODO: support block passing "return yield if block_given?"
return true if key.nil?
return true if payload[key.to_s]
false
end
def unless_validation(payload, key)
# TODO: support block passing "return yield if block_given?"
return true if key.nil?
return true unless payload[key.to_s]
false
end
end
end
end
end