Repository URL to install this package:
Version:
3.4.0.pre.2 ▾
|
# frozen_string_literal: true
require 'sinatra/flash'
require 'sinatra/param'
# Top-level module documentation comment goes here...
module Sinatra
# Helpers for checking parameters
module ParamValidator
def validator(identifier, &block)
validator = Validator.new(block)
Sinatra::ParamValidator::ValidateMethods.add identifier, validator
end
def valdr(identifier, *args)
ValidatorIdentifier.new(identifier, *args)
end
# Helpers for Sinatra
module Helpers
def filter_params
# Filter params, replace empty strings/arrays with nil
params.each do |(param, value)|
params[param] = nil if value == ''
params[param] = [nil] if value == ['']
end
rescue StandardError => e
raise "Filter params failed: #{e}"
end
def validate(validator_identifier)
validator = Sinatra::ParamValidator::ValidateMethods.get validator_identifier.identifier
validator.context = self
validator.run(*validator_identifier.args)
validator.handle_failure unless validator.success?
end
def form_values(hash)
hash = IndifferentHash[hash]
flash.now[:params] = flash.now.key?(:params) ? hash.merge(flash.now[:params]) : hash
end
def form_value(field)
flash[:params]&.fetch(field, nil)
end
def form_error?(field = nil)
return !flash[:form_errors].nil? && !flash[:form_errors]&.empty? if field.nil?
(flash[:form_errors] || {}).key?(field)
end
def form_error(field)
warn '[DEPRECATED] `form_error` is deprecated; use `form_errors` instead'
flash[:form_errors]&.fetch(field, '')
end
def form_errors(field)
(flash[:form_errors] || {}).fetch(field, [])
end
end
# A validator for a form. Essentially holds a list of methods to call and their arguments for parameter validation.
class Validator
VALID_METHODS = %i[block param one_of any_of].freeze
attr_writer :context, :raise_status
def initialize(definition)
@definition = definition
clear
end
def method_missing(name, *args, &block)
super unless VALID_METHODS.include? name
@methods.push({ name: name, args: args, block: block })
end
def respond_to_missing?(method, *)
VALID_METHODS.include?(method) || super
end
def run(*args)
clear
save_params_copy
run_definition(*args)
@methods.each do |method|
run_method method
rescue Sinatra::Param::InvalidParameterError => e
save_method_error method, e
end
end
def success?
@errors.empty?
end
def handle_failure
raise @context.http_status(@raise_status) if @raise_status
@context.halt 400, { error: 'Validation failed', fields: @errors }.to_json if @context.request.xhr?
@context.flash[:params] = @params
@context.flash[:form_errors] = @errors
@context.redirect @context.back
end
private
def clear
@methods = []
@errors = {}
end
# Run the validator definition block in the route context
# Pass in this validator object and any arguments
def run_definition(*args)
@context.instance_exec self, *args, &@definition
end
# Run a defined method in the route context
def run_method(method)
# :block is a placeholder to just run the given block, not a specific validation method
@context.instance_exec(*method[:args], &@context.method(method[:name])) unless method[:name] == :block
@context.instance_eval(&method[:block]) if method[:block]
end
def save_method_error(method, error)
key = method[:name] == :param ? method[:args][0] : :global_errors
@errors[key] ||= []
@errors[key].push error.options&.key?(:message) ? error.options[:message].dup : error.to_s.dup
end
def save_params_copy
@params = @context.params.dup
end
end
# Holds a validator method identifier and any arguments to pass to the validator
class ValidatorIdentifier
attr_reader :identifier, :args
def initialize(identifier, *args)
@identifier = identifier
@args = args
end
end
# Keep hold of the list of valid validator methods
module ValidateMethods
@methods = {}
module_function
def add(identifier, validator)
@methods[identifier] = validator
end
def get(identifier)
raise "Unknown validator: '#{identifier}'" unless @methods.key? identifier
@methods[identifier]
end
end
def self.registered(app) # rubocop:disable Metrics/MethodLength
app.helpers Helpers
app.helpers Sinatra::Param
app.enable :raise_sinatra_param_exceptions
app.set(:validate) do |*identifiers|
condition do
filter_params
identifiers.each do |identifier|
identifier = ValidatorIdentifier.new(identifier) if identifier.is_a? Symbol
validate identifier
end
end
end
end
end
register Sinatra::ParamValidator
end