Ralf Peine

NAME

Scalar::Validation - Makes validation of scalar values or function (sub) parameters easy and uses pure Perl.

VERSION

This documentation refers to version 0.700 of Scalar::Validation

SYNOPSIS

  use Scalar::Validation qw(:all);

  my $int_1    = validate int_1   => Int   => 123;
  my $float_1  = validate float_1 => Float => 3.1415927;

  my $para_1   = par  parameter_1 => -Range => [1,5] => Int => shift;
  my $exponent = npar -exponent   => -Range => [1,5] => Int => \%options;

  my $para_2     = parameter       parameter_1 => -Range => [1,5] => Int => shift;
  my $exponent_2 = named_parameter -exponent   => -Range => [1,5] => Int => \%options;

  my $int_2    = validate (int_2    => -And => [Scalar => 'Int'],  123);
  my $int_3    = validate (int_3    => -Or  => [Int => 'CodeRef'], 123);
  my $code_ref = validate (code_ref => -Or  => [Int => 'CodeRef'], sub { 123; });

  my $enum_abc = validate (parameter => -Enum => {a => 1, b => 1, c => 1}, 'c');
  my $enum_abc = validate (parameter => -Enum => [ qw (a b c) ], 'c');

  my $int_4    = validate (int_4   => -Optional =>  Int   =>                             undef);
  my $int_5    = validate (int_5   => -Optional => -And   => [Scalar => Int => 0] =>     undef);
  my $int_6    = validate (int_6   => -Optional => -Or    => [Int => CodeRef => 0] =>    undef);
  my $enum_2   = validate (enum_2  => -Optional => -Enum  => {a => 1, b => 1, c => 1} => undef);
  my $range_1  = validate (range_1 => -Optional => -Range => [1,5] => Int =>             undef);

  my $float_1  = validate (float_1 => -Default => '1e1'  => Float                           => undef);
  my $float_2  = validate (float_2 => -Default => '-3.1' => -And  => [Scalar => Float => 0] => undef);

  my $rounded  = validate_and_correct ([rounded => Int => 1.1],
                                       {  -correction => sub {
                                             my $float = par (rounded => Float => shift);
                                             return int($float + 0.5) if $float > 0;
                                             return int($float - 0.5);
                                           },
                                       });

Just checks, never dies:

  is_valid (valid_1 => Int => 123);   # is valid,     returns 1;
  is_valid (valid_2 => Int => 1.23);  # is not valid, returns 0;
  is_valid (valid_3 => Int => 'a');   # is not valid, returns 0;
  is_valid (valid_4 => Int => undef); # is not valid, returns 0;

Free defined rules or wheres only (also for validate(...))

  my $value = 2;

  # be careful, doesn't check that $_ is an integer!
  is_valid (free_where_greater_zero => sub { $_ && $_ > 0} => $value);  # is valid, returns 1

  is_valid (free_rule_greater_zero => { -as      => Int =>
                                        -where   => sub { $_ > 0},
                                        -message => sub { "$_ is not > 0" },
                                      }
            => $value); # is valid, returns 1

  my $my_rule = { -as => Int => -where => sub { $_ && $_ > 0} => -message => sub { "$_ is not > 0" }};

  is_valid (free_rule_greater_zero => $my_rule => $value);              # is valid, returns 1

Managing Rules

  declare_rule (
      NegativeInt => -as      => Int =>           # Parent rule is optional
                     -where   => sub { $_ < 0 },
                     -message => sub { "value $_ is not a negative integer" },
  );

  replace_rule (
      NegativeInt => -as      => Int =>           # Parent rule is optional
                     -where   => sub { $_ =< 0 },
                     -message => sub { "value $_ is not a negative integer" },
  );

  delete_rule ('NegativeInt');

  rule_known(Unknown  => 1); # returns 0 (false)
  rule_known(Negative => 1); # returns 1 (true)

Dynamic Rules For Comparison

  par       parameter => greater_than  4 => Int    => shift;     # = 5
  validate  parameter => g_t           4 => Int    => $value;    # = 6
  
  is_valid (parameter => greater_equal 4 => Float  => $value);   # = 4.1
  is_valid (parameter => g_e           4 => Int    => $value);   # = 4
  
  npar     -parameter => less_than  4 => Int       => \%options;
  is_valid (parameter => l_t       (4 => 'Float')  => $value);   # = 4.1
  
  validate (parameter => less_equal (4 => 'Float') => $value);   # = 4.1
  is_valid (parameter => l_e         4 => Int      => $value);   # = 3
  
  my $value = 'Text';
  is_valid (parameter => equal_to (text => String) => lc($value)); # compares as String
  
  is_valid (parameter => equal_to (4 => String) => '4.0'); # not valid, compares as String
  is_valid (parameter => equal_to (4 => Float)  =>  4.0);  # valid,    compares as number
  is_valid (parameter => equal_to (4 => Int)    =>  4.0);  # valid !!, compares as number

Dynamic Rules To Check Types

  my $animal = par is_a  Animal => shift;
  my $person = par is_a (Person => shift);
  my $tree   = par is_a (Tree),    shift;

Validation Modes

  local ($Scalar::Validation::fail_action, $Scalar::Validation::off)
       = prepare_validation_mode('die');  
  local ($Scalar::Validation::fail_action, $Scalar::Validation::off)
       = prepare_validation_mode(warn => 1);

  local $Scalar::Validation::fail_action = sub { my_log('Error', $@); return undef; }

DESCRIPTION

You should not use this module without reason.

If possible, choose Moose.

If using Moo, there is the pure Perl module Type::Params which does not need be compiled. Or choose Kavorka with nice syntax, but you need to compile the modules. Or use experimental type checks of Perl 5.20 .

If that all is not possible or you want different run modes for validation, then you should take a look.

This class implements a fast and flexible validation for scalars. It is implemented functional to get speed and some problems using global rules for all ;).

It is safer to use than Type::Params, but slower.

It is written to work well with antique Perl versions like 5.6 and 5.8.

It is also written to be used in antique Code written for such antique Perl versions. You can add it sub for sub. You can also validate single values, that are not call parameters. Thats the name is coming from.

You can declare and test your own rules, give every process or sub in process its own rules, if you want.

Validate Subs

Following validation functions exist:

  # --- sub parameters
  parameter(...);       # Positional parameter
    par(...);           # Alias for parameter()

  named_parameter(...);
    n_par(...);         # Alias for named_parameter()

  # --- just validate a value, that is not a sub parameter
  validate(...);

  # --- same as validate, but with option to correct invalid value
  validate_and_correct(...);

  is_valid(...);

validate(), parameter() and par()

Different names for same functionality.

But

  validate(...);

is ignored while building up meta information.

Use like

  my $var_float = validate ('PI is a float' => Float => $PI);
  my $par_int   = par      (par_int         => Int   => shift);

First argument is a free name of the check done. If used as parameter check for subs it is the 'name' of the parameter.

Last argument holds the value to be checked. It has to be a scalar, and therefore the module was named Scalar::Validation.

Optional last argument: After the value argument can be added a sub to print out an own error message instead of the default error message:

  my $var_float = validate ('PI is a float' => Float => $PI => sub { 'wrong defined $PI: '.$_ } );

All parameters after first before value argument are used to select or define "validation rules": 'Float' and 'Int' in this example.

named_parameter(), n_par()

These subs extract named parameters out of a parameter_hash_ref. Key and value will be deleted from hash during validation. After processing all parameters hash_ref should be empty.

  my $par_1_int   = npar            (par_1 => Int   => \%parameters);
  my $par_2_float = named_parameter (par_2 => Float => \%parameters);

First argument ($key) is the key of the parameter. Last argument ($parameters) has to be a hash_ref.

Without these subs you would have to implement for reading par_1:

  my $key       = 'par_1';
  my $value     = delete $parameters->{$key};
  my $par_1_int = par ($key => Int   => $value);

It could be done in one line, but this line will be complicated and not easy to understand. The key value is needed twice and that can cause Copy-Paste-Errors.

validate_and_correct()

Description is still missing, sorry ...

Dies by error message

On default, application dies with error message, if data checked by named_parameter(...) or validate(...) is not valid.

  validate (parameter => -And => [Scalar => 'Int'],  {} );
  validate (parameter => -And => [Scalar => 'Int'],  [] );
  validate (parameter => -And => [Scalar => 'Int'],  sub { 'abc'; });

Just check without die

is_valid(...) just does validation and returns 1 in case on success and 0 in case of fail.

  print is_valid(parameter => -And => [Scalar => 'Int'],  123) ." => 123 is int\n";
  print is_valid(parameter => -And => [Scalar => 'Int'],  {} ) ." => {} is no scalar\n";
  print is_valid(parameter => -And => [Scalar => 'Int'],  [] ) ." => [] is no scalar\n";
  print is_valid(parameter => -And => [Scalar => 'Int'],  sub { 'abc'; }) ." => sub { 'abc'; } is no scalar\n";

Avoid trouble using invalid data

If a validation fails and validation mode is not set as 'die', you probably will run in trouble afterwards, when you use invalid data.

Therefore do

  my $trouble_level = p_start;

  # add your code

  # fire exit, if validation does not die
  return undef if validation_trouble($trouble_level);

or something similar.

is_valid(...) does not rise $trouble_level, as to be expected. All other validation subs do!

Get validation messages

Per default, no messages are stored to increase performance. To store messages, the message store has to be localized into an array_ref.

This is the only safe way to deal with recursive calls and die! So use a block like this to store messages

  my @messages;
  {
      local ($Scalar::Validation::message_store) = [];
  
      my $result = is_valid(parameter => -And => [Scalar => 'Int'],  {} );
              
      @messages = @{validation_messages()} unless $result;
  }

As parameter check for indexed arguments

Scalar::Validation can be also used a parameter check for unnamed and named sub parameters. parameters_start (Shorthand: p_start) starts parameter validation and gives back current trouble level. parameters_end \@_; ensures, that all parameters are processed. Otherwise it rises the usual validation error. Shorthand: p_end.

  sub create_some_polynom {
      my $trouble_level = p_start;

      my $max_potenz = par maximum_potenz => -Range => [1,5] => Int => shift;
      # additional parameters ...

      p_end \@_;

      # fire exit, if validation does not die
      return undef if validation_trouble($trouble_level);

      # --- run sub -------------------------------------------------

      my $polynom = '';      map { $polynom .= " + ".int (100*rand())."*x^".($max_potenz-$_); } (0..$max_potenz);

      return $polynom;
  };

  print create_some_polynom(1)."\n";
  print create_some_polynom(2)."\n";
  print create_some_polynom(3)."\n";
  print create_some_polynom(4)."\n";
  print create_some_polynom(5)."\n";

Dies by error message

  print create_some_polynom("four")."\n";
  print create_some_polynom(5.5)."\n";
  print create_some_polynom(6)."\n";
  print create_some_polynom(6, 1)."\n";

As parameter check for named arguments

Named arguments can also be handled. This needs more runtime than the indexed variant.

convert_to_named_params() does a safe conversion by validate().

  sub create_some_polynom_named {
      my $trouble_level = p_start;

      my %pars = convert_to_named_params \@_;

      my $max_potenz = npar -maximum_potenz => -Range => [1,5] => Int => \%pars;
      # additional parameters ...

      parameters_end \%pars;

      # fire exit, if validation does not die
      return undef if validation_trouble($trouble_level);

      # --- run sub -------------------------------------------------

      my $polynom = '';
      map { $polynom .= " + ".int (100*rand())."*x^".($max_potenz-$_); } (0..$max_potenz);

      return $polynom;
  };

  print create_some_polynom_named(-maximum_potenz => 4);

Rules

declare_rule(...)

You can and should create your own rules, i.e.

  declare_rule (
      Positive =>  -as      => Int =>           # Parent rule is optional
                   -where   => sub { $_ >= 0 },
                   -message => sub { "value $_ is not a positive integer" },
                   -owner   => 'Me'             # Use your own name
                   -description => "This rule checks if $_ >= 0 and is an Integer"
  );

  rule_known(Unknown  => 1); # returns 0 (false)
  rule_known(Positive => 1); # returns 1 (true)

The value to be validated is stored in variable $_. For

  -message => sub { "my message for wrong value $_."}

it is enclosed in single ticks, so that you get the following output for $_ = "Garfield":

  my message for wrong value 'Garfield'.

delete_rule(...)

deletes_rule($rule) deletes rule $rule. It calls current validation fail method, if $rule not set or rule cannot be found.

  delete_rule ('NegativeInt');

replace_rule(...)

replace_rule($rule = ...)> deletes rule $rule first and then declares it. Same arguments as for declare_rule;

  replace_rule (
      NegativeInt => -as      => Int =>           # Parent rule is optional
                     -where   => sub { $_ =< 0 },
                     -message => sub { "value $_ is not a negative integer or 0" },
  );

Documentation Of Rules

To get a html documentation (or text or csv) of all existing rules, you may use Report::Porf, one of my other modules, like this:

  use Report::Porf qw(:all);
  use Scalar::Validation qw(:all);

  my $rules_ref = get_rules();
  my @rule_info = map { $rules_ref->{$_} } sort keys %$rules_ref;

  auto_report(\@rule_info, "rule_info.html");

All rules - also yours - are listed in the html file.

Main Rules

There are some main rules, that should not be changed by you, because they are used internally. If defined wrong, Scalar::Validation may stop working or doing strange things...

    Defined
    Filled
    Empty
    Optional
    String
    Int
    Even
    Scalar
    Ref
    ArrayRef
    HashRef
    CodeRef
    Class

Special Rules

There are some special rules, that cannot be changed. Those rules start with an '-' char in front:

 -Optional    # value may be undefined. If not, use following rule
 -Default     # if value not defined or eq '', use given default value instead
 -And         # all rules must be ok
 -Or          # at least one rule must be ok
 -Enum        # for easy defining enumeration on the fly, by array_ref or hash_ref
 -Range       # Intervall: [start, end] => type
 -RefEmpty    # array_ref: scalar (@$array_ref)     == 0
              # hash_ref:  scalar (keys %$hash_ref) == 0

Reason is, that they combine other rules or have more or different parameters than a "normal" rule or using own implementation just to speed up.

All normal rules should not start with a '-', but it is not forbidden to do so.

  my $var_float = validate ('PI is a float' => -Optional => Float => $PI => sub { 'wrong defined $PI: '.$_ } );

This rule does not die, if $PI is undef because of -Optional in front.

Create Own Validation Module

You should not use Scalar::Validation direct in your code.

Better is creating an own module My::Validation, that adds the rules you need and only exports the subs the developers in your project should use:

  use My::Validation;

  my $v = validate v => my_type => new MyType();

You can also define your own fail_action by:

  local $Scalar::Validation::fail_action = sub { my_log('Error', $@); return undef; }

to write validation messages into your own log system by using my_log(...);. See "Validation Modes" for details.

Dealing with XSD

In this case My::Validation should create rules out of XML datatypes after reading in a XSD file. So rules are dynamic and your application can handle different XSD definitions without knowing something about XSD outside of this module.

Also you can filter XSD type contents, i.e. for enmuerations: Allowing not all possible values in UI or remove entries only for compatibility with old versions.

And your Application or GUI doesn't need to know about it.

Validation Modes

Validation modes are selected by

  local ($Scalar::Validation::fail_action, $Scalar::Validation::off) = prepare_validation_mode($mode);

There are 4 predefined validation modes:

  die
  warn
  silent
  off

is_valid()

is_valid() uses a special validation mode independent from the followings. It will do the checks in all cases except mode 'off'.

Validation Mode 'die' (default)

The validation methods call croak "validation message"; in case of failures or a rule fails. Your script (or this part of your script) will die.

Validation Mode 'warn'

The validation methods call carp "validation message"; in case of failures or a rule fails. Your get warnings for every failed rule.

Your script (or this part of your script) will NOT die.

It will continue work. In critical cases you should take care, that process will be stopped: Use validation_trouble()!

Validation Mode 'silent'

The validation methods just store messages, if there is a message store available. No messages will be printed out.

Your script (or this part of your script) will NOT die.

It will continue work. In critical cases you should take care, that process will be stopped: Use validation_trouble()!

Validation Mode 'off'

The validation methods just give back the value. They even don't process the call parameters of the validation routines.

is_valid() is also turned off and returns 1 for all calls.

Be careful! Now you are playing with dynamite! But you are fast. If you explode, just try switch validation on, if you are still alive.

Faster than Validation Mode 'off'

Remove validation calls

Remove all validation calls from your code and fall back to era before Scalar::Validation.

Be careful! Now you are playing with nitroglycerin! But you cannot be faster.

Duplicate parameter management

Having two parameter managements, switch between them by testing $Scalar::Validation::off:

  sub create_some_polynom {
      my ($max_potenz,
      ) = @_;

      if ($Scalar::Validation::off) {
          my $trouble_level = p_start;

          $max_potenz = par maximum_potenz => -Range => [1,5] => Int => shift;
          # additional parameters ...
          
          p_end \@_;
          
          # fire exit, if validation does not die
          return undef if validation_trouble($trouble_level);
      }

      # --- run sub -------------------------------------------------

      my $polynom = '';
      map { $polynom .= " + ".int (100*rand())."*x^".($max_potenz-$_); } (0..$max_potenz);

      return $polynom;
  };

Now you have only one extra if statement compared to version without checks. But you have duplicate parameter management. So only choose this variant if performance is a real problem for your sub.

Be careful! You are still playing with dynamite! But you are fast. If you explode, just try switch validation on, if you are still alive.

Traps

  my $unvalidated  = par => value => Int => shift;   # value is just used without checking
  my $string_shift = par    value => Int => shift => sub { "$_ is still 'shift'!!. Why?"};

par => value

  my $unvalidated  = par => value => Int => shift;   # value is just used without checking

par, npar, validate, ... are functions, not keywords. The mistake here is to use " => " instead of ",". Why? The special comma operator quotes the left argument of

  par => value

so you get

  'par', value

and now par is a string and not a function. You get the last value of the list, and that is shift! NO VALIDATION! So don't forget to write tests with invalid data to detect this trap.

By using brackets

  my $unvalidated  = par (value => Int => shift);

you avoid this trap.

shift => sub { ... }

Another trap

  my $string_shift = par    value => Int => shift => sub { "$_ is still 'shift!!'. Why?"};

The mistake here is also to use " => " instead of ","

  shift => sub { }

It will be interpreted as

  'shift', sub { }

and so you get constant string 'shift' instead of the next value from parameter stack.

Or if both traps are combined, you get the last argument, that may be a coderef.

You cannot avoid this trap by safe coding, but it will be easy detected by unit testing.

More Examples

Have a look into the examples directory or Scalar-Validation.t to see what else is possible.

Extraction Of Meta Information

It is possible to extract meta information out of subs or classes, if subs are build by following structure:

  package MyClass;

  sub my_sub {
      my $trouble_level = p_start;

      my $first_par = par first_par => Int => shift;
      # additional parameters ...

      my %pars = convert_to_named_params \@_;

      my $max_potenz = npar -first_named => PositiveFloat => \%pars;
      # additional named parameters ...

      p_end \%pars;

      # needed to exit sub in meta extraction mode
      return undef if validation_trouble($trouble_level);

      # ------------------

      # Code of sub doing something
  }

  sub my_next_sub { ...

Extracting meta information will be done as followed:

  use Scalar::Validation qw(:all);
  use MyValidation; # if existing!

  meta_info_clear();

  # ------------------------------------------------------------------------------

  my $my_class = build_meta_info_for_module('MyClass');

  # or, if constructor needs arguments
  $my_class = build_meta_info_for_module('MyClass',
              sub { return MyClass->new(-name => 'AnyPerson') });

  # call without paramters, they are not needed for meta information mode
  $my_class->my_sub();
  $my_class->my_next_sub();

  # ------------------------------------------------------------------------------

  # next classes

  # ------------------------------------------------------------------------------
  end_meta_info_gen();

  print "\n# === Meta Class Information Dump ============================\n"
    .Dumper(get_meta_info());

This will print out

  $VAR = {'MyClass' => {
              'subs' => 
              {
                  'my_next_sub' => {
                      'params'  => [
                          # this sub has no parameters
                      ],
                  },
                  'my_sub' => {
                      'params'  => [
                          {
                              'kind' => 'Positional',
                              'name' => 'first_par',
                              'rule' => 'Int',
                          },
                          {
                              'kind' => 'Named',
                              'name' => '-first_named',
                              'rule' => 'PositiveFloat',
                          },
                      ],
                  },
              },
         };

as hash_ref of classes/packages containing methods/subs containing an array of parameters for each sub. The additional keys 'subs' and 'params' let this meta model be easy extended later on.

Or use list_meta_info() to get an ordered list containing one hash per parameter like:

  [ { 'module' => 'MyClass',
      'sub'    => 'my_sub',
      'kind'   => 'Positional',
      'name'   => 'first_par',
      'rule'   => 'Int',
    },
  ...
  ]

Coming Soon

if_par()

if_par sets value only, if matching to rule. Otherwise returns undef.

  my $file_option = if_par_named  (-file => Defined => \%options);

or

  my $file_option = if_par_indexed (file => Defined => shift);
  
  my $file_name   = if_par (file => FileName   => $file_option);
  my $file_handle = if_par (file => FileHandle => $file_option);

  if_par_end $file_option; # raises $trouble_level if $file_option is not Empty

Both $file_name and $file_handle, one or none can be set, that depends on the rules.

$file_option is a hash containing keys -value, -matched (counter), -rules (string of all tried).

if_par_end checks that at least one rule fits.

SEE ALSO Moose, Moo, Type::Params and Kavorka

Use Moose if possible. If not possible, have a look to Moo and Type::Params. Or choose Kavorka with nice syntax, but you need to compile the modules. Or use experimental type checks of Perl 5.20 .

Differences to Type::Params

Type::Params uses the state pragma, which comes in Perl Version 12. This is much faster than Scalar::Validation, but cannot be run with earlier versions than Perl V12.

Scalar::Validation doesn't use state and runs even with Perl V5.6, I tested it on sun.

Also there is no validation mode in Type::Params, it dies in case of validaiton failure.

LICENSE AND COPYRIGHT

Copyright (c) 2014 by Ralf Peine, Germany. All rights reserved.

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.6.0 or, at your option, any later version of Perl 5 you may have available.

DISCLAIMER OF WARRANTY

This library is distributed in the hope that it will be useful, but without any warranty; without even the implied warranty of merchantability or fitness for a particular purpose.