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
The constraints hash
The Hash of raw field data (if validation has occurred)
Public Class Methods
Create a new Strelka::ParamValidator
object.
# File lib/strelka/paramvalidator.rb, line 550 def initialize @constraints = {} @fields = {} self.reset end
Public Instance Methods
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
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 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 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
Returns true
if there were arguments given.
# File lib/strelka/paramvalidator.rb, line 829 def args? return !self.fields.empty? end
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
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
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
Returns true
if there were no arguments given.
# File lib/strelka/paramvalidator.rb, line 823 def empty? return self.fields.empty? end
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
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
Returns true
if any fields are missing or contain invalid values.
# File lib/strelka/paramvalidator.rb, line 858 def errors? return !self.okay? end
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
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
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
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
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
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 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
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
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
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
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
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 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
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
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
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
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
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 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
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?
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 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 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