summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Attribute/Params/Validate.pm208
-rw-r--r--lib/Params/Validate.pm900
-rw-r--r--lib/Params/Validate/Constants.pm39
-rw-r--r--lib/Params/Validate/PP.pm735
-rw-r--r--lib/Params/Validate/XS.pm51
-rw-r--r--lib/Params/Validate/XS.xs1811
-rw-r--r--lib/Params/ValidatePP.pm9
-rw-r--r--lib/Params/ValidateXS.pm9
8 files changed, 3762 insertions, 0 deletions
diff --git a/lib/Attribute/Params/Validate.pm b/lib/Attribute/Params/Validate.pm
new file mode 100644
index 0000000..f72c16a
--- /dev/null
+++ b/lib/Attribute/Params/Validate.pm
@@ -0,0 +1,208 @@
+package Attribute::Params::Validate;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.20';
+
+use attributes;
+
+use Attribute::Handlers 0.79;
+
+# this will all be re-exported
+use Params::Validate qw(:all);
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+my %tags = (
+ types => [
+ qw( SCALAR ARRAYREF HASHREF CODEREF GLOB GLOBREF SCALARREF HANDLE UNDEF OBJECT )
+ ],
+);
+
+our %EXPORT_TAGS = (
+ 'all' => [ qw( validation_options ), map { @{ $tags{$_} } } keys %tags ],
+ %tags,
+);
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} }, 'validation_options' );
+
+
+sub UNIVERSAL::Validate : ATTR(CODE, INIT) {
+ _wrap_sub( 'named', @_ );
+}
+
+sub UNIVERSAL::ValidatePos : ATTR(CODE, INIT) {
+ _wrap_sub( 'positional', @_ );
+}
+
+sub _wrap_sub {
+ my ( $type, $package, $symbol, $referent, $attr, $params ) = @_;
+
+ my @p = ref $params ? @{$params} : $params;
+
+ my $subname = $package . '::' . *{$symbol}{NAME};
+
+ my %attributes = map { $_ => 1 } attributes::get($referent);
+ my $is_method = $attributes{method};
+
+ {
+ no warnings 'redefine';
+ no strict 'refs';
+
+ # An unholy mixture of closure and eval. This is done so that
+ # the code to automatically create the relevant scalars from
+ # the hash of params can create the scalars in the proper
+ # place lexically.
+
+ my $code = <<"EOF";
+sub
+{
+ package $package;
+EOF
+
+ $code .= " my \$object = shift;\n" if $is_method;
+
+ if ( $type eq 'named' ) {
+ $params = {@p};
+ $code .= " Params::Validate::validate(\@_, \$params);\n";
+ }
+ else {
+ $code .= " Params::Validate::validate_pos(\@_, \@p);\n";
+ }
+
+ $code .= " unshift \@_, \$object if \$object;\n" if $is_method;
+
+ $code .= <<"EOF";
+ \$referent->(\@_);
+}
+EOF
+
+ my $sub = eval $code;
+ die $@ if $@;
+
+ *{$subname} = $sub;
+ }
+}
+
+1;
+
+# ABSTRACT: Define validation through subroutine attributes
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Attribute::Params::Validate - Define validation through subroutine attributes
+
+=head1 VERSION
+
+version 1.20
+
+=head1 SYNOPSIS
+
+ use Attribute::Params::Validate qw(:all);
+
+ # takes named params (hash or hashref)
+ # foo is mandatory, bar is optional
+ sub foo : Validate( foo => 1, bar => 0 )
+ {
+ # insert code here
+ }
+
+ # takes positional params
+ # first two are mandatory, third is optional
+ sub bar : ValidatePos( 1, 1, 0 )
+ {
+ # insert code here
+ }
+
+ # for some reason Perl insists that the entire attribute be on one line
+ sub foo2 : Validate( foo => { type => ARRAYREF }, bar => { can => [ 'print', 'flush', 'frobnicate' ] }, baz => { type => SCALAR, callbacks => { 'numbers only' => sub { shift() =~ /^\d+$/ }, 'less than 90' => sub { shift() < 90 } } } )
+ {
+ # insert code here
+ }
+
+ # note that this is marked as a method. This is very important!
+ sub baz : Validate( foo => { type => ARRAYREF }, bar => { isa => 'Frobnicator' } ) method
+ {
+ # insert code here
+ }
+
+=head1 DESCRIPTION
+
+The Attribute::Params::Validate module allows you to validate method
+or function call parameters just like Params::Validate does. However,
+this module allows you to specify your validation spec as an
+attribute, rather than by calling the C<validate> routine.
+
+Please see Params::Validate for more information on how you can
+specify what validation is performed.
+
+=head2 EXPORT
+
+This module exports everything that Params::Validate does except for
+the C<validate> and C<validate_pos> subroutines.
+
+=head2 ATTRIBUTES
+
+=over 4
+
+=item * Validate
+
+This attribute corresponds to the C<validate> subroutine in
+Params::Validate.
+
+=item * ValidatePos
+
+This attribute corresponds to the C<validate_pos> subroutine in
+Params::Validate.
+
+=back
+
+=head2 OO
+
+If you are using this module to mark B<methods> for validation, as
+opposed to subroutines, it is crucial that you mark these methods with
+the C<:method> attribute, as well as the C<Validate> or C<ValidatePos>
+attribute.
+
+If you do not do this, then the object or class used in the method
+call will be passed to the validation routines, which is probably not
+what you want.
+
+=head2 CAVEATS
+
+You B<must> put all the arguments to the C<Validate> or C<ValidatePos>
+attribute on a single line, or Perl will complain.
+
+=head1 SEE ALSO
+
+Params::Validate
+
+=head1 AUTHORS
+
+=over 4
+
+=item *
+
+Dave Rolsky <autarch@urth.org>
+
+=item *
+
+Ilya Martynov <ilya@martynov.org>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2001 - 2015 by Dave Rolsky and Ilya Martynov.
+
+This is free software, licensed under:
+
+ The Artistic License 2.0 (GPL Compatible)
+
+=cut
diff --git a/lib/Params/Validate.pm b/lib/Params/Validate.pm
new file mode 100644
index 0000000..7a5ff01
--- /dev/null
+++ b/lib/Params/Validate.pm
@@ -0,0 +1,900 @@
+package Params::Validate;
+
+use 5.008001;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.20';
+
+use Exporter;
+use Module::Implementation;
+use Params::Validate::Constants;
+
+use vars qw( $NO_VALIDATION %OPTIONS $options );
+
+our @ISA = 'Exporter';
+
+my @types = qw(
+ SCALAR
+ ARRAYREF
+ HASHREF
+ CODEREF
+ GLOB
+ GLOBREF
+ SCALARREF
+ HANDLE
+ BOOLEAN
+ UNDEF
+ OBJECT
+);
+
+our %EXPORT_TAGS = (
+ 'all' => [
+ qw( validate validate_pos validation_options validate_with ),
+ @types
+ ],
+ types => \@types,
+);
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} }, 'set_options' );
+our @EXPORT = qw( validate validate_pos );
+
+$NO_VALIDATION = $ENV{PERL_NO_VALIDATION};
+
+{
+ my $loader = Module::Implementation::build_loader_sub(
+ implementations => [ 'XS', 'PP' ],
+ symbols => [
+ qw(
+ validate
+ validate_pos
+ validate_with
+ validation_options
+ set_options
+ ),
+ ],
+ );
+
+ $ENV{PARAMS_VALIDATE_IMPLEMENTATION} = 'PP' if $ENV{PV_TEST_PERL};
+
+ $loader->();
+}
+
+1;
+
+# ABSTRACT: Validate method/function parameters
+
+__END__
+
+=pod
+
+=head1 NAME
+
+Params::Validate - Validate method/function parameters
+
+=head1 VERSION
+
+version 1.20
+
+=head1 SYNOPSIS
+
+ use Params::Validate qw(:all);
+
+ # takes named params (hash or hashref)
+ sub foo {
+ validate(
+ @_, {
+ foo => 1, # mandatory
+ bar => 0, # optional
+ }
+ );
+ }
+
+ # takes positional params
+ sub bar {
+ # first two are mandatory, third is optional
+ validate_pos( @_, 1, 1, 0 );
+ }
+
+ sub foo2 {
+ validate(
+ @_, {
+ foo =>
+ # specify a type
+ { type => ARRAYREF },
+ bar =>
+ # specify an interface
+ { can => [ 'print', 'flush', 'frobnicate' ] },
+ baz => {
+ type => SCALAR, # a scalar ...
+ # ... that is a plain integer ...
+ regex => qr/^\d+$/,
+ callbacks => { # ... and smaller than 90
+ 'less than 90' => sub { shift() < 90 },
+ },
+ }
+ }
+ );
+ }
+
+ sub callback_with_custom_error {
+ validate(
+ @_,
+ {
+ foo => callbacks => {
+ 'is an integer' => sub {
+ return 1 if $_[0] =~ /^-?[1-9][0-9]*$/;
+ die "$_[0] is not a valid integer value";
+ },
+ }
+ }
+ );
+ }
+
+ sub with_defaults {
+ my %p = validate(
+ @_, {
+ # required
+ foo => 1,
+ # $p{bar} will be 99 if bar is not given. bar is now
+ # optional.
+ bar => { default => 99 }
+ }
+ );
+ }
+
+ sub pos_with_defaults {
+ my @p = validate_pos( @_, 1, { default => 99 } );
+ }
+
+ sub sets_options_on_call {
+ my %p = validate_with(
+ params => \@_,
+ spec => { foo => { type => SCALAR, default => 2 } },
+ normalize_keys => sub { $_[0] =~ s/^-//; lc $_[0] },
+ );
+ }
+
+=head1 DESCRIPTION
+
+The Params::Validate module allows you to validate method or function
+call parameters to an arbitrary level of specificity. At the simplest
+level, it is capable of validating the required parameters were given
+and that no unspecified additional parameters were passed in.
+
+It is also capable of determining that a parameter is of a specific
+type, that it is an object of a certain class hierarchy, that it
+possesses certain methods, or applying validation callbacks to
+arguments.
+
+=head2 EXPORT
+
+The module always exports the C<validate()> and C<validate_pos()>
+functions.
+
+It also has an additional function available for export,
+C<validate_with>, which can be used to validate any type of
+parameters, and set various options on a per-invocation basis.
+
+In addition, it can export the following constants, which are used as
+part of the type checking. These are C<SCALAR>, C<ARRAYREF>,
+C<HASHREF>, C<CODEREF>, C<GLOB>, C<GLOBREF>, and C<SCALARREF>,
+C<UNDEF>, C<OBJECT>, C<BOOLEAN>, and C<HANDLE>. These are explained
+in the section on L<Type Validation|Params::Validate/Type Validation>.
+
+The constants are available via the export tag C<:types>. There is
+also an C<:all> tag which includes all of the constants as well as the
+C<validation_options()> function.
+
+=encoding UTF-8
+
+=head1 PARAMETER VALIDATION
+
+The validation mechanisms provided by this module can handle both
+named or positional parameters. For the most part, the same features
+are available for each. The biggest difference is the way that the
+validation specification is given to the relevant subroutine. The
+other difference is in the error messages produced when validation
+checks fail.
+
+When handling named parameters, the module will accept either a hash
+or a hash reference.
+
+Subroutines expecting named parameters should call the C<validate()>
+subroutine like this:
+
+ validate(
+ @_, {
+ parameter1 => validation spec,
+ parameter2 => validation spec,
+ ...
+ }
+ );
+
+Subroutines expecting positional parameters should call the
+C<validate_pos()> subroutine like this:
+
+ validate_pos( @_, { validation spec }, { validation spec } );
+
+=head2 Mandatory/Optional Parameters
+
+If you just want to specify that some parameters are mandatory and
+others are optional, this can be done very simply.
+
+For a subroutine expecting named parameters, you would do this:
+
+ validate( @_, { foo => 1, bar => 1, baz => 0 } );
+
+This says that the "foo" and "bar" parameters are mandatory and that
+the "baz" parameter is optional. The presence of any other
+parameters will cause an error.
+
+For a subroutine expecting positional parameters, you would do this:
+
+ validate_pos( @_, 1, 1, 0, 0 );
+
+This says that you expect at least 2 and no more than 4 parameters.
+If you have a subroutine that has a minimum number of parameters but
+can take any maximum number, you can do this:
+
+ validate_pos( @_, 1, 1, (0) x (@_ - 2) );
+
+This will always be valid as long as at least two parameters are
+given. A similar construct could be used for the more complex
+validation parameters described further on.
+
+Please note that this:
+
+ validate_pos( @_, 1, 1, 0, 1, 1 );
+
+makes absolutely no sense, so don't do it. Any zeros must come at the
+end of the validation specification.
+
+In addition, if you specify that a parameter can have a default, then
+it is considered optional.
+
+=head2 Type Validation
+
+This module supports the following simple types, which can be
+L<exported as constants|/EXPORT>:
+
+=over 4
+
+=item * SCALAR
+
+A scalar which is not a reference, such as C<10> or C<'hello'>. A
+parameter that is undefined is B<not> treated as a scalar. If you
+want to allow undefined values, you will have to specify C<SCALAR |
+UNDEF>.
+
+=item * ARRAYREF
+
+An array reference such as C<[1, 2, 3]> or C<\@foo>.
+
+=item * HASHREF
+
+A hash reference such as C<< { a => 1, b => 2 } >> or C<\%bar>.
+
+=item * CODEREF
+
+A subroutine reference such as C<\&foo_sub> or C<sub { print "hello" }>.
+
+=item * GLOB
+
+This one is a bit tricky. A glob would be something like C<*FOO>, but
+not C<\*FOO>, which is a glob reference. It should be noted that this
+trick:
+
+ my $fh = do { local *FH; };
+
+makes C<$fh> a glob, not a glob reference. On the other hand, the
+return value from C<Symbol::gensym> is a glob reference. Either can
+be used as a file or directory handle.
+
+=item * GLOBREF
+
+A glob reference such as C<\*FOO>. See the L<GLOB|GLOB> entry above
+for more details.
+
+=item * SCALARREF
+
+A reference to a scalar such as C<\$x>.
+
+=item * UNDEF
+
+An undefined value
+
+=item * OBJECT
+
+A blessed reference.
+
+=item * BOOLEAN
+
+This is a special option, and is just a shortcut for C<UNDEF | SCALAR>.
+
+=item * HANDLE
+
+This option is also special, and is just a shortcut for C<GLOB |
+GLOBREF>. However, it seems likely that most people interested in
+either globs or glob references are likely to really be interested in
+whether the parameter in question could be a valid file or directory
+handle.
+
+=back
+
+To specify that a parameter must be of a given type when using named
+parameters, do this:
+
+ validate(
+ @_, {
+ foo => { type => SCALAR },
+ bar => { type => HASHREF }
+ }
+ );
+
+If a parameter can be of more than one type, just use the bitwise or
+(C<|>) operator to combine them.
+
+ validate( @_, { foo => { type => GLOB | GLOBREF } );
+
+For positional parameters, this can be specified as follows:
+
+ validate_pos( @_, { type => SCALAR | ARRAYREF }, { type => CODEREF } );
+
+=head2 Interface Validation
+
+To specify that a parameter is expected to have a certain set of
+methods, we can do the following:
+
+ validate(
+ @_, {
+ foo =>
+ # just has to be able to ->bar
+ { can => 'bar' }
+ }
+ );
+
+ ... or ...
+
+ validate(
+ @_, {
+ foo =>
+ # must be able to ->bar and ->print
+ { can => [qw( bar print )] }
+ }
+ );
+
+=head2 Class Validation
+
+A word of warning. When constructing your external interfaces, it is
+probably better to specify what methods you expect an object to
+have rather than what class it should be of (or a child of). This
+will make your API much more flexible.
+
+With that said, if you want to validate that an incoming parameter
+belongs to a class (or child class) or classes, do:
+
+ validate(
+ @_,
+ { foo => { isa => 'My::Frobnicator' } }
+ );
+
+ ... or ...
+
+ validate(
+ @_,
+ # must be both, not either!
+ { foo => { isa => [qw( My::Frobnicator IO::Handle )] } }
+ );
+
+=head2 Regex Validation
+
+If you want to specify that a given parameter must match a specific
+regular expression, this can be done with "regex" spec key. For
+example:
+
+ validate(
+ @_,
+ { foo => { regex => qr/^\d+$/ } }
+ );
+
+The value of the "regex" key may be either a string or a pre-compiled
+regex created via C<qr>.
+
+If the value being checked against a regex is undefined, the regex is
+explicitly checked against the empty string ('') instead, in order to
+avoid "Use of uninitialized value" warnings.
+
+The C<Regexp::Common> module on CPAN is an excellent source of regular
+expressions suitable for validating input.
+
+=head2 Callback Validation
+
+If none of the above are enough, it is possible to pass in one or more
+callbacks to validate the parameter. The callback will be given the
+B<value> of the parameter as its first argument. Its second argument
+will be all the parameters, as a reference to either a hash or array.
+Callbacks are specified as hash reference. The key is an id for the
+callback (used in error messages) and the value is a subroutine
+reference, such as:
+
+ validate(
+ @_,
+ {
+ foo => {
+ callbacks => {
+ 'smaller than a breadbox' => sub { shift() < $breadbox },
+ 'green or blue' => sub {
+ return 1 if $_[0] eq 'green' || $_[0] eq 'blue';
+ die "$_[0] is not green or blue!";
+ }
+ }
+ }
+ }
+ );
+
+ validate(
+ @_, {
+ foo => {
+ callbacks => {
+ 'bigger than baz' => sub { $_[0] > $_[1]->{baz} }
+ }
+ }
+ }
+ );
+
+The callback should return a true value if the value is valid. If not, it can
+return false or die. If you return false, a generic error message will be
+thrown by C<Params::Validate>.
+
+If your callback dies instead you can provide a custom error message. If the
+callback dies with a plain string, this string will be appended to an
+exception message generated by C<Params::Validate>. If the callback dies with
+a reference (blessed or not), then this will be rethrown as-is by
+C<Params::Validate>.
+
+=head2 Untainting
+
+If you want values untainted, set the "untaint" key in a spec hashref
+to a true value, like this:
+
+ my %p = validate(
+ @_, {
+ foo => { type => SCALAR, untaint => 1 },
+ bar => { type => ARRAYREF }
+ }
+ );
+
+This will untaint the "foo" parameter if the parameters are valid.
+
+Note that untainting is only done if I<all parameters> are valid.
+Also, only the return values are untainted, not the original values
+passed into the validation function.
+
+Asking for untainting of a reference value will not do anything, as
+C<Params::Validate> will only attempt to untaint the reference itself.
+
+=head2 Mandatory/Optional Revisited
+
+If you want to specify something such as type or interface, plus the
+fact that a parameter can be optional, do this:
+
+ validate(
+ @_, {
+ foo => { type => SCALAR },
+ bar => { type => ARRAYREF, optional => 1 }
+ }
+ );
+
+or this for positional parameters:
+
+ validate_pos(
+ @_,
+ { type => SCALAR },
+ { type => ARRAYREF, optional => 1 }
+ );
+
+By default, parameters are assumed to be mandatory unless specified as
+optional.
+
+=head2 Dependencies
+
+It also possible to specify that a given optional parameter depends on
+the presence of one or more other optional parameters.
+
+ validate(
+ @_, {
+ cc_number => {
+ type => SCALAR,
+ optional => 1,
+ depends => [ 'cc_expiration', 'cc_holder_name' ],
+ },
+ cc_expiration => { type => SCALAR, optional => 1 },
+ cc_holder_name => { type => SCALAR, optional => 1 },
+ }
+ );
+
+In this case, "cc_number", "cc_expiration", and "cc_holder_name" are
+all optional. However, if "cc_number" is provided, then
+"cc_expiration" and "cc_holder_name" must be provided as well.
+
+This allows you to group together sets of parameters that all must be
+provided together.
+
+The C<validate_pos()> version of dependencies is slightly different,
+in that you can only depend on one other parameter. Also, if for
+example, the second parameter 2 depends on the fourth parameter, then
+it implies a dependency on the third parameter as well. This is
+because if the fourth parameter is required, then the user must also
+provide a third parameter so that there can be four parameters in
+total.
+
+C<Params::Validate> will die if you try to depend on a parameter not
+declared as part of your parameter specification.
+
+=head2 Specifying defaults
+
+If the C<validate()> or C<validate_pos()> functions are called in a list
+context, they will return a hash or containing the original parameters plus
+defaults as indicated by the validation spec.
+
+If the function is not called in a list context, providing a default
+in the validation spec still indicates that the parameter is optional.
+
+The hash or array returned from the function will always be a copy of
+the original parameters, in order to leave C<@_> untouched for the
+calling function.
+
+Simple examples of defaults would be:
+
+ my %p = validate( @_, { foo => 1, bar => { default => 99 } } );
+
+ my @p = validate_pos( @_, 1, { default => 99 } );
+
+In scalar context, a hash reference or array reference will be
+returned, as appropriate.
+
+=head1 USAGE NOTES
+
+=head2 Validation failure
+
+By default, when validation fails C<Params::Validate> calls
+C<Carp::confess()>. This can be overridden by setting the C<on_fail>
+option, which is described in the L<"GLOBAL" OPTIONS|"GLOBAL" OPTIONS>
+section.
+
+=head2 Method calls
+
+When using this module to validate the parameters passed to a method
+call, you will probably want to remove the class/object from the
+parameter list B<before> calling C<validate()> or C<validate_pos()>.
+If your method expects named parameters, then this is necessary for
+the C<validate()> function to actually work, otherwise C<@_> will not
+be usable as a hash, because it will first have your object (or
+class) B<followed> by a set of keys and values.
+
+Thus the idiomatic usage of C<validate()> in a method call will look
+something like this:
+
+ sub method {
+ my $self = shift;
+
+ my %params = validate(
+ @_, {
+ foo => 1,
+ bar => { type => ARRAYREF },
+ }
+ );
+ }
+
+=head2 Speeding Up Validation
+
+In most cases, the validation spec will remain the same for each call to a
+subroutine. In that case, you can speed up validation by defining the
+validation spec just once, rather than on each call to the subroutine:
+
+ my %spec = ( ... );
+ sub foo {
+ my %params = validate( @_, \%spec );
+ }
+
+You can also use the C<state> feature to do this:
+
+ use feature 'state';
+
+ sub foo {
+ state $spec = { ... };
+ my %params = validate( @_, $spec );
+ }
+
+=head1 "GLOBAL" OPTIONS
+
+Because the API for the C<validate()> and C<validate_pos()> functions does not
+make it possible to specify any options other than the validation spec, it is
+possible to set some options as pseudo-'globals'. These allow you to specify
+such things as whether or not the validation of named parameters should be
+case sensitive, for one example.
+
+These options are called pseudo-'globals' because these settings are
+B<only applied to calls originating from the package that set the
+options>.
+
+In other words, if I am in package C<Foo> and I call
+C<validation_options()>, those options are only in effect when I call
+C<validate()> from package C<Foo>.
+
+While this is quite different from how most other modules operate, I
+feel that this is necessary in able to make it possible for one
+module/application to use Params::Validate while still using other
+modules that also use Params::Validate, perhaps with different
+options set.
+
+The downside to this is that if you are writing an app with a standard
+calling style for all functions, and your app has ten modules, B<each
+module must include a call to C<validation_options()>>. You could of
+course write a module that all your modules use which uses various
+trickery to do this when imported.
+
+=head2 Options
+
+=over 4
+
+=item * normalize_keys => $callback
+
+This option is only relevant when dealing with named parameters.
+
+This callback will be used to transform the hash keys of both the
+parameters and the parameter spec when C<validate()> or
+C<validate_with()> are called.
+
+Any alterations made by this callback will be reflected in the
+parameter hash that is returned by the validation function. For
+example:
+
+ sub foo {
+ return validate_with(
+ params => \@_,
+ spec => { foo => { type => SCALAR } },
+ normalize_keys =>
+ sub { my $k = shift; $k =~ s/^-//; return uc $k },
+ );
+
+ }
+
+ %p = foo( foo => 20 );
+
+ # $p{FOO} is now 20
+
+ %p = foo( -fOo => 50 );
+
+ # $p{FOO} is now 50
+
+The callback must return a defined value.
+
+If a callback is given then the deprecated "ignore_case" and
+"strip_leading" options are ignored.
+
+=item * allow_extra => $boolean
+
+If true, then the validation routine will allow extra parameters not
+named in the validation specification. In the case of positional
+parameters, this allows an unlimited number of maximum parameters
+(though a minimum may still be set). Defaults to false.
+
+=item * on_fail => $callback
+
+If given, this callback will be called whenever a validation check
+fails. It will be called with a single parameter, which will be a
+string describing the failure. This is useful if you wish to have
+this module throw exceptions as objects rather than as strings, for
+example.
+
+This callback is expected to C<die()> internally. If it does not, the
+validation will proceed onwards, with unpredictable results.
+
+The default is to simply use the Carp module's C<confess()> function.
+
+=item * stack_skip => $number
+
+This tells Params::Validate how many stack frames to skip when finding
+a subroutine name to use in error messages. By default, it looks one
+frame back, at the immediate caller to C<validate()> or
+C<validate_pos()>. If this option is set, then the given number of
+frames are skipped instead.
+
+=item * ignore_case => $boolean
+
+DEPRECATED
+
+This is only relevant when dealing with named parameters. If it is
+true, then the validation code will ignore the case of parameter
+names. Defaults to false.
+
+=item * strip_leading => $characters
+
+DEPRECATED
+
+This too is only relevant when dealing with named parameters. If this
+is given then any parameters starting with these characters will be
+considered equivalent to parameters without them entirely. For
+example, if this is specified as '-', then C<-foo> and C<foo> would be
+considered identical.
+
+=back
+
+=head1 PER-INVOCATION OPTIONS
+
+The C<validate_with()> function can be used to set the options listed
+above on a per-invocation basis. For example:
+
+ my %p = validate_with(
+ params => \@_,
+ spec => {
+ foo => { type => SCALAR },
+ bar => { default => 10 }
+ },
+ allow_extra => 1,
+ );
+
+In addition to the options listed above, it is also possible to set
+the option "called", which should be a string. This string will be
+used in any error messages caused by a failure to meet the validation
+spec.
+
+This subroutine will validate named parameters as a hash if the "spec"
+parameter is a hash reference. If it is an array reference, the
+parameters are assumed to be positional.
+
+ my %p = validate_with(
+ params => \@_,
+ spec => {
+ foo => { type => SCALAR },
+ bar => { default => 10 }
+ },
+ allow_extra => 1,
+ called => 'The Quux::Baz class constructor',
+ );
+
+ my @p = validate_with(
+ params => \@_,
+ spec => [
+ { type => SCALAR },
+ { default => 10 }
+ ],
+ allow_extra => 1,
+ called => 'The Quux::Baz class constructor',
+ );
+
+=head1 DISABLING VALIDATION
+
+If the environment variable C<PERL_NO_VALIDATION> is set to something
+true, then validation is turned off. This may be useful if you only
+want to use this module during development but don't want the speed
+hit during production.
+
+The only error that will be caught will be when an odd number of
+parameters are passed into a function/method that expects a hash.
+
+If you want to selectively turn validation on and off at runtime, you
+can directly set the C<$Params::Validate::NO_VALIDATION> global
+variable. It is B<strongly> recommended that you B<localize> any
+changes to this variable, because other modules you are using may
+expect validation to be on when they execute. For example:
+
+ {
+ local $Params::Validate::NO_VALIDATION = 1;
+
+ # no error
+ foo( bar => 2 );
+ }
+
+ # error
+ foo( bar => 2 );
+
+ sub foo {
+ my %p = validate( @_, { foo => 1 } );
+ ...;
+ }
+
+But if you want to shoot yourself in the foot and just turn it off, go
+ahead!
+
+=head1 TAINT MODE
+
+The XS implementation of this module has some problems Under taint mode with
+version of Perl before 5.14. If validation I<fails>, then instead of getting
+the expected error message you'll get a message like "Insecure dependency in
+eval_sv". This can be worked around by either untainting the arguments
+yourself, using the pure Perl implementation, or upgrading your Perl.
+
+=head1 LIMITATIONS
+
+Right now there is no way (short of a callback) to specify that
+something must be of one of a list of classes, or that it must possess
+one of a list of methods. If this is desired, it can be added in the
+future.
+
+Ideally, there would be only one validation function. If someone
+figures out how to do this, please let me know.
+
+=head1 SUPPORT
+
+Please submit bugs and patches to the CPAN RT system at
+http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Params%3A%3AValidate or
+via email at bug-params-validate@rt.cpan.org.
+
+Support questions can be sent to Dave at autarch@urth.org.
+
+=head1 DONATIONS
+
+If you'd like to thank me for the work I've done on this module,
+please consider making a "donation" to me via PayPal. I spend a lot of
+free time creating free software, and would appreciate any support
+you'd care to offer.
+
+Please note that B<I am not suggesting that you must do this> in order
+for me to continue working on this particular software. I will
+continue to do so, inasmuch as I have in the past, for as long as it
+interests me.
+
+Similarly, a donation made in this way will probably not make me work
+on this software much more, unless I get so many donations that I can
+consider working on free software full time, which seems unlikely at
+best.
+
+To donate, log into PayPal and send money to autarch@urth.org or use
+the button on this page:
+L<http://www.urth.org/~autarch/fs-donation.html>
+
+=head1 AUTHORS
+
+=over 4
+
+=item *
+
+Dave Rolsky <autarch@urth.org>
+
+=item *
+
+Ilya Martynov <ilya@martynov.org>
+
+=back
+
+=head1 CONTRIBUTORS
+
+=for stopwords Ivan Bessarabov J.R. Mash Noel Maddy Olivier Mengué Vincent Pit
+
+=over 4
+
+=item *
+
+Ivan Bessarabov <ivan@bessarabov.ru>
+
+=item *
+
+J.R. Mash <jmash.code@gmail.com>
+
+=item *
+
+Noel Maddy <zhtwnpanta@gmail.com>
+
+=item *
+
+Olivier Mengué <dolmen@cpan.org>
+
+=item *
+
+Vincent Pit <perl@profvince.com>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is Copyright (c) 2001 - 2015 by Dave Rolsky and Ilya Martynov.
+
+This is free software, licensed under:
+
+ The Artistic License 2.0 (GPL Compatible)
+
+=cut
diff --git a/lib/Params/Validate/Constants.pm b/lib/Params/Validate/Constants.pm
new file mode 100644
index 0000000..6204282
--- /dev/null
+++ b/lib/Params/Validate/Constants.pm
@@ -0,0 +1,39 @@
+package Params::Validate::Constants;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.20';
+
+our @ISA = 'Exporter';
+
+our @EXPORT = qw(
+ SCALAR
+ ARRAYREF
+ HASHREF
+ CODEREF
+ GLOB
+ GLOBREF
+ SCALARREF
+ HANDLE
+ BOOLEAN
+ UNDEF
+ OBJECT
+ UNKNOWN
+);
+
+sub SCALAR () { 1 }
+sub ARRAYREF () { 2 }
+sub HASHREF () { 4 }
+sub CODEREF () { 8 }
+sub GLOB () { 16 }
+sub GLOBREF () { 32 }
+sub SCALARREF () { 64 }
+sub UNKNOWN () { 128 }
+sub UNDEF () { 256 }
+sub OBJECT () { 512 }
+
+sub HANDLE () { 16 | 32 }
+sub BOOLEAN () { 1 | 256 }
+
+1;
diff --git a/lib/Params/Validate/PP.pm b/lib/Params/Validate/PP.pm
new file mode 100644
index 0000000..e766475
--- /dev/null
+++ b/lib/Params/Validate/PP.pm
@@ -0,0 +1,735 @@
+package Params::Validate::PP;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.20';
+
+use Params::Validate::Constants;
+use Scalar::Util 1.10 ();
+
+our $options;
+
+# Various internals notes (for me and any future readers of this
+# monstrosity):
+#
+# - A lot of the weirdness is _intentional_, because it optimizes for
+# the _success_ case. It does not really matter how slow the code is
+# after it enters a path that leads to reporting failure. But the
+# "success" path should be as fast as possible.
+#
+# -- We only calculate $called as needed for this reason, even though it
+# means copying code all over.
+#
+# - All the validation routines need to be careful never to alter the
+# references that are passed.
+#
+# -- The code assumes that _most_ callers will not be using the
+# skip_leading or ignore_case features. In order to not alter the
+# references passed in, we copy them wholesale when normalizing them
+# to make these features work. This is slower but lets us be faster
+# when not using them.
+
+# Matt Sergeant came up with this prototype, which slickly takes the
+# first array (which should be the caller's @_), and makes it a
+# reference. Everything after is the parameters for validation.
+sub validate_pos (\@@) {
+ return if $Params::Validate::NO_VALIDATION && !defined wantarray;
+
+ my $p = shift;
+
+ my @specs = @_;
+
+ my @p = @$p;
+ if ($Params::Validate::NO_VALIDATION) {
+
+ # if the spec is bigger that's where we can start adding
+ # defaults
+ for ( my $x = $#p + 1; $x <= $#specs; $x++ ) {
+ $p[$x] = $specs[$x]->{default}
+ if ref $specs[$x] && exists $specs[$x]->{default};
+ }
+
+ return wantarray ? @p : \@p;
+ }
+
+ # I'm too lazy to pass these around all over the place.
+ local $options ||= _get_options( ( caller(0) )[0] )
+ unless defined $options;
+
+ my $min = 0;
+
+ while (1) {
+ last
+ unless (
+ ref $specs[$min]
+ ? !( exists $specs[$min]->{default} || $specs[$min]->{optional} )
+ : $specs[$min]
+ );
+
+ $min++;
+ }
+
+ my $max = scalar @specs;
+
+ my $actual = scalar @p;
+ unless ( $actual >= $min
+ && ( $options->{allow_extra} || $actual <= $max ) ) {
+ my $minmax = (
+ $options->{allow_extra}
+ ? "at least $min"
+ : ( $min != $max ? "$min - $max" : $max )
+ );
+
+ my $val = $options->{allow_extra} ? $min : $max;
+ $minmax .= $val != 1 ? ' were' : ' was';
+
+ my $called = _get_called();
+
+ $options->{on_fail}->( "$actual parameter"
+ . ( $actual != 1 ? 's' : '' ) . " "
+ . ( $actual != 1 ? 'were' : 'was' )
+ . " passed to $called but $minmax expected\n" );
+ }
+
+ my $bigger = $#p > $#specs ? $#p : $#specs;
+ foreach ( 0 .. $bigger ) {
+ my $spec = $specs[$_];
+
+ next unless ref $spec;
+
+ if ( $_ <= $#p ) {
+ _validate_one_param(
+ $p[$_], \@p, $spec,
+ 'Parameter #' . ( $_ + 1 ) . ' (%s)'
+ );
+ }
+
+ $p[$_] = $spec->{default} if $_ > $#p && exists $spec->{default};
+ }
+
+ _validate_pos_depends( \@p, \@specs );
+
+ foreach (
+ grep {
+ defined $p[$_]
+ && !ref $p[$_]
+ && ref $specs[$_]
+ && $specs[$_]{untaint}
+ } 0 .. $bigger
+ ) {
+ ( $p[$_] ) = $p[$_] =~ /(.+)/;
+ }
+
+ return wantarray ? @p : \@p;
+}
+
+sub _validate_pos_depends {
+ my ( $p, $specs ) = @_;
+
+ for my $p_idx ( 0 .. $#$p ) {
+ my $spec = $specs->[$p_idx];
+
+ next
+ unless $spec
+ && UNIVERSAL::isa( $spec, 'HASH' )
+ && exists $spec->{depends};
+
+ my $depends = $spec->{depends};
+
+ if ( ref $depends ) {
+ require Carp;
+ local $Carp::CarpLevel = 2;
+ Carp::croak(
+ "Arguments to 'depends' for validate_pos() must be a scalar");
+ }
+
+ my $p_size = scalar @$p;
+ if ( $p_size < $depends - 1 ) {
+ my $error
+ = ( "Parameter #"
+ . ( $p_idx + 1 )
+ . " depends on parameter #"
+ . $depends
+ . ", which was not given" );
+
+ $options->{on_fail}->($error);
+ }
+ }
+ return 1;
+}
+
+sub _validate_named_depends {
+ my ( $p, $specs ) = @_;
+
+ foreach my $pname ( keys %$p ) {
+ my $spec = $specs->{$pname};
+
+ next
+ unless $spec
+ && UNIVERSAL::isa( $spec, 'HASH' )
+ && $spec->{depends};
+
+ unless ( UNIVERSAL::isa( $spec->{depends}, 'ARRAY' )
+ || !ref $spec->{depends} ) {
+ require Carp;
+ local $Carp::CarpLevel = 2;
+ Carp::croak(
+ "Arguments to 'depends' must be a scalar or arrayref");
+ }
+
+ foreach my $depends_name (
+ ref $spec->{depends}
+ ? @{ $spec->{depends} }
+ : $spec->{depends}
+ ) {
+ unless ( exists $p->{$depends_name} ) {
+ my $error
+ = ( "Parameter '$pname' depends on parameter '"
+ . $depends_name
+ . "', which was not given" );
+
+ $options->{on_fail}->($error);
+ }
+ }
+ }
+}
+
+sub validate (\@$) {
+ return if $Params::Validate::NO_VALIDATION && !defined wantarray;
+
+ my $p = $_[0];
+
+ my $specs = $_[1];
+ local $options = _get_options( ( caller(0) )[0] ) unless defined $options;
+
+ if ( ref $p eq 'ARRAY' ) {
+
+ # we were called as validate( @_, ... ) where @_ has a
+ # single element, a hash reference
+ if ( ref $p->[0] ) {
+ $p = { %{ $p->[0] } };
+ }
+ elsif ( @$p % 2 ) {
+ my $called = _get_called();
+
+ $options->{on_fail}
+ ->( "Odd number of parameters in call to $called "
+ . "when named parameters were expected\n" );
+ }
+ else {
+ $p = {@$p};
+ }
+ }
+
+ if ( $options->{normalize_keys} ) {
+ $specs = _normalize_callback( $specs, $options->{normalize_keys} );
+ $p = _normalize_callback( $p, $options->{normalize_keys} );
+ }
+ elsif ( $options->{ignore_case} || $options->{strip_leading} ) {
+ $specs = _normalize_named($specs);
+ $p = _normalize_named($p);
+ }
+
+ if ($Params::Validate::NO_VALIDATION) {
+ return (
+ wantarray
+ ? (
+
+ # this is a hash containing just the defaults
+ (
+ map { $_ => $specs->{$_}->{default} }
+ grep {
+ ref $specs->{$_}
+ && exists $specs->{$_}->{default}
+ }
+ keys %$specs
+ ),
+ (
+ ref $p eq 'ARRAY'
+ ? (
+ ref $p->[0]
+ ? %{ $p->[0] }
+ : @$p
+ )
+ : %$p
+ )
+ )
+ : do {
+ my $ref = (
+ ref $p eq 'ARRAY'
+ ? (
+ ref $p->[0]
+ ? $p->[0]
+ : {@$p}
+ )
+ : $p
+ );
+
+ foreach (
+ grep {
+ ref $specs->{$_}
+ && exists $specs->{$_}->{default}
+ }
+ keys %$specs
+ ) {
+ $ref->{$_} = $specs->{$_}->{default}
+ unless exists $ref->{$_};
+ }
+
+ return $ref;
+ }
+ );
+ }
+
+ _validate_named_depends( $p, $specs );
+
+ unless ( $options->{allow_extra} ) {
+ if ( my @unmentioned = grep { !exists $specs->{$_} } keys %$p ) {
+ my $called = _get_called();
+
+ $options->{on_fail}->( "The following parameter"
+ . ( @unmentioned > 1 ? 's were' : ' was' )
+ . " passed in the call to $called but "
+ . ( @unmentioned > 1 ? 'were' : 'was' )
+ . " not listed in the validation options: @unmentioned\n"
+ );
+ }
+ }
+
+ my @missing;
+
+ # the iterator needs to be reset in case the same hashref is being
+ # passed to validate() on successive calls, because we may not go
+ # through all the hash's elements
+ keys %$specs;
+OUTER:
+ while ( my ( $key, $spec ) = each %$specs ) {
+ if (
+ !exists $p->{$key}
+ && (
+ ref $spec
+ ? !(
+ do {
+
+ # we want to short circuit the loop here if we
+ # can assign a default, because there's no need
+ # check anything else at all.
+ if ( exists $spec->{default} ) {
+ $p->{$key} = $spec->{default};
+ next OUTER;
+ }
+ }
+ || do {
+
+ # Similarly, an optional parameter that is
+ # missing needs no additional processing.
+ next OUTER if $spec->{optional};
+ }
+ )
+ : $spec
+ )
+ ) {
+ push @missing, $key;
+ }
+
+ # Can't validate a non hashref spec beyond the presence or
+ # absence of the parameter.
+ elsif ( ref $spec ) {
+ my $value = defined $p->{$key} ? qq|"$p->{$key}"| : 'undef';
+ _validate_one_param(
+ $p->{$key}, $p, $spec,
+ qq{The '$key' parameter (%s)}
+ );
+ }
+ }
+
+ if (@missing) {
+ my $called = _get_called();
+
+ my $missing = join ', ', map {"'$_'"} @missing;
+ $options->{on_fail}->( "Mandatory parameter"
+ . ( @missing > 1 ? 's' : '' )
+ . " $missing missing in call to $called\n" );
+ }
+
+ # do untainting after we know everything passed
+ foreach my $key (
+ grep {
+ defined $p->{$_}
+ && !ref $p->{$_}
+ && ref $specs->{$_}
+ && $specs->{$_}{untaint}
+ }
+ keys %$p
+ ) {
+ ( $p->{$key} ) = $p->{$key} =~ /(.+)/;
+ }
+
+ return wantarray ? %$p : $p;
+}
+
+sub validate_with {
+ return if $Params::Validate::NO_VALIDATION && !defined wantarray;
+
+ my %p = @_;
+
+ local $options = _get_options( ( caller(0) )[0], %p );
+
+ unless ($Params::Validate::NO_VALIDATION) {
+ unless ( exists $options->{called} ) {
+ $options->{called} = ( caller( $options->{stack_skip} ) )[3];
+ }
+
+ }
+
+ if ( UNIVERSAL::isa( $p{spec}, 'ARRAY' ) ) {
+ return validate_pos( @{ $p{params} }, @{ $p{spec} } );
+ }
+ else {
+
+ # intentionally ignore the prototype because this contains
+ # either an array or hash reference, and validate() will
+ # handle either one properly
+ return &validate( $p{params}, $p{spec} );
+ }
+}
+
+sub _normalize_callback {
+ my ( $p, $func ) = @_;
+
+ my %new;
+
+ foreach my $key ( keys %$p ) {
+ my $new_key = $func->($key);
+
+ unless ( defined $new_key ) {
+ die
+ "The normalize_keys callback did not return a defined value when normalizing the key '$key'";
+ }
+
+ if ( exists $new{$new_key} ) {
+ die
+ "The normalize_keys callback returned a key that already exists, '$new_key', when normalizing the key '$key'";
+ }
+
+ $new{$new_key} = $p->{$key};
+ }
+
+ return \%new;
+}
+
+sub _normalize_named {
+
+ # intentional copy so we don't destroy original
+ my %h = ( ref $_[0] ) =~ /ARRAY/ ? @{ $_[0] } : %{ $_[0] };
+
+ if ( $options->{ignore_case} ) {
+ $h{ lc $_ } = delete $h{$_} for keys %h;
+ }
+
+ if ( $options->{strip_leading} ) {
+ foreach my $key ( keys %h ) {
+ my $new;
+ ( $new = $key ) =~ s/^\Q$options->{strip_leading}\E//;
+ $h{$new} = delete $h{$key};
+ }
+ }
+
+ return \%h;
+}
+
+my %Valid = map { $_ => 1 }
+ qw( callbacks can default depends isa optional regex type untaint );
+
+sub _validate_one_param {
+ my ( $value, $params, $spec, $id ) = @_;
+
+ # for my $key ( keys %{$spec} ) {
+ # unless ( $Valid{$key} ) {
+ # $options->{on_fail}
+ # ->(qq{"$key" is not an allowed validation spec key});
+ # }
+ # }
+
+ if ( exists $spec->{type} ) {
+ unless ( defined $spec->{type}
+ && Scalar::Util::looks_like_number( $spec->{type} )
+ && $spec->{type} > 0 ) {
+ my $msg
+ = "$id has a type specification which is not a number. It is ";
+ if ( defined $spec->{type} ) {
+ $msg .= "a string - $spec->{type}";
+ }
+ else {
+ $msg .= "undef";
+ }
+
+ $msg
+ .= ".\n Use the constants exported by Params::Validate to declare types.";
+
+ $options->{on_fail}->( sprintf( $msg, _stringify($value) ) );
+ }
+
+ unless ( _get_type($value) & $spec->{type} ) {
+ my $type = _get_type($value);
+
+ my @is = _typemask_to_strings($type);
+ my @allowed = _typemask_to_strings( $spec->{type} );
+ my $article = $is[0] =~ /^[aeiou]/i ? 'an' : 'a';
+
+ my $called = _get_called(1);
+
+ $options->{on_fail}->(
+ sprintf(
+ "$id to $called was $article '@is', which "
+ . "is not one of the allowed types: @allowed\n",
+ _stringify($value)
+ )
+ );
+ }
+ }
+
+ # short-circuit for common case
+ return
+ unless ( $spec->{isa}
+ || $spec->{can}
+ || $spec->{callbacks}
+ || $spec->{regex} );
+
+ if ( exists $spec->{isa} ) {
+ foreach ( ref $spec->{isa} ? @{ $spec->{isa} } : $spec->{isa} ) {
+ unless (
+ do {
+ local $@ = q{};
+ eval { $value->isa($_) };
+ }
+ ) {
+ my $is = ref $value ? ref $value : 'plain scalar';
+ my $article1 = $_ =~ /^[aeiou]/i ? 'an' : 'a';
+ my $article2 = $is =~ /^[aeiou]/i ? 'an' : 'a';
+
+ my $called = _get_called(1);
+
+ $options->{on_fail}->(
+ sprintf(
+ "$id to $called was not $article1 '$_' "
+ . "(it is $article2 $is)\n", _stringify($value)
+ )
+ );
+ }
+ }
+ }
+
+ if ( exists $spec->{can} ) {
+ foreach ( ref $spec->{can} ? @{ $spec->{can} } : $spec->{can} ) {
+ unless (
+ do {
+ local $@ = q{};
+ eval { $value->can($_) };
+ }
+ ) {
+ my $called = _get_called(1);
+
+ $options->{on_fail}->(
+ sprintf(
+ "$id to $called does not have the method: '$_'\n",
+ _stringify($value)
+ )
+ );
+ }
+ }
+ }
+
+ if ( $spec->{callbacks} ) {
+ unless ( UNIVERSAL::isa( $spec->{callbacks}, 'HASH' ) ) {
+ my $called = _get_called(1);
+
+ $options->{on_fail}->(
+ "'callbacks' validation parameter for $called must be a hash reference\n"
+ );
+ }
+
+ foreach ( keys %{ $spec->{callbacks} } ) {
+ unless ( UNIVERSAL::isa( $spec->{callbacks}{$_}, 'CODE' ) ) {
+ my $called = _get_called(1);
+
+ $options->{on_fail}->(
+ "callback '$_' for $called is not a subroutine reference\n"
+ );
+ }
+
+ my $ok;
+ my $e = do {
+ local $@ = q{};
+ local $SIG{__DIE__};
+ $ok = eval { $spec->{callbacks}{$_}->( $value, $params ) };
+ $@;
+ };
+
+ if ( !$ok ) {
+ my $called = _get_called(1);
+
+ if ( ref $e ) {
+ $options->{on_fail}->($e);
+ }
+ else {
+ my $msg = "$id to $called did not pass the '$_' callback";
+ $msg .= ": $e" if length $e;
+ $msg .= "\n";
+ $options->{on_fail}->( sprintf( $msg, _stringify($value) ) );
+ }
+ }
+ }
+ }
+
+ if ( exists $spec->{regex} ) {
+ unless ( ( defined $value ? $value : '' ) =~ /$spec->{regex}/ ) {
+ my $called = _get_called(1);
+
+ $options->{on_fail}->(
+ sprintf(
+ "$id to $called did not pass regex check\n",
+ _stringify($value)
+ )
+ );
+ }
+ }
+}
+
+{
+ # if it UNIVERSAL::isa the string on the left then its the type on
+ # the right
+ my %isas = (
+ 'ARRAY' => ARRAYREF,
+ 'HASH' => HASHREF,
+ 'CODE' => CODEREF,
+ 'GLOB' => GLOBREF,
+ 'SCALAR' => SCALARREF,
+ 'REGEXP' => SCALARREF,
+ );
+ my %simple_refs = map { $_ => 1 } keys %isas;
+
+ sub _get_type {
+ return UNDEF unless defined $_[0];
+
+ my $ref = ref $_[0];
+ unless ($ref) {
+
+ # catches things like: my $fh = do { local *FH; };
+ return GLOB if UNIVERSAL::isa( \$_[0], 'GLOB' );
+ return SCALAR;
+ }
+
+ return $isas{$ref} if $simple_refs{$ref};
+
+ foreach ( keys %isas ) {
+ return $isas{$_} | OBJECT if UNIVERSAL::isa( $_[0], $_ );
+ }
+
+ # I really hope this never happens.
+ return UNKNOWN;
+ }
+}
+
+{
+ my %type_to_string = (
+ SCALAR() => 'scalar',
+ ARRAYREF() => 'arrayref',
+ HASHREF() => 'hashref',
+ CODEREF() => 'coderef',
+ GLOB() => 'glob',
+ GLOBREF() => 'globref',
+ SCALARREF() => 'scalarref',
+ UNDEF() => 'undef',
+ OBJECT() => 'object',
+ UNKNOWN() => 'unknown',
+ );
+
+ sub _typemask_to_strings {
+ my $mask = shift;
+
+ my @types;
+ foreach (
+ SCALAR, ARRAYREF, HASHREF, CODEREF, GLOB, GLOBREF,
+ SCALARREF, UNDEF, OBJECT, UNKNOWN
+ ) {
+ push @types, $type_to_string{$_} if $mask & $_;
+ }
+ return @types ? @types : ('unknown');
+ }
+}
+
+{
+ my %defaults = (
+ ignore_case => 0,
+ strip_leading => 0,
+ allow_extra => 0,
+ on_fail => sub {
+ require Carp;
+ Carp::confess( $_[0] );
+ },
+ stack_skip => 1,
+ normalize_keys => undef,
+ );
+
+ *set_options = \&validation_options;
+
+ sub validation_options {
+ my %opts = @_;
+
+ my $caller = caller;
+
+ foreach ( keys %defaults ) {
+ $opts{$_} = $defaults{$_} unless exists $opts{$_};
+ }
+
+ $Params::Validate::OPTIONS{$caller} = \%opts;
+ }
+
+ sub _get_options {
+ my $caller = shift;
+
+ if (@_) {
+
+ return (
+ $Params::Validate::OPTIONS{$caller}
+ ? {
+ %{ $Params::Validate::OPTIONS{$caller} },
+ @_
+ }
+ : { %defaults, @_ }
+ );
+ }
+ else {
+ return (
+ exists $Params::Validate::OPTIONS{$caller}
+ ? $Params::Validate::OPTIONS{$caller}
+ : \%defaults
+ );
+ }
+ }
+}
+
+sub _get_called {
+ my $extra_skip = $_[0] || 0;
+
+ # always add one more for this sub
+ $extra_skip++;
+
+ my $called = (
+ exists $options->{called}
+ ? $options->{called}
+ : ( caller( $options->{stack_skip} + $extra_skip ) )[3]
+ );
+
+ $called = 'N/A' unless defined $called;
+
+ return $called;
+}
+
+sub _stringify {
+ return defined $_[0] ? qq{"$_[0]"} : 'undef';
+}
+
+1;
diff --git a/lib/Params/Validate/XS.pm b/lib/Params/Validate/XS.pm
new file mode 100644
index 0000000..256131d
--- /dev/null
+++ b/lib/Params/Validate/XS.pm
@@ -0,0 +1,51 @@
+package Params::Validate::XS;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.20';
+
+use Carp;
+
+my $default_fail = sub {
+ Carp::confess( $_[0] );
+};
+
+{
+ my %defaults = (
+ ignore_case => 0,
+ strip_leading => 0,
+ allow_extra => 0,
+ on_fail => $default_fail,
+ stack_skip => 1,
+ normalize_keys => undef,
+ );
+
+ *set_options = \&validation_options;
+
+ sub validation_options {
+ my %opts = @_;
+
+ my $caller = caller;
+
+ foreach ( keys %defaults ) {
+ $opts{$_} = $defaults{$_} unless exists $opts{$_};
+ }
+
+ $Params::Validate::OPTIONS{$caller} = \%opts;
+ }
+
+ use XSLoader;
+ XSLoader::load(
+ __PACKAGE__,
+ exists $Params::Validate::XS::{VERSION}
+ ? ${ $Params::Validate::XS::{VERSION} }
+ : (),
+ );
+}
+
+sub _check_regex_from_xs {
+ return ( defined $_[0] ? $_[0] : '' ) =~ /$_[1]/ ? 1 : 0;
+}
+
+1;
diff --git a/lib/Params/Validate/XS.xs b/lib/Params/Validate/XS.xs
new file mode 100644
index 0000000..109145a
--- /dev/null
+++ b/lib/Params/Validate/XS.xs
@@ -0,0 +1,1811 @@
+/* Copyright (c) 2000-2012 Dave Rolsky and Ilya Martynov */
+
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+#include "ppport.h"
+
+#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L))
+#define INLINE inline
+#else
+#define INLINE
+#endif
+
+/* type constants */
+#define SCALAR 1
+#define ARRAYREF 2
+#define HASHREF 4
+#define CODEREF 8
+#define GLOB 16
+#define GLOBREF 32
+#define SCALARREF 64
+#define UNKNOWN 128
+#define UNDEF 256
+#define OBJECT 512
+
+#define HANDLE (GLOB | GLOBREF)
+#define BOOLEAN (SCALAR | UNDEF)
+
+/* return data macros */
+#define RETURN_ARRAY(ret) \
+ STMT_START \
+ { \
+ I32 i; \
+ switch(GIMME_V) \
+ { \
+ case G_VOID: \
+ return; \
+ case G_ARRAY: \
+ EXTEND(SP, av_len(ret) + 1); \
+ for(i = 0; i <= av_len(ret); i++) \
+ { \
+ PUSHs(*av_fetch(ret, i, 1)); \
+ } \
+ break; \
+ case G_SCALAR: \
+ XPUSHs(sv_2mortal(newRV_inc((SV*) ret))); \
+ break; \
+ } \
+ } STMT_END \
+
+#define RETURN_HASH(ret) \
+ STMT_START \
+ { \
+ HE* he; \
+ I32 keys; \
+ switch(GIMME_V) \
+ { \
+ case G_VOID: \
+ return; \
+ case G_ARRAY: \
+ keys = hv_iterinit(ret); \
+ EXTEND(SP, keys * 2); \
+ while ((he = hv_iternext(ret))) \
+ { \
+ PUSHs(HeSVKEY_force(he)); \
+ PUSHs(HeVAL(he)); \
+ } \
+ break; \
+ case G_SCALAR: \
+ XPUSHs(sv_2mortal(newRV_inc((SV*) ret))); \
+ break; \
+ } \
+ } STMT_END
+
+
+static SV *module;
+void peek(SV *thing)
+{
+ if (NULL == module) {
+ module = newSVpv("Devel::Peek", 0);
+ load_module(PERL_LOADMOD_NOIMPORT, module, NULL);
+ }
+
+ {
+ dSP;
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ XPUSHs(thing);
+ PUTBACK;
+
+ (void)call_pv("Devel::Peek::Dump", G_VOID);
+
+ SPAGAIN;
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ }
+}
+
+INLINE static bool
+no_validation() {
+ SV* no_v;
+
+ no_v = get_sv("Params::Validate::NO_VALIDATION", 0);
+ if (! no_v)
+ croak("Cannot retrieve $Params::Validate::NO_VALIDATION\n");
+
+ return SvTRUE(no_v);
+}
+
+/* return type string that corresponds to typemask */
+INLINE static SV*
+typemask_to_string(IV mask) {
+ SV* buffer;
+ IV empty = 1;
+
+ buffer = sv_2mortal(newSVpv("", 0));
+
+ if (mask & SCALAR) {
+ sv_catpv(buffer, "scalar");
+ empty = 0;
+ }
+ if (mask & ARRAYREF) {
+ sv_catpv(buffer, empty ? "arrayref" : " arrayref");
+ empty = 0;
+ }
+ if (mask & HASHREF) {
+ sv_catpv(buffer, empty ? "hashref" : " hashref");
+ empty = 0;
+ }
+ if (mask & CODEREF) {
+ sv_catpv(buffer, empty ? "coderef" : " coderef");
+ empty = 0;
+ }
+ if (mask & GLOB) {
+ sv_catpv(buffer, empty ? "glob" : " glob");
+ empty = 0;
+ }
+ if (mask & GLOBREF) {
+ sv_catpv(buffer, empty ? "globref" : " globref");
+ empty = 0;
+ }
+ if (mask & SCALARREF) {
+ sv_catpv(buffer, empty ? "scalarref" : " scalarref");
+ empty = 0;
+ }
+ if (mask & UNDEF) {
+ sv_catpv(buffer, empty ? "undef" : " undef");
+ empty = 0;
+ }
+ if (mask & OBJECT) {
+ sv_catpv(buffer, empty ? "object" : " object");
+ empty = 0;
+ }
+ if (mask & UNKNOWN) {
+ sv_catpv(buffer, empty ? "unknown" : " unknown");
+ empty = 0;
+ }
+
+ return buffer;
+}
+
+/* compute numberic datatype for variable */
+INLINE static IV
+get_type(SV* sv) {
+ IV type = 0;
+
+ if (SvTYPE(sv) == SVt_PVGV) {
+ return GLOB;
+ }
+ if (!SvOK(sv)) {
+ return UNDEF;
+ }
+ if (!SvROK(sv)) {
+ return SCALAR;
+ }
+
+ switch (SvTYPE(SvRV(sv))) {
+ case SVt_NULL:
+ case SVt_IV:
+ case SVt_NV:
+ case SVt_PV:
+ #if PERL_VERSION <= 10
+ case SVt_RV:
+ #endif
+ case SVt_PVMG:
+ case SVt_PVIV:
+ case SVt_PVNV:
+ #if PERL_VERSION <= 8
+ case SVt_PVBM:
+ #elif PERL_VERSION >= 11
+ case SVt_REGEXP:
+ #endif
+ type = SCALARREF;
+ break;
+ case SVt_PVAV:
+ type = ARRAYREF;
+ break;
+ case SVt_PVHV:
+ type = HASHREF;
+ break;
+ case SVt_PVCV:
+ type = CODEREF;
+ break;
+ case SVt_PVGV:
+ type = GLOBREF;
+ break;
+ /* Perl 5.10 has a bunch of new types that I don't think will ever
+ actually show up here (I hope), but not handling them makes the
+ C compiler cranky. */
+ default:
+ type = UNKNOWN;
+ break;
+ }
+
+ if (type) {
+ if (sv_isobject(sv)) return type | OBJECT;
+ return type;
+ }
+
+ /* Getting here should not be possible */
+ return UNKNOWN;
+}
+
+/* get an article for given string */
+INLINE static const char*
+article(SV* string) {
+ STRLEN len;
+ char* rawstr;
+
+ rawstr = SvPV(string, len);
+ if (len) {
+ switch(rawstr[0]) {
+ case 'a':
+ case 'e':
+ case 'i':
+ case 'o':
+ case 'u':
+ return "an";
+ }
+ }
+
+ return "a";
+}
+
+char *
+string_representation(SV* value) {
+ if(SvOK(value)) {
+ return (void *)form("\"%s\"", SvPV_nolen(value));
+ }
+ else {
+ return (void *)"undef";
+ }
+}
+
+/* raises exception either using user-defined callback or using
+ built-in method */
+static void
+validation_failure(SV* message, HV* options) {
+ SV** temp;
+ SV* on_fail;
+
+ if ((temp = hv_fetch(options, "on_fail", 7, 0))) {
+ SvGETMAGIC(*temp);
+ on_fail = *temp;
+ }
+ else {
+ on_fail = NULL;
+ }
+
+ {
+ dSP;
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ mXPUSHs(message);
+ PUTBACK;
+
+ /* use user defined callback if available */
+ if (on_fail) {
+ call_sv(on_fail, G_DISCARD);
+ }
+ else {
+ /* by default resort to Carp::confess for error reporting */
+ call_pv("Carp::confess", G_DISCARD);
+ }
+
+ /* We shouldn't get here if the thing we just called dies, but it
+ doesn't hurt to be careful. */
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ }
+
+ return;
+}
+
+/* get called subroutine fully qualified name */
+static SV*
+get_caller(HV* options) {
+ SV** temp;
+
+ if ((temp = hv_fetch(options, "called", 6, 0))) {
+ SvGETMAGIC(*temp);
+ SvREFCNT_inc(*temp);
+ return *temp;
+ }
+ else {
+ IV frame;
+ SV *caller;
+#if PERL_VERSION >= 14
+ const PERL_CONTEXT *cx;
+ GV *cvgv;
+# else
+ SV *buffer;
+#endif
+
+ if ((temp = hv_fetch(options, "stack_skip", 10, 0))) {
+ SvGETMAGIC(*temp);
+ frame = SvIV(*temp);
+ }
+ else {
+ frame = 1;
+ }
+
+#if PERL_VERSION >= 14
+ if (frame > 0) {
+ frame--;
+ }
+
+ cx = caller_cx(frame, NULL);
+
+ if (cx) {
+ switch (CxTYPE(cx)) {
+ case CXt_EVAL:
+ caller = newSVpv("\"eval\"", 6);
+ break;
+ case CXt_SUB:
+ cvgv = CvGV(cx->blk_sub.cv);
+ caller = newSV(0);
+ if (cvgv && isGV(cvgv)) {
+ gv_efullname4(caller, cvgv, NULL, 1);
+ }
+ break;
+ default:
+ caller = newSVpv("(unknown)", 9);
+ break;
+ }
+ }
+ else {
+ caller = newSVpv("(unknown)", 9);
+ }
+#else
+ buffer = sv_2mortal(newSVpvf("(caller(%d))[3]", (int) frame));
+
+ caller = eval_pv(SvPV_nolen(buffer), 1);
+ if (SvTYPE(caller) == SVt_NULL) {
+ sv_setpv(caller, "(unknown");
+ }
+
+ /* This will be decremented by the code that asked for this value, but
+ we need to do this here because the return value of caller() is
+ mortal and has a refcnt of 1. */
+ SvREFCNT_inc(caller);
+#endif
+
+ return caller;
+ }
+}
+
+/* $value->isa alike validation */
+static IV
+validate_isa(SV* value, SV* package, char* id, HV* options) {
+ IV ok = 1;
+
+ if (! value) {
+ return 0;
+ }
+
+ SvGETMAGIC(value);
+ if (SvOK(value) && (sv_isobject(value) || (SvPOK(value) && ! looks_like_number(value)))) {
+ dSP;
+
+ SV* ret;
+ IV count;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ EXTEND(SP, 2);
+ PUSHs(value);
+ PUSHs(package);
+ PUTBACK;
+
+ count = call_method("isa", G_SCALAR);
+
+ if (! count)
+ croak("Calling isa did not return a value");
+
+ SPAGAIN;
+
+ ret = POPs;
+ SvGETMAGIC(ret);
+
+ ok = SvTRUE(ret);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ }
+ else {
+ ok = 0;
+ }
+
+ if (! ok) {
+ SV *caller = get_caller(options);
+ SV* buffer = newSVpvf(id, string_representation(value));
+ sv_catpv(buffer, " to ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " was not ");
+ sv_catpv(buffer, article(package));
+ sv_catpv(buffer, " '");
+ sv_catsv(buffer, package);
+ sv_catpv(buffer, "' (it is ");
+ if ( SvOK(value) ) {
+ sv_catpv(buffer, article(value));
+ sv_catpv(buffer, " ");
+ sv_catsv(buffer, value);
+ }
+ else {
+ sv_catpv(buffer, "undef");
+ }
+ sv_catpv(buffer, ")\n");
+ validation_failure(buffer, options);
+ }
+
+ return 1;
+}
+
+static IV
+validate_can(SV* value, SV* method, char* id, HV* options) {
+ IV ok = 1;
+
+ if (! value) {
+ return 0;
+ }
+
+ SvGETMAGIC(value);
+ if (SvOK(value) && (sv_isobject(value) || (SvPOK(value) && ! looks_like_number(value)))) {
+ dSP;
+
+ SV* ret;
+ IV count;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ EXTEND(SP, 2);
+ PUSHs(value);
+ PUSHs(method);
+ PUTBACK;
+
+ count = call_method("can", G_SCALAR);
+
+ if (! count)
+ croak("Calling can did not return a value");
+
+ SPAGAIN;
+
+ ret = POPs;
+ SvGETMAGIC(ret);
+
+ ok = SvTRUE(ret);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ }
+ else {
+ ok = 0;
+ }
+
+ if (! ok) {
+ SV* buffer = newSVpvf(id, string_representation(value));
+ SV *caller = get_caller(options);
+ sv_catpv(buffer, " to ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " does not have the method: '");
+ sv_catsv(buffer, method);
+ sv_catpv(buffer, "'\n");
+ validation_failure(buffer, options);
+ }
+
+ return 1;
+}
+
+/* validates specific parameter using supplied parameter specification */
+static IV
+validate_one_param(SV* value, SV* params, HV* spec, char* id, HV* options, IV* untaint) {
+ SV** temp;
+ IV i;
+
+ /*
+ HE* he;
+ hv_iterinit(spec);
+
+ while (he = hv_iternext(spec)) {
+ STRLEN len;
+ char* key = HePV(he, len);
+ int ok = 0;
+ int j;
+ for ( j = 0; j < VALID_KEY_COUNT; j++ ) {
+ if ( strcmp( key, valid_keys[j] ) == 0) {
+ ok = 1;
+ break;
+ }
+ }
+
+ if ( ! ok ) {
+ SV* buffer = sv_2mortal(newSVpv("\"",0));
+ sv_catpv( buffer, key );
+ sv_catpv( buffer, "\" is not an allowed validation spec key\n");
+ validation_failure(buffer, options);
+ }
+ }
+ */
+
+ /* check type */
+ if ((temp = hv_fetch(spec, "type", 4, 0))) {
+ IV type;
+
+ if ( ! ( SvOK(*temp)
+ && looks_like_number(*temp)
+ && SvIV(*temp) > 0 ) ) {
+
+ SV* buffer = newSVpvf(id, string_representation(value));
+ sv_catpv( buffer, " has a type specification which is not a number. It is ");
+ if ( SvOK(*temp) ) {
+ sv_catpv( buffer, "a string - " );
+ sv_catsv( buffer, *temp );
+ }
+ else {
+ sv_catpv( buffer, "undef");
+ }
+ sv_catpv( buffer, ".\n Use the constants exported by Params::Validate to declare types." );
+
+ validation_failure(buffer, options);
+ }
+
+ SvGETMAGIC(*temp);
+ type = get_type(value);
+ if (! (type & SvIV(*temp))) {
+ SV* buffer = newSVpvf(id, string_representation(value));
+ SV *caller = get_caller(options);
+ SV* is;
+ SV* allowed;
+
+ sv_catpv(buffer, " to ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " was ");
+ is = typemask_to_string(type);
+ allowed = typemask_to_string(SvIV(*temp));
+ sv_catpv(buffer, article(is));
+ sv_catpv(buffer, " '");
+ sv_catsv(buffer, is);
+ sv_catpv(buffer, "', which is not one of the allowed types: ");
+ sv_catsv(buffer, allowed);
+ sv_catpv(buffer, "\n");
+
+ validation_failure(buffer, options);
+ }
+ }
+
+ /* check isa */
+ if ((temp = hv_fetch(spec, "isa", 3, 0))) {
+ SvGETMAGIC(*temp);
+
+ if (SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVAV) {
+ AV* array = (AV*) SvRV(*temp);
+
+ for(i = 0; i <= av_len(array); i++) {
+ SV* package;
+
+ package = *av_fetch(array, i, 1);
+ if (! package) {
+ return 0;
+ }
+
+ SvGETMAGIC(package);
+ if (! validate_isa(value, package, id, options)) {
+ return 0;
+ }
+ }
+ }
+ else {
+ if (! validate_isa(value, *temp, id, options)) {
+ return 0;
+ }
+ }
+ }
+
+ /* check can */
+ if ((temp = hv_fetch(spec, "can", 3, 0))) {
+ SvGETMAGIC(*temp);
+ if (SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVAV) {
+ AV* array = (AV*) SvRV(*temp);
+
+ for (i = 0; i <= av_len(array); i++) {
+ SV* method;
+
+ method = *av_fetch(array, i, 1);
+ if (! method) {
+ return 0;
+ }
+
+ SvGETMAGIC(method);
+
+ if (! validate_can(value, method, id, options)) {
+ return 0;
+ }
+ }
+ }
+ else {
+ if (! validate_can(value, *temp, id, options)) {
+ return 0;
+ }
+ }
+ }
+
+ /* let callbacks to do their tests */
+ if ((temp = hv_fetch(spec, "callbacks", 9, 0))) {
+ HE* he;
+
+ SvGETMAGIC(*temp);
+ if (!(SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVHV)) {
+ SV* buffer = newSVpv("'callbacks' validation parameter for '", 0);
+ SV *caller = get_caller(options);
+
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " must be a hash reference\n");
+ validation_failure(buffer, options);
+ }
+
+ hv_iterinit((HV*) SvRV(*temp));
+ while ((he = hv_iternext((HV*) SvRV(*temp)))) {
+ SV* ret;
+ IV ok;
+ IV count;
+ SV *err;
+
+ if (!(SvROK(HeVAL(he)) && SvTYPE(SvRV(HeVAL(he))) == SVt_PVCV)) {
+ SV* buffer = newSVpv("callback '", 0);
+ SV *caller = get_caller(options);
+
+ sv_catsv(buffer, HeSVKEY_force(he));
+ sv_catpv(buffer, "' for ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " is not a subroutine reference\n");
+ validation_failure(buffer, options);
+ }
+
+ {
+ dSP;
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ EXTEND(SP, 2);
+ PUSHs(value);
+ mPUSHs(newRV_inc(params));
+ PUTBACK;
+
+ /* local $@ = q{}; */
+ save_scalar(PL_errgv);
+ sv_setpv(ERRSV, "");
+
+ count = call_sv(SvRV(HeVAL(he)), G_EVAL|G_SCALAR);
+
+ SPAGAIN;
+
+ if (!count) {
+ croak("Validation callback did not return anything");
+ }
+
+ ret = POPs;
+ SvGETMAGIC(ret);
+ ok = SvTRUE(ret);
+
+ err = newSV(0);
+ SvSetSV_nosteal(err, ERRSV);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ if (! ok) {
+ if (SvROK(err)) {
+ validation_failure(err, options);
+ }
+ else {
+ SV* buffer = newSVpvf(id, string_representation(value));
+ SV *caller = get_caller(options);
+
+ sv_catpv(buffer, " to ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " did not pass the '");
+ sv_catsv(buffer, HeSVKEY_force(he));
+ sv_catpv(buffer, "' callback");
+ if (SvLEN(err) > 0) {
+ sv_catpv(buffer, ": ");
+ sv_catsv(buffer, err);
+ }
+ sv_catpv(buffer, "\n");
+ validation_failure(buffer, options);
+ }
+ }
+ else {
+ SvREFCNT_dec(err);
+ }
+ }
+ }
+ }
+
+ if ((temp = hv_fetch(spec, "regex", 5, 0))) {
+ dSP;
+
+ IV has_regex = 0;
+ IV ok;
+
+ SvGETMAGIC(*temp);
+ if (SvPOK(*temp)) {
+ has_regex = 1;
+ }
+ else if (SvROK(*temp)) {
+ SV* svp;
+
+ svp = (SV*)SvRV(*temp);
+
+ #if PERL_VERSION <= 10
+ if (SvMAGICAL(svp) && mg_find(svp, PERL_MAGIC_qr)) {
+ has_regex = 1;
+ }
+ #else
+ if (SvTYPE(svp) == SVt_REGEXP) {
+ has_regex = 1;
+ }
+ #endif
+ }
+
+ if (!has_regex) {
+ SV* buffer = newSVpv("'regex' validation parameter for '", 0);
+ SV *caller = get_caller(options);
+
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " must be a string or qr// regex\n");
+ validation_failure(buffer, options);
+ }
+
+ PUSHMARK(SP);
+ EXTEND(SP, 2);
+ PUSHs(value);
+ PUSHs(*temp);
+ PUTBACK;
+ call_pv("Params::Validate::XS::_check_regex_from_xs", G_SCALAR);
+ SPAGAIN;
+ ok = POPi;
+ PUTBACK;
+
+ if (!ok) {
+ SV* buffer = newSVpvf(id, string_representation(value));
+ SV *caller = get_caller(options);
+
+ sv_catpv(buffer, " to ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " did not pass regex check\n");
+ validation_failure(buffer, options);
+ }
+ }
+
+ if ((temp = hv_fetch(spec, "untaint", 7, 0))) {
+ if (SvTRUE(*temp)) {
+ *untaint = 1;
+ }
+ }
+
+ return 1;
+}
+
+/* merges one hash into another (not deep copy) */
+static void
+merge_hashes(HV* in, HV* out) {
+ HE* he;
+
+ hv_iterinit(in);
+ while ((he = hv_iternext(in))) {
+ if (!hv_store_ent(out, HeSVKEY_force(he),
+ SvREFCNT_inc(HeVAL(he)), HeHASH(he))) {
+ SvREFCNT_dec(HeVAL(he));
+ croak("Cannot add new key to hash");
+ }
+ }
+}
+
+/* convert array to hash */
+static IV
+convert_array2hash(AV* in, HV* options, HV* out) {
+ IV i;
+ I32 len;
+
+ len = av_len(in);
+ if (len > -1 && len % 2 != 1) {
+ SV* buffer = newSVpv("Odd number of parameters in call to ", 0);
+ SV *caller = get_caller(options);
+
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " when named parameters were expected\n");
+
+ validation_failure(buffer, options);
+ }
+
+ for (i = 0; i <= av_len(in); i += 2) {
+ SV* key;
+ SV* value;
+
+ key = *av_fetch(in, i, 1);
+ if (! key) {
+ continue;
+ }
+
+ SvGETMAGIC(key);
+
+ /* We need to make a copy because if the array was @_, then the
+ values in the array are marked as readonly, which causes
+ problems when the hash being made gets returned to the
+ caller. */
+ value = sv_2mortal( newSVsv( *av_fetch(in, i + 1, 1) ) );
+
+ if (value) {
+ SvGETMAGIC(value);
+ }
+
+ if (! hv_store_ent(out, key, SvREFCNT_inc(value), 0)) {
+ SvREFCNT_dec(value);
+ croak("Cannot add new key to hash");
+ }
+ }
+
+ return 1;
+}
+
+/* get current Params::Validate options */
+static HV*
+get_options(HV* options) {
+ HV* OPTIONS;
+ HV* ret;
+ HE *he;
+ HV *stash;
+ SV* pkg;
+ SV *pkg_options;
+
+ ret = (HV*) sv_2mortal((SV*) newHV());
+
+ /* get package specific options */
+ stash = CopSTASH(PL_curcop);
+ pkg = sv_2mortal(newSVpv(HvNAME(stash), 0));
+
+ OPTIONS = get_hv("Params::Validate::OPTIONS", 1);
+ if ((he = hv_fetch_ent(OPTIONS, pkg, 0, 0))) {
+ pkg_options = HeVAL(he);
+ SvGETMAGIC(pkg_options);
+ if (SvROK(pkg_options) && SvTYPE(SvRV(pkg_options)) == SVt_PVHV) {
+ if (options) {
+ merge_hashes((HV*) SvRV(pkg_options), ret);
+ }
+ else {
+ return (HV*) SvRV(pkg_options);
+ }
+ }
+ }
+ if (options) {
+ merge_hashes(options, ret);
+ }
+
+ return ret;
+}
+
+static SV*
+normalize_one_key(SV* key, SV* normalize_func, SV* strip_leading, IV ignore_case) {
+ SV* copy;
+ STRLEN len_sl;
+ STRLEN len;
+ char *rawstr_sl;
+ char *rawstr;
+
+ copy = sv_2mortal(newSVsv(key));
+
+ /* if normalize_func is provided, ignore the other options */
+ if (normalize_func) {
+ dSP;
+
+ SV* normalized;
+
+ PUSHMARK(SP);
+ XPUSHs(copy);
+ PUTBACK;
+ if (! call_sv(SvRV(normalize_func), G_SCALAR)) {
+ croak("The normalize_keys callback did not return anything");
+ }
+ SPAGAIN;
+ normalized = POPs;
+ PUTBACK;
+
+ if (! SvOK(normalized)) {
+ croak("The normalize_keys callback did not return a defined value when normalizing the key '%s'", SvPV_nolen(copy));
+ }
+
+ return normalized;
+ }
+ else if (ignore_case || strip_leading) {
+ if (ignore_case) {
+ STRLEN i;
+
+ rawstr = SvPV(copy, len);
+ for (i = 0; i < len; i++) {
+ /* should this account for UTF8 strings? */
+ *(rawstr + i) = toLOWER(*(rawstr + i));
+ }
+ }
+
+ if (strip_leading) {
+ rawstr_sl = SvPV(strip_leading, len_sl);
+ rawstr = SvPV(copy, len);
+
+ if (len > len_sl && strnEQ(rawstr_sl, rawstr, len_sl)) {
+ copy = sv_2mortal(newSVpvn(rawstr + len_sl, len - len_sl));
+ }
+ }
+ }
+
+ return copy;
+}
+
+static HV*
+normalize_hash_keys(HV* p, SV* normalize_func, SV* strip_leading, IV ignore_case) {
+ SV* normalized;
+ HE* he;
+ HV* norm_p;
+
+ if (!normalize_func && !ignore_case && !strip_leading) {
+ return p;
+ }
+
+ norm_p = (HV*) sv_2mortal((SV*) newHV());
+ hv_iterinit(p);
+ while ((he = hv_iternext(p))) {
+ normalized =
+ normalize_one_key(HeSVKEY_force(he), normalize_func, strip_leading, ignore_case);
+
+ if (hv_fetch_ent(norm_p, normalized, 0, 0)) {
+ croak("The normalize_keys callback returned a key that already exists, '%s', when normalizing the key '%s'",
+ SvPV_nolen(normalized), SvPV_nolen(HeSVKEY_force(he)));
+ }
+
+ if (! hv_store_ent(norm_p, normalized, SvREFCNT_inc(HeVAL(he)), 0)) {
+ SvREFCNT_dec(HeVAL(he));
+ croak("Cannot add new key to hash");
+ }
+ }
+ return norm_p;
+}
+
+static IV
+validate_pos_depends(AV* p, AV* specs, HV* options) {
+ IV p_idx;
+ SV** depends;
+ SV** p_spec;
+
+ for (p_idx = 0; p_idx <= av_len(p); p_idx++) {
+ p_spec = av_fetch(specs, p_idx, 0);
+
+ if (p_spec != NULL && SvROK(*p_spec) &&
+ SvTYPE(SvRV(*p_spec)) == SVt_PVHV) {
+
+ depends = hv_fetch((HV*) SvRV(*p_spec), "depends", 7, 0);
+
+ if (! depends) {
+ return 1;
+ }
+
+ if (SvROK(*depends)) {
+ croak("Arguments to 'depends' for validate_pos() must be a scalar");
+ }
+
+ if (av_len(p) < SvIV(*depends) -1) {
+ SV *buffer =
+ newSVpvf("Parameter #%d depends on parameter #%d, which was not given",
+ (int) p_idx + 1,
+ (int) SvIV(*depends));
+
+ validation_failure(buffer, options);
+ }
+ }
+ }
+
+ return 1;
+}
+
+static IV
+validate_named_depends(HV* p, HV* specs, HV* options) {
+ HE* he;
+ HE* he1;
+ SV* buffer;
+ SV** depends_value;
+ AV* depends_list;
+ SV* depend_name;
+ SV* temp;
+ I32 d_idx;
+
+ /* the basic idea here is to iterate through the parameters
+ * (which we assumed to have already gone through validation
+ * via validate_one_param()), and the check to see if that
+ * parameter contains a "depends" spec. If it does, we'll
+ * check if that parameter specified by depends exists in p
+ */
+ hv_iterinit(p);
+ while ((he = hv_iternext(p))) {
+ he1 = hv_fetch_ent(specs, HeSVKEY_force(he), 0, HeHASH(he));
+
+ if (he1 && SvROK(HeVAL(he1)) &&
+ SvTYPE(SvRV(HeVAL(he1))) == SVt_PVHV) {
+
+ if (hv_exists((HV*) SvRV(HeVAL(he1)), "depends", 7)) {
+
+ depends_value = hv_fetch((HV*) SvRV(HeVAL(he1)), "depends", 7, 0);
+
+ if (! depends_value) {
+ return 1;
+ }
+
+ if (! SvROK(*depends_value)) {
+ depends_list = (AV*) sv_2mortal((SV*) newAV());
+ temp = sv_2mortal(newSVsv(*depends_value));
+ av_push(depends_list,SvREFCNT_inc(temp));
+ }
+ else if (SvTYPE(SvRV(*depends_value)) == SVt_PVAV) {
+ depends_list = (AV*) SvRV(*depends_value);
+ }
+ else {
+ croak("Arguments to 'depends' must be a scalar or arrayref");
+ }
+
+ for (d_idx =0; d_idx <= av_len(depends_list); d_idx++) {
+
+ depend_name = *av_fetch(depends_list, d_idx, 0);
+
+ /* first check if the parameter to which this
+ * depends on was given to us
+ */
+ if (!hv_exists(p, SvPV_nolen(depend_name),
+ SvCUR(depend_name))) {
+ /* oh-oh, the parameter that this parameter
+ * depends on is not available. Let's first check
+ * if this is even valid in the spec (i.e., the
+ * spec actually contains a spec for such parameter)
+ */
+ if (!hv_exists(specs, SvPV_nolen(depend_name),
+ SvCUR(depend_name))) {
+
+ buffer =
+ sv_2mortal(newSVpv("Following parameter specified in depends for '", 0));
+
+ sv_catsv(buffer, HeSVKEY_force(he1));
+ sv_catpv(buffer, "' does not exist in spec: ");
+ sv_catsv(buffer, depend_name);
+
+ croak("%s", SvPV_nolen(buffer));
+ }
+ /* if we got here, the spec was correct. we just
+ * need to issue a regular validation failure
+ */
+ buffer = newSVpv( "Parameter '", 0);
+ sv_catsv(buffer, HeSVKEY_force(he1));
+ sv_catpv(buffer, "' depends on parameter '");
+ sv_catsv(buffer, depend_name);
+ sv_catpv(buffer, "', which was not given");
+ validation_failure(buffer, options);
+ }
+ }
+ }
+ }
+ }
+ return 1;
+}
+
+void
+apply_defaults(HV *ret, HV *p, HV *specs, AV *missing) {
+ HE* he;
+ SV** temp;
+
+ hv_iterinit(specs);
+ while ((he = hv_iternext(specs))) {
+ HV* spec;
+ SV* val;
+
+ val = HeVAL(he);
+
+ /* get extended param spec if available */
+ if (val && SvROK(val) && SvTYPE(SvRV(val)) == SVt_PVHV) {
+ spec = (HV*) SvRV(val);
+ }
+ else {
+ spec = NULL;
+ }
+
+ /* test for parameter existence */
+ if (hv_exists_ent(p, HeSVKEY_force(he), HeHASH(he))) {
+ continue;
+ }
+
+ /* parameter may not be defined but we may have default */
+ if (spec && (temp = hv_fetch(spec, "default", 7, 0))) {
+ SV* value;
+
+ SvGETMAGIC(*temp);
+ value = sv_2mortal(newSVsv(*temp));
+
+ /* make sure that parameter is put into return hash */
+ if (GIMME_V != G_VOID) {
+ if (!hv_store_ent(ret, HeSVKEY_force(he),
+ SvREFCNT_inc(value), HeHASH(he))) {
+ SvREFCNT_dec(value);
+ croak("Cannot add new key to hash");
+ }
+ }
+
+ continue;
+ }
+
+ /* find if missing parameter is mandatory */
+ if (! no_validation()) {
+ SV** temp;
+
+ if (spec) {
+ if ((temp = hv_fetch(spec, "optional", 8, 0))) {
+ SvGETMAGIC(*temp);
+
+ if (SvTRUE(*temp)) continue;
+ }
+ }
+ else if (!SvTRUE(HeVAL(he))) {
+ continue;
+ }
+ av_push(missing, SvREFCNT_inc(HeSVKEY_force(he)));
+ }
+ }
+}
+
+static IV
+validate(HV* p, HV* specs, HV* options, HV* ret) {
+ AV* missing;
+ AV* unmentioned;
+ HE* he;
+ HE* he1;
+ SV* hv;
+ SV* hv1;
+ IV ignore_case = 0;
+ SV* strip_leading = NULL;
+ IV allow_extra = 0;
+ SV** temp;
+ SV* normalize_func = NULL;
+ AV* untaint_keys = (AV*) sv_2mortal((SV*) newAV());
+ IV i;
+
+ if ((temp = hv_fetch(options, "ignore_case", 11, 0))) {
+ SvGETMAGIC(*temp);
+ ignore_case = SvTRUE(*temp);
+ }
+
+ if ((temp = hv_fetch(options, "strip_leading", 13, 0))) {
+ SvGETMAGIC(*temp);
+ if (SvOK(*temp)) strip_leading = *temp;
+ }
+
+ if ((temp = hv_fetch(options, "normalize_keys", 14, 0))) {
+ SvGETMAGIC(*temp);
+ if(SvROK(*temp) && SvTYPE(SvRV(*temp)) == SVt_PVCV) {
+ normalize_func = *temp;
+ }
+ }
+
+ if (normalize_func || ignore_case || strip_leading) {
+ p = normalize_hash_keys(p, normalize_func, strip_leading, ignore_case);
+ specs = normalize_hash_keys(specs, normalize_func, strip_leading, ignore_case);
+ }
+
+ /* short-circuit everything else when no_validation is true */
+ if (no_validation()) {
+ if (GIMME_V != G_VOID) {
+ while ((he = hv_iternext(p))) {
+ hv = HeVAL(he);
+ if (! hv) {
+ continue;
+ }
+
+ SvGETMAGIC(hv);
+
+ /* put the parameter into return hash */
+ if (!hv_store_ent(ret, HeSVKEY_force(he), SvREFCNT_inc(hv),
+ HeHASH(he))) {
+ SvREFCNT_dec(hv);
+ croak("Cannot add new key to hash");
+ }
+ }
+ apply_defaults(ret, p, specs, NULL);
+ }
+
+ return 1;
+ }
+
+ if ((temp = hv_fetch(options, "allow_extra", 11, 0))) {
+ SvGETMAGIC(*temp);
+ allow_extra = SvTRUE(*temp);
+ }
+
+ /* find extra parameters and validate good parameters */
+ unmentioned = (AV*) sv_2mortal((SV*) newAV());
+
+ hv_iterinit(p);
+ while ((he = hv_iternext(p))) {
+ hv = HeVAL(he);
+ if (! hv) {
+ continue;
+ }
+
+ SvGETMAGIC(hv);
+
+ /* put the parameter into return hash */
+ if (GIMME_V != G_VOID) {
+ if (!hv_store_ent(ret, HeSVKEY_force(he), SvREFCNT_inc(hv),
+ HeHASH(he))) {
+ SvREFCNT_dec(hv);
+ croak("Cannot add new key to hash");
+ }
+ }
+
+ /* check if this parameter is defined in spec and if it is
+ then validate it using spec */
+ he1 = hv_fetch_ent(specs, HeSVKEY_force(he), 0, HeHASH(he));
+ if(he1) {
+ hv1 = HeVAL(he1);
+ if (SvROK(hv1) && SvTYPE(SvRV(hv1)) == SVt_PVHV) {
+ char* buffer;
+ HV* spec;
+ IV untaint = 0;
+
+ spec = (HV*) SvRV(hv1);
+ buffer = form("The '%s' parameter (%%s)", HePV(he, PL_na));
+
+ if (! validate_one_param(hv, (SV*) p, spec, buffer, options, &untaint))
+ return 0;
+
+ /* The value stored here is meaningless, we're just tracking
+ keys to untaint later */
+ if (untaint) {
+ av_push(untaint_keys, SvREFCNT_inc(HeSVKEY_force(he1)));
+ }
+ }
+ }
+ else if (! allow_extra) {
+ av_push(unmentioned, SvREFCNT_inc(HeSVKEY_force(he)));
+ }
+
+ if (av_len(unmentioned) > -1) {
+ SV* buffer = newSVpv("The following parameter", 0);
+ SV *caller = get_caller(options);
+
+ if (av_len(unmentioned) != 0) {
+ sv_catpv(buffer, "s were ");
+ }
+ else {
+ sv_catpv(buffer, " was ");
+ }
+ sv_catpv(buffer, "passed in the call to ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, " but ");
+ if (av_len(unmentioned) != 0) {
+ sv_catpv(buffer, "were ");
+ }
+ else {
+ sv_catpv(buffer, "was ");
+ }
+ sv_catpv(buffer, "not listed in the validation options: ");
+ for(i = 0; i <= av_len(unmentioned); i++) {
+ sv_catsv(buffer, *av_fetch(unmentioned, i, 1));
+ if (i < av_len(unmentioned)) {
+ sv_catpv(buffer, " ");
+ }
+ }
+ sv_catpv(buffer, "\n");
+
+ validation_failure(buffer, options);
+ }
+ }
+
+ validate_named_depends(p, specs, options);
+
+ /* find missing parameters */
+ missing = (AV*) sv_2mortal((SV*) newAV());
+
+ apply_defaults(ret, p, specs, missing);
+
+ if (av_len(missing) > -1) {
+ SV* buffer = newSVpv("Mandatory parameter", 0);
+ SV *caller = get_caller(options);
+
+ if (av_len(missing) > 0) {
+ sv_catpv(buffer, "s ");
+ }
+ else {
+ sv_catpv(buffer, " ");
+ }
+
+ for(i = 0; i <= av_len(missing); i++) {
+ sv_catpvf(buffer, "'%s'",
+ SvPV_nolen(*av_fetch(missing, i, 0)));
+ if (i < av_len(missing)) {
+ sv_catpv(buffer, ", ");
+ }
+ }
+ sv_catpv(buffer, " missing in call to ");
+ sv_catsv(buffer, caller);
+ SvREFCNT_dec(caller);
+ sv_catpv(buffer, "\n");
+
+ validation_failure(buffer, options);
+ }
+
+ if (GIMME_V != G_VOID) {
+ for (i = 0; i <= av_len(untaint_keys); i++) {
+ SvTAINTED_off(HeVAL(hv_fetch_ent(p, *av_fetch(untaint_keys, i, 0), 0, 0)));
+ }
+ }
+
+ return 1;
+}
+
+static SV*
+validate_pos_failure(IV pnum, IV min, IV max, HV* options) {
+ SV* buffer;
+ SV** temp;
+ IV allow_extra;
+
+ if ((temp = hv_fetch(options, "allow_extra", 11, 0))) {
+ SvGETMAGIC(*temp);
+ allow_extra = SvTRUE(*temp);
+ }
+ else {
+ allow_extra = 0;
+ }
+
+ buffer = newSViv(pnum + 1);
+ if (pnum != 0) {
+ sv_catpv(buffer, " parameters were passed to ");
+ }
+ else {
+ sv_catpv(buffer, " parameter was passed to ");
+ }
+ sv_catsv(buffer, get_caller(options));
+ sv_catpv(buffer, " but ");
+ if (!allow_extra) {
+ if (min != max) {
+ sv_catpvf(buffer, "%d - %d", (int) min + 1, (int) max + 1);
+ }
+ else {
+ sv_catpvf(buffer, "%d", (int) max + 1);
+ }
+ }
+ else {
+ sv_catpvf(buffer, "at least %d", (int) min + 1);
+ }
+ if ((allow_extra ? min : max) != 0) {
+ sv_catpv(buffer, " were expected\n");
+ }
+ else {
+ sv_catpv(buffer, " was expected\n");
+ }
+
+ return buffer;
+}
+
+/* Given a single parameter spec and a corresponding complex spec form
+ of it (which must be false if the spec is not complex), return true
+ says that the parameter is options. */
+static bool
+spec_says_optional(SV* spec, IV complex_spec) {
+ SV** temp;
+
+ if (complex_spec) {
+ if ((temp = hv_fetch((HV*) SvRV(spec), "optional", 8, 0))) {
+ SvGETMAGIC(*temp);
+ if (!SvTRUE(*temp))
+ return FALSE;
+ }
+ else {
+ return FALSE;
+ }
+ }
+ else {
+ if (SvTRUE(spec)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static IV
+validate_pos(AV* p, AV* specs, HV* options, AV* ret) {
+ char* buffer;
+ SV* value;
+ SV* spec = NULL;
+ SV** temp;
+ IV i;
+ IV complex_spec = 0;
+ IV allow_extra;
+ /* Index of highest-indexed required parameter known so far, or -1
+ if no required parameters are known yet. */
+ IV min = -1;
+ AV* untaint_indexes = (AV*) sv_2mortal((SV*) newAV());
+
+ if (no_validation()) {
+ IV spec_count = av_len(specs);
+ IV p_count = av_len(p);
+ IV max = spec_count > p_count ? spec_count : p_count;
+
+ if (GIMME_V == G_VOID) {
+ return 1;
+ }
+
+ for (i = 0; i <= max; i++) {
+ if (i <= spec_count) {
+ spec = *av_fetch(specs, i, 1);
+ if (spec) {
+ SvGETMAGIC(spec);
+ }
+ complex_spec = (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV);
+ }
+
+ if (i <= av_len(p)) {
+ value = *av_fetch(p, i, 1);
+ SvGETMAGIC(value);
+ av_push(ret, SvREFCNT_inc(value));
+ } else if (complex_spec &&
+ (temp = hv_fetch((HV*) SvRV(spec), "default", 7, 0))) {
+ SvGETMAGIC(*temp);
+ av_push(ret, SvREFCNT_inc(*temp));
+ }
+ }
+ return 1;
+ }
+
+ /* iterate through all parameters and validate them */
+ for (i = 0; i <= av_len(specs); i++) {
+ spec = *av_fetch(specs, i, 1);
+ if (! spec) {
+ continue;
+ }
+ SvGETMAGIC(spec);
+ complex_spec = (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV);
+
+ /* Unless the current spec refers to an optional argument, update
+ our notion of the index of the highest-idexed required
+ parameter. */
+ if (! spec_says_optional(spec, complex_spec) ) {
+ min = i;
+ }
+
+ if (i <= av_len(p)) {
+ value = *av_fetch(p, i, 1);
+ SvGETMAGIC(value);
+
+ if (complex_spec) {
+ IV untaint = 0;
+
+ buffer = form("Parameter #%d (%%s)", (int)i + 1);
+
+ if (! validate_one_param(value, (SV*) p, (HV*) SvRV(spec), buffer, options, &untaint)) {
+ return 0;
+ }
+
+ if (untaint) {
+ av_push(untaint_indexes, newSViv(i));
+ }
+ }
+
+ if (GIMME_V != G_VOID) {
+ av_push(ret, SvREFCNT_inc(value));
+ }
+
+ } else if (complex_spec &&
+ (temp = hv_fetch((HV*) SvRV(spec), "default", 7, 0))) {
+ SvGETMAGIC(*temp);
+
+ if (GIMME_V != G_VOID) {
+ av_store(ret, i, SvREFCNT_inc(*temp));
+ }
+
+ }
+ else {
+ if (i == min) {
+ /* We don't have as many arguments as the arg spec requires. */
+ SV* buffer;
+
+ /* Look forward through remaining argument specifications to
+ find the last non-optional one, so we can correctly report the
+ number of arguments required. */
+ for (i++ ; i <= av_len(specs); i++) {
+ spec = *av_fetch(specs, i, 1);
+ if (! spec) {
+ continue;
+ }
+
+ SvGETMAGIC(spec);
+ complex_spec = (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV);
+ if (! spec_says_optional(spec, complex_spec)) {
+ min = i;
+ }
+ if (min != i)
+ break;
+ }
+
+ buffer = validate_pos_failure(av_len(p), min, av_len(specs), options);
+
+ validation_failure(buffer, options);
+ }
+ }
+ }
+
+ validate_pos_depends(p, specs, options);
+
+ /* test for extra parameters */
+ if (av_len(p) > av_len(specs)) {
+ if ((temp = hv_fetch(options, "allow_extra", 11, 0))) {
+ SvGETMAGIC(*temp);
+ allow_extra = SvTRUE(*temp);
+ }
+ else {
+ allow_extra = 0;
+ }
+ if (allow_extra) {
+ /* put all additional parameters into return array */
+ if (GIMME_V != G_VOID) {
+ for(i = av_len(specs) + 1; i <= av_len(p); i++) {
+ value = *av_fetch(p, i, 1);
+ if (value) {
+ SvGETMAGIC(value);
+ av_push(ret, SvREFCNT_inc(value));
+ }
+ else {
+ av_push(ret, &PL_sv_undef);
+ }
+ }
+ }
+ }
+ else {
+ SV* buffer = validate_pos_failure(av_len(p), min, av_len(specs), options);
+ validation_failure(buffer, options);
+ }
+ }
+
+ if (GIMME_V != G_VOID) {
+ for (i = 0; i <= av_len(untaint_indexes); i++) {
+ SvTAINTED_off(*av_fetch(p, SvIV(*av_fetch(untaint_indexes, i, 0)), 0));
+ }
+ }
+
+ return 1;
+}
+
+MODULE = Params::Validate::XS PACKAGE = Params::Validate::XS
+
+void
+validate(p, specs)
+ SV* p
+ SV* specs
+
+ PROTOTYPE: \@$
+
+ PPCODE:
+
+ HV* ret = NULL;
+ AV* pa;
+ HV* ph;
+ HV* options;
+
+ if (no_validation() && GIMME_V == G_VOID) {
+ XSRETURN(0);
+ }
+
+ SvGETMAGIC(p);
+ if (! (SvROK(p) && SvTYPE(SvRV(p)) == SVt_PVAV)) {
+ croak("Expecting array reference as first parameter");
+ }
+
+ SvGETMAGIC(specs);
+ if (! (SvROK(specs) && SvTYPE(SvRV(specs)) == SVt_PVHV)) {
+ croak("Expecting hash reference as second parameter");
+ }
+
+ pa = (AV*) SvRV(p);
+ ph = NULL;
+ if (av_len(pa) == 0) {
+ /* we were called as validate( @_, ... ) where @_ has a
+ single element, a hash reference */
+ SV* value;
+
+ value = *av_fetch(pa, 0, 1);
+ if (value) {
+ SvGETMAGIC(value);
+ if (SvROK(value) && SvTYPE(SvRV(value)) == SVt_PVHV) {
+ ph = (HV*) SvRV(value);
+ }
+ }
+ }
+
+ options = get_options(NULL);
+
+ if (! ph) {
+ ph = (HV*) sv_2mortal((SV*) newHV());
+
+ if (! convert_array2hash(pa, options, ph) ) {
+ XSRETURN(0);
+ }
+ }
+ if (GIMME_V != G_VOID) {
+ ret = (HV*) sv_2mortal((SV*) newHV());
+ }
+ if (! validate(ph, (HV*) SvRV(specs), options, ret)) {
+ XSRETURN(0);
+ }
+ RETURN_HASH(ret);
+
+void
+validate_pos(p, ...)
+SV* p
+
+ PROTOTYPE: \@@
+
+ PPCODE:
+
+ AV* specs;
+ AV* ret = NULL;
+ IV i;
+
+ if (no_validation() && GIMME_V == G_VOID) {
+ XSRETURN(0);
+ }
+
+ SvGETMAGIC(p);
+ if (!SvROK(p) || !(SvTYPE(SvRV(p)) == SVt_PVAV)) {
+ croak("Expecting array reference as first parameter");
+ }
+
+ specs = (AV*) sv_2mortal((SV*) newAV());
+ av_extend(specs, items);
+ for(i = 1; i < items; i++) {
+ if (!av_store(specs, i - 1, SvREFCNT_inc(ST(i)))) {
+ SvREFCNT_dec(ST(i));
+ croak("Cannot store value in array");
+ }
+ }
+
+ if (GIMME_V != G_VOID) {
+ ret = (AV*) sv_2mortal((SV*) newAV());
+ }
+
+ if (! validate_pos((AV*) SvRV(p), specs, get_options(NULL), ret)) {
+ XSRETURN(0);
+ }
+
+ RETURN_ARRAY(ret);
+
+void
+validate_with(...)
+
+ PPCODE:
+
+ HV* p;
+ SV* params;
+ SV* spec;
+ IV i;
+
+ if (no_validation() && GIMME_V == G_VOID) XSRETURN(0);
+
+ /* put input list into hash */
+ p = (HV*) sv_2mortal((SV*) newHV());
+ for(i = 0; i < items; i += 2) {
+ SV* key;
+ SV* value;
+
+ key = ST(i);
+ if (i + 1 < items) {
+ value = ST(i + 1);
+ }
+ else {
+ value = &PL_sv_undef;
+ }
+ if (! hv_store_ent(p, key, SvREFCNT_inc(value), 0)) {
+ SvREFCNT_dec(value);
+ croak("Cannot add new key to hash");
+ }
+ }
+
+ params = *hv_fetch(p, "params", 6, 1);
+ SvGETMAGIC(params);
+ spec = *hv_fetch(p, "spec", 4, 1);
+ SvGETMAGIC(spec);
+
+ if (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVAV) {
+ if (SvROK(params) && SvTYPE(SvRV(params)) == SVt_PVAV) {
+ AV* ret = NULL;
+
+ if (GIMME_V != G_VOID) {
+ ret = (AV*) sv_2mortal((SV*) newAV());
+ }
+
+ PUTBACK;
+
+ if (! validate_pos((AV*) SvRV(params), (AV*) SvRV(spec), get_options(p), ret)) {
+ SPAGAIN;
+ XSRETURN(0);
+ }
+
+ SPAGAIN;
+ RETURN_ARRAY(ret);
+ }
+ else {
+ croak("Expecting array reference in 'params'");
+ }
+ }
+ else if (SvROK(spec) && SvTYPE(SvRV(spec)) == SVt_PVHV) {
+ HV* hv;
+ HV* ret = NULL;
+ HV* options;
+
+ options = get_options(p);
+
+ if (SvROK(params) && SvTYPE(SvRV(params)) == SVt_PVHV) {
+ hv = (HV*) SvRV(params);
+ }
+ else if (SvROK(params) && SvTYPE(SvRV(params)) == SVt_PVAV) {
+ I32 hv_set = 0;
+
+ /* Check to see if we have a one element array
+ containing a hash reference */
+ if (av_len((AV*) SvRV(params)) == 0) {
+ SV** first_elem;
+
+ first_elem = av_fetch((AV*) SvRV(params), 0, 0);
+
+ if (first_elem && SvROK(*first_elem) &&
+ SvTYPE(SvRV(*first_elem)) == SVt_PVHV) {
+
+ hv = (HV*) SvRV(*first_elem);
+ hv_set = 1;
+ }
+ }
+
+ if (! hv_set) {
+ hv = (HV*) sv_2mortal((SV*) newHV());
+
+ if (! convert_array2hash((AV*) SvRV(params), options, hv))
+ XSRETURN(0);
+ }
+ }
+ else {
+ croak("Expecting array or hash reference in 'params'");
+ }
+
+ if (GIMME_V != G_VOID) {
+ ret = (HV*) sv_2mortal((SV*) newHV());
+ }
+
+ PUTBACK;
+
+ if (! validate(hv, (HV*) SvRV(spec), options, ret)) {
+ SPAGAIN;
+ XSRETURN(0);
+ }
+
+ SPAGAIN;
+ RETURN_HASH(ret);
+ }
+ else {
+ croak("Expecting array or hash reference in 'spec'");
+ }
diff --git a/lib/Params/ValidatePP.pm b/lib/Params/ValidatePP.pm
new file mode 100644
index 0000000..9740cc9
--- /dev/null
+++ b/lib/Params/ValidatePP.pm
@@ -0,0 +1,9 @@
+package # hide from PAUSE
+ Params::Validate;
+
+our $VERSION = '1.20';
+
+BEGIN { $ENV{PARAMS_VALIDATE_IMPLEMENTATION} = 'PP' }
+use Params::Validate;
+
+1;
diff --git a/lib/Params/ValidateXS.pm b/lib/Params/ValidateXS.pm
new file mode 100644
index 0000000..4f07801
--- /dev/null
+++ b/lib/Params/ValidateXS.pm
@@ -0,0 +1,9 @@
+package # hide from PAUSE
+ Params::Validate;
+
+our $VERSION = '1.20';
+
+BEGIN { $ENV{PARAMS_VALIDATE_IMPLEMENTATION} = 'XS' }
+use Params::Validate;
+
+1;