class Strelka::ParamValidator

A validator for user parameters.

Usage

require 'strelka/paramvalidator'

validator = Strelka::ParamValidator.new

# Add validation criteria for input parameters
validator.add( :name, /^(?<lastname>\S+), (?<firstname>\S+)$/, "Customer Name" )
validator.add( :email, "Customer Email" )
validator.add( :feedback, :printable, "Customer Feedback" )
validator.override( :email, :printable, "Your Email Address" )

# Now pass in values in a hash (e.g., from an HTML form)
validator.validate( req.params )

# Now if there weren't any errors, use some form values to fill out the
# success page template
if validator.okay?
        tmpl = template :success
        tmpl.firstname = validator[:name][:firstname]
        tmpl.lastname  = validator[:name][:lastname]
        tmpl.email      = validator[:email]
        tmpl.feedback  = validator[:feedback]
        return tmpl

# Otherwise fill in the error template with auto-generated error messages
# and return that instead.
else
        tmpl = template :feedback_form
        tmpl.errors = validator.error_messages
        return tmpl
end

Constants

PARAMETER_PATTERN_STRIP_RE

Pattern to use to strip binding operators from parameter patterns so they can be used in the middle of routing Regexps.

PARAMS_HASH_RE

Pattern for countint the number of hash levels in a parameter key

Attributes

constraints[R]

The constraints hash

fields[R]

The Hash of raw field data (if validation has occurred)

Public Class Methods

new() click to toggle source

Create a new Strelka::ParamValidator object.

# File lib/strelka/paramvalidator.rb, line 550
def initialize
        @constraints = {}
        @fields      = {}

        self.reset
end

Public Instance Methods

[]( key ) click to toggle source

Index fetch operator; fetch the validated (and possible parsed) value for form field key.

# File lib/strelka/paramvalidator.rb, line 808
def []( key )
        self.validate unless self.validated?
        return @valid[ key.to_sym ]
end
[]=( key, val ) click to toggle source

Index assignment operator; set the validated value for form field key to the specified val.

# File lib/strelka/paramvalidator.rb, line 816
def []=( key, val )
        @parsed_params = nil
        @valid[ key.to_sym ] = val
end
add( name, *flags ) click to toggle source
add( name, constraint, *flags )
add( name, description, *flags )
add( name, constraint, description, *flags )

Add a validation for a parameter with the specified name. The args can include a constraint, a description, and one or more flags.

# File lib/strelka/paramvalidator.rb, line 603
def add( name, *args, &block )
        name = name.to_sym
        constraint = Constraint.for( name, *args, &block )

        # No-op if there's already a parameter with the same name and constraint
        if self.constraints.key?( name )
                return if self.constraints[ name ] == constraint
                raise ArgumentError,
                        "parameter %p is already defined as %s; perhaps you meant to use #override?" %
                                [ name.to_s, self.constraints[name] ]
        end

        self.log.debug "Adding parameter %p: %p" % [ name, constraint ]
        self.constraints[ name ] = constraint

        self.validated = false
end
apply_constraint( constraint, value ) click to toggle source

Apply the specified constraint (a Strelka::ParamValidator::Constraint object) to the given value, and add the field to the appropriate field list based on the result.

# File lib/strelka/paramvalidator.rb, line 731
def apply_constraint( constraint, value )
        if !( value.nil? || value == '' )
                result = constraint.apply( value )

                if !result.nil?
                        self.log.debug "  constraint for %p passed: %p" % [ constraint.name, result ]
                        self[ constraint.name ] = result
                else
                        self.log.debug "  constraint for %p failed" % [ constraint.name ]
                        @invalid[ constraint.name.to_s ] = value
                end
        elsif constraint.required?
                self.log.debug "  missing parameter for %p" % [ constraint.name ]
                @missing << constraint.name.to_s
        end
end
args?() click to toggle source

Returns true if there were arguments given.

# File lib/strelka/paramvalidator.rb, line 829
def args?
        return !self.fields.empty?
end
Also aliased as: has_args?
constraint_regexp_for( name ) click to toggle source

Fetch the constraint/s that apply to the parameter named name as a Regexp, if possible.

# File lib/strelka/paramvalidator.rb, line 759
def constraint_regexp_for( name )
        self.log.debug "  searching for a constraint for %p" % [ name ]

        # Fetch the constraint's regexp
        constraint = self.constraints[ name.to_sym ] or
                raise NameError, "no such parameter %p" % [ name ]
        raise ScriptError,
                "can't route on a parameter with a %p" % [ constraint.class ] unless
                constraint.respond_to?( :pattern )

        re = constraint.pattern
        self.log.debug "  bounded constraint is: %p" % [ re ]

        # Unbind the pattern from beginning or end of line.
        # :TODO: This is pretty ugly. Find a better way of modifying the regex.
        re_str = re.to_s.
                sub( %r{\(\?[\-mix]+:(.*)\)}, '\1' ).
                gsub( PARAMETER_PATTERN_STRIP_RE, '' )
        self.log.debug "  stripped constraint pattern down to: %p" % [ re_str ]

        return Regexp.new( "(?<#{name}>#{re_str})", re.options )
end
descriptions() click to toggle source

Hash of field descriptions

# File lib/strelka/paramvalidator.rb, line 671
def descriptions
        return self.constraints.each_with_object({}) do |(field,constraint), hash|
                hash[ field ] = constraint.description
        end
end
descriptions=( new_descs ) click to toggle source

Set field descriptions en masse to new_descs.

# File lib/strelka/paramvalidator.rb, line 679
def descriptions=( new_descs )
        new_descs.each do |name, description|
                raise NameError, "no parameter named #{name}" unless
                        self.constraints.key?( name.to_sym )
                self.constraints[ name.to_sym ].description = description
        end
end
empty?() click to toggle source

Returns true if there were no arguments given.

# File lib/strelka/paramvalidator.rb, line 823
def empty?
        return self.fields.empty?
end
error_fields() click to toggle source

Return an array of field names which had some kind of error associated with them.

# File lib/strelka/paramvalidator.rb, line 873
def error_fields
        return self.missing | self.invalid.keys
end
error_messages( include_unknown=false ) click to toggle source

Return an error message for each missing or invalid field; if includeUnknown is true, also include messages for unknown fields.

# File lib/strelka/paramvalidator.rb, line 880
def error_messages( include_unknown=false )
        msgs = []

        msgs += self.missing_param_errors + self.invalid_param_errors
        msgs += self.unknown_param_errors if include_unknown

        return msgs
end
errors?() click to toggle source

Returns true if any fields are missing or contain invalid values.

# File lib/strelka/paramvalidator.rb, line 858
def errors?
        return !self.okay?
end
Also aliased as: has_errors?
get_description( field ) click to toggle source

Get the description for the specified field.

# File lib/strelka/paramvalidator.rb, line 689
def get_description( field )
        constraint = self.constraints[ field.to_sym ] or return nil
        return constraint.description
end
has_args?()
Alias for: args?
has_errors?()
Alias for: errors?
initialize_copy( original ) click to toggle source

Copy constructor.

# File lib/strelka/paramvalidator.rb, line 559
def initialize_copy( original )
        fields       = deep_copy( original.fields )
        self.reset
        @fields      = fields
        @constraints = deep_copy( original.constraints )
end
inspect() click to toggle source

Return a human-readable representation of the validator, suitable for debugging.

# File lib/strelka/paramvalidator.rb, line 655
def inspect
        required, optional = self.constraints.partition do |_, constraint|
                constraint.required?
        end

        return "#<%p:0x%016x %s, profile: [required: %s, optional: %s]>" % [
                self.class,
                self.object_id / 2,
                self.to_s,
                required.empty? ? "(none)" : required.map( &:last ).map( &:name ).join(','),
                optional.empty? ? "(none)" : optional.map( &:last ).map( &:name ).join(','),
        ]
end
invalid() click to toggle source

The Hash of fields that were present, but invalid (didn't match the field's constraint)

# File lib/strelka/paramvalidator.rb, line 843
def invalid
        self.validate unless self.validated?
        return @invalid
end
invalid_param_errors() click to toggle source

Return an Array of error messages, one for each field that was invalid from the last validation.

# File lib/strelka/paramvalidator.rb, line 902
def invalid_param_errors
        return self.invalid.collect do |field, _|
                constraint = self.constraints[ field.to_sym ] or
                        raise NameError, "no such field %p!" % [ field ]
                "Invalid value for '%s'" % [ constraint.description ]
        end
end
merge( params ) click to toggle source

Return a new ParamValidator with the additional params merged into its values and re-validated.

# File lib/strelka/paramvalidator.rb, line 923
def merge( params )
        copy = self.dup
        copy.merge!( params )
        return copy
end
merge!( params ) click to toggle source

Merge the specified params into the receiving ParamValidator and re-validate the resulting values.

# File lib/strelka/paramvalidator.rb, line 932
def merge!( params )
        return if params.empty?
        self.log.debug "Merging parameters for revalidation: %p" % [ params ]
        self.revalidate( params )
end
missing() click to toggle source

The names of fields that were required, but missing from the parameter list.

# File lib/strelka/paramvalidator.rb, line 836
def missing
        self.validate unless self.validated?
        return @missing
end
missing_param_errors() click to toggle source

Return an Array of error messages, one for each field missing from the last validation.

# File lib/strelka/paramvalidator.rb, line 891
def missing_param_errors
        return self.missing.collect do |field|
                constraint = self.constraints[ field.to_sym ] or
                        raise NameError, "no such field %p!" % [ field ]
                "Missing value for '%s'" % [ constraint.description ]
        end
end
okay?() click to toggle source

Return true if all required fields were present and all present fields validated correctly.

# File lib/strelka/paramvalidator.rb, line 866
def okay?
        return (self.missing.empty? && self.invalid.empty?)
end
override( name, *args, &block ) click to toggle source

Replace the existing parameter with the specified name. The args replace the existing description, constraints, and flags. See add for details.

# File lib/strelka/paramvalidator.rb, line 624
def override( name, *args, &block )
        name = name.to_sym
        raise ArgumentError,
                "no parameter %p defined; perhaps you meant to use #add?" % [ name.to_s ] unless
                self.constraints.key?( name )

        self.log.debug "Overriding parameter %p" % [ name ]
        self.constraints[ name ] = Constraint.for( name, *args, &block )

        self.validated = false
end
param_names() click to toggle source

Return the Array of parameter names the validator knows how to validate (as Strings).

# File lib/strelka/paramvalidator.rb, line 638
def param_names
        return self.constraints.keys.map( &:to_s ).sort
end
reset() click to toggle source

Reset the validation state.

# File lib/strelka/paramvalidator.rb, line 584
def reset
        self.log.debug "Resetting validation state."
        @validated     = false
        @valid         = {}
        @parsed_params = nil
        @missing       = []
        @unknown       = []
        @invalid       = {}
end
revalidate( params={} ) click to toggle source

Clear existing validation information, merge the specified params with any existing raw fields, and re-run the validation.

# File lib/strelka/paramvalidator.rb, line 751
def revalidate( params={} )
        merged_fields = self.fields.merge( params )
        self.reset
        self.validate( merged_fields )
end
to_s() click to toggle source

Stringified description of the validator

# File lib/strelka/paramvalidator.rb, line 644
def to_s
    "%d parameters (%d valid, %d invalid, %d missing)" % [
        self.fields.size,
        self.valid.size,
        self.invalid.size,
        self.missing.size,
    ]
end
unknown() click to toggle source

The names of fields that were present in the parameters, but didn't have a corresponding constraint.

# File lib/strelka/paramvalidator.rb, line 851
def unknown
        self.validate unless self.validated?
        return @unknown
end
unknown_param_errors() click to toggle source

Return an Array of error messages, one for each field present in the parameters in the last validation that didn't have a constraint associated with it.

# File lib/strelka/paramvalidator.rb, line 913
def unknown_param_errors
        self.log.debug "Fetching unknown param errors for %p." % [ self.unknown ]
        return self.unknown.collect do |field|
                "Unknown parameter '%s'" % [ field.capitalize ]
        end
end
valid() click to toggle source

Returns the valid fields after expanding Rails-style 'customer[street]' variables into multi-level hashes.

# File lib/strelka/paramvalidator.rb, line 785
def valid
        self.validate unless self.validated?

        self.log.debug "Building valid fields hash from raw data: %p" % [ @valid ]
        unless @parsed_params
                @parsed_params = {}
                for key, value in @valid
                        self.log.debug "  adding %s: %p" % [ key, value ]
                        value = [ value ] if key.to_s.end_with?( '[]' )
                        if key.to_s.include?( '[' )
                                build_deep_hash( value, @parsed_params, get_levels(key.to_s) )
                        else
                                @parsed_params[ key ] = value
                        end
                end
        end

        return @parsed_params
end
validate( params=nil, additional_constraints=nil ) click to toggle source

Validate the input in params. If the optional additional_constraints is given, merge it with the validator's existing constraints before validating.

# File lib/strelka/paramvalidator.rb, line 697
def validate( params=nil, additional_constraints=nil )
        self.log.debug "Validating."
        self.reset

        # :TODO: Handle the additional_constraints

        params ||= @fields
        params = stringify_keys( params )
        @fields = deep_copy( params )

        self.log.debug "Starting validation with fields: %p" % [ @fields ]

        # Use the constraints list to extract all the parameters that have corresponding
        # constraints
        self.constraints.each do |field, constraint|
                self.log.debug "  applying %s to any %p parameter/s" % [ constraint, field ]
                value = params.delete( field.to_s )
                self.log.debug "  value is: %p" % [ value ]
                self.apply_constraint( constraint, value )
        end

        # Any left over are unknown
        params.keys.each do |field|
                self.log.debug "  unknown field %p" % [ field ]
                @unknown << field
        end

        @validated = true
end
validated?() click to toggle source

Returns true if the paramvalidator has been given parameters to validate. Adding or overriding constraints resets this.

# File lib/strelka/paramvalidator.rb, line 580
attr_predicate_accessor :validated?
values_at( *selector ) click to toggle source

Returns an array containing valid parameters in the validator corresponding to the given selector(s).

# File lib/strelka/paramvalidator.rb, line 941
def values_at( *selector )
        selector.map!( &:to_sym )
        return self.valid.values_at( *selector )
end

Private Instance Methods

build_deep_hash( value, hash, levels ) click to toggle source

Build a deep hash out of the given parameter value

# File lib/strelka/paramvalidator.rb, line 953
def build_deep_hash( value, hash, levels )
        if levels.length == 0
                value
        elsif hash.nil?
                { levels.first => build_deep_hash(value, nil, levels[1..-1]) }
        else
                hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) })
        end
end
get_levels( key ) click to toggle source

Get the number of hash levels in the specified key Stolen from the CGIMethods class in Rails' action_controller.

# File lib/strelka/paramvalidator.rb, line 966
def get_levels( key )
        all, main, bracketed, trailing = PARAMS_HASH_RE.match( key ).to_a
        if main.nil?
                return []
        elsif trailing
                return [key]
        elsif bracketed
                return [main] + bracketed.slice(1...-1).split('][')
        else
                return [main]
        end
end