Class: Strelka::ParamValidator

Inherits:
Object
  • Object
show all
Extended by:
Forwardable, Loggability, MethodUtilities
Includes:
DataUtilities
Defined in:
lib/strelka/paramvalidator.rb

Overview

A validator for user parameters.

Usage

require 'strelka/paramvalidator'

validator = Strelka::ParamValidator.new

Add validation criteria for input parameters

validator.add( :name, /^(?\S+), (?\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[:firstname] tmpl.lastname = validator[:lastname] tmpl.email = validator tmpl.feedback = validator 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

Defined Under Namespace

Classes: BuiltinConstraint, Constraint, RegexpConstraint

Constant Summary collapse

PARAMS_HASH_RE =

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

/^([^\[]+)(\[.*\])?(.)?.*$/
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.

Regexp.union( '^', '$', '\\A', '\\z', '\\Z' )

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MethodUtilities

attr_predicate, attr_predicate_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader

Methods included from DataUtilities

autovivify, deep_copy, stringify_keys, symbolify_keys

Constructor Details

#initializeParamValidator

Create a new Strelka::ParamValidator object.



551
552
553
554
555
556
# File 'lib/strelka/paramvalidator.rb', line 551

def initialize
	@constraints = {}
	@fields      = {}

	self.reset
end

Instance Attribute Details

#constraintsObject (readonly)

The constraints hash



573
574
575
# File 'lib/strelka/paramvalidator.rb', line 573

def constraints
  @constraints
end

#fieldsObject (readonly)

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



576
577
578
# File 'lib/strelka/paramvalidator.rb', line 576

def fields
  @fields
end

Instance Method Details

#[](key) ⇒ Object

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



809
810
811
812
# File 'lib/strelka/paramvalidator.rb', line 809

def []( key )
	self.validate unless self.validated?
	return @valid[ key.to_sym ]
end

#[]=(key, val) ⇒ Object

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



817
818
819
820
# File 'lib/strelka/paramvalidator.rb', line 817

def []=( key, val )
	@parsed_params = nil
	@valid[ key.to_sym ] = val
end

#add(name, *args, &block) ⇒ Object

:call-seq:

add( name, *flags )
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.



604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/strelka/paramvalidator.rb', line 604

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) ⇒ Object

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.



732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
# File 'lib/strelka/paramvalidator.rb', line 732

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?Boolean Also known as: has_args?

Returns true if there were arguments given.

Returns:

  • (Boolean)


830
831
832
# File 'lib/strelka/paramvalidator.rb', line 830

def args?
	return !self.fields.empty?
end

#constraint_regexp_for(name) ⇒ Object

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

Raises:

  • (ScriptError)


760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
# File 'lib/strelka/paramvalidator.rb', line 760

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

#descriptionsObject

Hash of field descriptions



672
673
674
675
676
# File 'lib/strelka/paramvalidator.rb', line 672

def descriptions
	return self.constraints.each_with_object({}) do |(field,constraint), hash|
		hash[ field ] = constraint.description
	end
end

#descriptions=(new_descs) ⇒ Object

Set field descriptions en masse to new_descs.



680
681
682
683
684
685
686
# File 'lib/strelka/paramvalidator.rb', line 680

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?Boolean

Returns true if there were no arguments given.

Returns:

  • (Boolean)


824
825
826
# File 'lib/strelka/paramvalidator.rb', line 824

def empty?
	return self.fields.empty?
end

#error_fieldsObject

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



874
875
876
# File 'lib/strelka/paramvalidator.rb', line 874

def error_fields
	return self.missing | self.invalid.keys
end

#error_messages(include_unknown = false) ⇒ Object

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



881
882
883
884
885
886
887
888
# File 'lib/strelka/paramvalidator.rb', line 881

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?Boolean Also known as: has_errors?

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

Returns:

  • (Boolean)


859
860
861
# File 'lib/strelka/paramvalidator.rb', line 859

def errors?
	return !self.okay?
end

#get_description(field) ⇒ Object

Get the description for the specified field.



690
691
692
693
# File 'lib/strelka/paramvalidator.rb', line 690

def get_description( field )
	constraint = self.constraints[ field.to_sym ] or return nil
	return constraint.description
end

#initialize_copy(original) ⇒ Object

Copy constructor.



560
561
562
563
564
565
# File 'lib/strelka/paramvalidator.rb', line 560

def initialize_copy( original )
	fields       = deep_copy( original.fields )
	self.reset
	@fields      = fields
	@constraints = deep_copy( original.constraints )
end

#inspectObject

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



656
657
658
659
660
661
662
663
664
665
666
667
668
# File 'lib/strelka/paramvalidator.rb', line 656

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

#invalidObject

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



844
845
846
847
# File 'lib/strelka/paramvalidator.rb', line 844

def invalid
	self.validate unless self.validated?
	return @invalid
end

#invalid_param_errorsObject

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



903
904
905
906
907
908
909
# File 'lib/strelka/paramvalidator.rb', line 903

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) ⇒ Object

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



924
925
926
927
928
# File 'lib/strelka/paramvalidator.rb', line 924

def merge( params )
	copy = self.dup
	copy.merge!( params )
	return copy
end

#merge!(params) ⇒ Object

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



933
934
935
936
937
# File 'lib/strelka/paramvalidator.rb', line 933

def merge!( params )
	return if params.empty?
	self.log.debug "Merging parameters for revalidation: %p" % [ params ]
	self.revalidate( params )
end

#missingObject

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



837
838
839
840
# File 'lib/strelka/paramvalidator.rb', line 837

def missing
	self.validate unless self.validated?
	return @missing
end

#missing_param_errorsObject

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



892
893
894
895
896
897
898
# File 'lib/strelka/paramvalidator.rb', line 892

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?Boolean

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

Returns:

  • (Boolean)


867
868
869
# File 'lib/strelka/paramvalidator.rb', line 867

def okay?
	return (self.missing.empty? && self.invalid.empty?)
end

#override(name, *args, &block) ⇒ Object

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

Raises:

  • (ArgumentError)


625
626
627
628
629
630
631
632
633
634
635
# File 'lib/strelka/paramvalidator.rb', line 625

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_namesObject

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



639
640
641
# File 'lib/strelka/paramvalidator.rb', line 639

def param_names
	return self.constraints.keys.map( &:to_s ).sort
end

#resetObject

Reset the validation state.



585
586
587
588
589
590
591
592
593
# File 'lib/strelka/paramvalidator.rb', line 585

def reset
	self.log.debug "Resetting validation state."
	@validated     = false
	@valid         = {}
	@parsed_params = nil
	@missing       = []
	@unknown       = []
	@invalid       = {}
end

#revalidate(params = {}) ⇒ Object

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



752
753
754
755
756
# File 'lib/strelka/paramvalidator.rb', line 752

def revalidate( params={} )
	merged_fields = self.fields.merge( params )
	self.reset
	self.validate( merged_fields )
end

#to_sObject

Stringified description of the validator



645
646
647
648
649
650
651
652
# File 'lib/strelka/paramvalidator.rb', line 645

def to_s
    "%d parameters (%d valid, %d invalid, %d missing)" % [
        self.fields.size,
        self.valid.size,
        self.invalid.size,
        self.missing.size,
    ]
end

#unknownObject

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



852
853
854
855
# File 'lib/strelka/paramvalidator.rb', line 852

def unknown
	self.validate unless self.validated?
	return @unknown
end

#unknown_param_errorsObject

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.



914
915
916
917
918
919
# File 'lib/strelka/paramvalidator.rb', line 914

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

#validObject

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



786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
# File 'lib/strelka/paramvalidator.rb', line 786

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) ⇒ Object

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



698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
# File 'lib/strelka/paramvalidator.rb', line 698

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?Object

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



581
# File 'lib/strelka/paramvalidator.rb', line 581

attr_predicate_accessor :validated?

#values_at(*selector) ⇒ Object

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



942
943
944
945
# File 'lib/strelka/paramvalidator.rb', line 942

def values_at( *selector )
	selector.map!( &:to_sym )
	return self.valid.values_at( *selector )
end