Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
uoy-faculty-sinatra / lib / sinatra / param_validator.rb
Size: Mime:
# 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