NAME

Params::Signature - support for parameter validation based on a subroutine signature, including type declaration, default values, optional parameters, and more

VERSION

Version 0.05

SYNOPSIS

The examples below do not cover more advanced scenarios which are covered in the Params::Signature::Manual. This page deals primarily with the class' public methods.

use Params::Signature qw(:all);
use Type::Utils;

# use built-in types in signature
my $params_hashref = validate(\@_, ["Str s", "Int i"])

# register new types to extend default type constraint system
our $Foo_Bar = class_type Foo_Bar => {class => "Foo::Bar"};
our $DoesIt = role_type DoesIt => {class => "MyRoles::Does::It"};
our $is_1_2_or_3 = enum "is_1_2_or_3", [qw(1 2 3)];

# use a CODE ref as a type constraint
our $HashRef2 = sub { ref($_[0]) eq "HASH" };

my $params_hashref = validate(\@_, ["DoesIt doit", "HashRef a_hash"])
my $params_hashref2 = validate(\@_, ["DoesIt doit", "HashRef2 a_hash"])

# to magically coerce a hash into a Foo::Bar via validate()
coerce "Foo_Bar", from "HashRef", via { new Foo::Bar(%{$_[0]}) };

# if bar is a HashRef in @_ it will be coerced by validate
# into a Foo::Bar object in @params_array
my @params_array = validate(\@_, ["is_1_2_or_3 number", "Foo_Bar bar"])

my $something = Something->new();
@params_array = $something->method(1);
# @params_array = (1, 2)

package Something;
sub new {...};

sub method
{
  # Note: the subroutine called to set the default value MUST be
  #       fully qualified (include the package name)
  ($self, $one, $two) = Params::Signature->validate_method(\@_, ["Object self", "Str one", "Int two = Something::set_two()"]);
  return ($one, $two);
}
sub set_two
{
  return(2)
}

DESCRIPTION

In its simplest form, you call Params::Signature's validate method with your parameters and a signature specification:

$params = $signature->validate(\@_, ["Str x = 'default'", "Undef|Str y?"]);

The signature is a list of parameter definitions. A basic parameter definition is a string which consists of a type constraint and the name of the parameter. The actual type constraints are defined via an external module, such as Type::Tiny or Moose::Util::TypeConstraints. Type constraints can also be implemented as subroutine references. Parameters are required by default. A default value may be assigned to a required parameter, but not an optional one. Parameters may be flagged as optional using the optional flag (trailing question mark).

More advanced scenarios are also supported. Per-parameter callbacks can be used for advanced parameter validation. Parameter aliases can be used to call a parameter by different names (e.g., "-param" or "param") In some cases, instead of using aliases, it may make more sense to use a subroutine to normalize parameter names (i.e., convert "-param" to "param"). Advanced topics such as these are discussed in the Params::Signature::Manual.

METHODS

All functionality is accessed via methods. You can use class methods (Params::Signature-method()>) or construct a module/application-specific Params::Signature (singleton) object. Class methods are used to validate parameters using the default global settings. By default, parameters are positional, fuzzy logic is disabled, and Carp::confess is used to report failures. Class methods work just like object methods. The exported subroutines validate and check are actually calls to the class methods with the default settings.

use Type::Utils;
use Params::Signature qw(:all);  # exports 'validate', 'check', 'hash_param_ok', 'kv_param_ok', 'strict_param'

our $EvenInt = declare "EvenInt",
                as Int,
                via { $_[0] % 2 == 0 };
our $EvenNum = declare "EvenNum",
                as Num,
                via { $_[0] % 2 == 0 };


# later ... use new types in a signature 
Params::Signature->validate(
                \@_,
                ["EvenInt one", "EvenNum two", "Str three"]
                );
# - same as -
validate(\@_,
        ["EvenInt one", "EvenNum two", "Str three"]
        );

#  - or use an object -

my $signature = new Params::Signature(fuzzy => hash_param_ok);

# later ... use new type known only to $signature
$signature->validate(\@_, ["EvenNum one", "EvenInt two", "Str three"]);

new

new Params::Signature(
        param_style => positional_style,
        fuzzy => hash_param_ok,
        coerce => 1,
        on_fail => sub { oops($_[0]) },
        normalize_keys => sub { lc $_[0] },
        called => "My::Module"
    );

param_style: Parameter style is one of "positional_style", "named_style" or "mixed_style". The actual signature passed to "validate" may override the default set in the new signature object. The default value in the object is used when the actual subroutine signature lacks sufficient information to determine the parameter style.

fuzzy: Allow the "validate" method to use 'fuzzy logic' to determine the parameter passing style used to invoke the caller. Enable this if you want to be able to call a subroutine with either positional parameters or key/value pairs that match the subroutine signature. When using named parameters, values should be passed as a hash. However, "raw" key/value pairs in @_ are supported but are generally not considered a good practice. If raw key/value pairs are used, the list must be balanced (it must have an even number of values). The fuzzy logic confirms that at least one parameter name from the signature appears as a key (however the key is found).

0 | strict_param

Fuzzy parameter checking is disabled

1 | hash_param_ok

Fuzzy parameter checking is enabled, but named parameters MUST be passed in a hash at $_[0]. At least 1 parameter name from the signature must exist in the hash, otherwise the hash will not be treated specially.

2 | kv_param_ok

Fuzzy parameter checking is enabled, so named parameters MAY be passed in a hash at $_[0] OR as raw key/value pairs. If a hash is used, the rules for "fuzzy=1" are used. Raw key/values must be "balanced" (an even number) in order to be treated as the key/value pairs of a hash. At least 1 parameter name from the signature must exist in this hash, otherwise the parameters are not given any special treatment. This form is potentially dangerous because it can be ambiguous. You've been warned.

my $fuzzy_signature = new Params::Signature(fuzzy => kv_param_ok);

...

# ok - positional: all parameters defined in signature are present 
foo("hi", 2, 3); 

# ok - named: both parameter names are at correct index 
foo(one => "hi", two => 2);

# preferred - named: hash makes it clear that "named" style is in 
# use and any unbalanced parameters will be caught at compile time
foo({one => "hi", two => 2});

# poor: 
#   fuzzy=1|hash_param_ok: this would be processed as positional
#   fuzzy=2|kv_param_ok: this would be processed as named
foo(one => 1);

# really BAD:
# is "one" supposed to be a parameter name or
# an actual value? it's impossible to tell!
#
#  fuzzy=1|hash_param_ok: processed as positional
#         in foo: $param = { "one" => "one", "two" => 2, "p_2" => 3, "p_3" => 4 }
#
#  fuzzy=2|kv_param_ok: processed as named
#         in foo: $param = { "one" => 2, "3" => 4 }
foo("one", 2, 3, 4);

# not as bad: the list is uneven, so it will not be treated
# as key/value pairs; these are handled as positional
#   in foo: $param = { "one" => "one", "two" => 2, "p_2" => 3 }
foo("one", 2, 3);

sub foo
{
    my $param = $fuzzy_signature->validate(
                    \@_,
                    ["Str one", "Int two?", "..."]
                    );
    ...
}

If fuzzy is enabled, it is mandatory that you pass named values in one anonymous hash or keys as positional values at the appropriate index position. That said, you really should use a hash and not raw key/value pairs. A clash between a raw value and a key name could lead to really bad results. Consider yourself warned! Consider fuzzy=1 and a real hash as a "best practice".

coerce: If a parameter value can be coerced into the type required in a signature, the "validate" method will automatically coerce it and return the coerced value rather than the original value. If multiple types are accepted for a parameter, "validate" will attempt to coerce the parameter value into each of the acceptable types until it finds one that succeeds or all coercion attempts fail. Coercions are implemented by the type constraint.

# this will coerce the value for 'even' into an EvenInt first
# then an EvenNum (assuming coercions for both types have
# been registered)
@params = validate(\@_, ["EvenInt|EvenNum even"]);

on_fail: Set the subroutine to call if an error is encountered. Carp::confess is called by default.

normalize_keys: A subroutine to normalize named parameters passed in to caller.

my $signature = new Params::Signature(
                    param_style => named_style,
                    normalize_keys => sub { $_[0] =~ s/^-//; lc $_[0] }
                    );

sub foo
{
    my $params = $signature->validate(
                    params => \@_,
                    signature => ["Int one"]
                    );
    ...
}
foo({-one => 1});
foo({-ONE => 1});
foo({one => 1});

called: String inserted at the beginning of each failure message. Your module or application name are good candidates for this value.

validate

Validate the parameters passed to a subroutine using a subroutine signature.

# use class method
my $params = Params::Signature->validate(
                  \@_,
                  [
                      "Int one",
                      "Int two",
                      "Int :$named_param",
                      "Int :$opt_named",
                      "..."
                  ]
              );

# use exported subroutine
my $params = validate(
                  \@_,
                  [
                      "Int one",
                      "Int two",
                      "Int :$named_param",
                      "Int :$opt_named",
                      "..."
                  ]
              );

# use object
my $params = $signature->validate(
                        \@_,
                        [
                            "Int one",
                            "Int two",
                            "Int :$named_param",
                            "Int :$opt_named",
                            "..."
                        ]
                    );

# use configuration hash (3rd parameter) to override default settings
my $params = validate(
                \@_,
                [
                    "Int one",
                    "Int two",
                    'named:',
                    "Int named_param",
                    "Int opt_named",
                    "..."
                ],
                {
                    param_style => mixed_style,
                    normalize_keys => sub { lc $_[0] },
                    fuzzy => hash_param_ok,
                    called => "YourModule",
                    on_fail => \&catch_validation_error,
                    callbacks => {
                        one => {
                            "equals one" => sub { $_[0] == 1 }
                            }
                        },
                }
            );

You can also validate the parameters passed to a method.

my $self = shift;
my $params = $signature->validate_method(
            \@_,
            ["Object self", "Int one", "Int two", "Int :opt_named?"]
            );

- or -

my $params = $signature->validate_method(
            \@_,
            ["Object self", "Int one", "Int two"]
            );

params: A reference to an array of parameters passed to the calling subroutine. Normally this is a reference to @_.

signature: The actual subroutine signature is an array with each element representing one parameter. Positional parameters are expected to be passed in in the same order as they appear in the signature.

Configuration Parameters: A hash containing any of the following values. Values set in this hash override default values passed in to the object constructor or pre-defined as a global default.

param_style: Explicitly set the parameter style used to validate parameters. The definition of the signature itself will (silently) overrule this value if there is a conflict. For example, setting this value to 'positional_style' while explicitly defining all fields as named values will force the parameter style to "named style".

normalize_keys: A reference to a subroutine. For named parameters, alters each key passed to the calling subroutine to match the parameter names used in the signature. The routine is passed one key at a time. The names in the signature are not passed to this subroutine.

fuzzy: Enable 'fuzzy logic' used to determine what type of parameter style was used. This overrides the default in the Params::Signature object or the global (class) value. Values are 'hash_param_ok', 'kv_param_ok' or 'strict_param' (the default).

coerce: Enable (1) or disable (0) automatic coercion of parameter values. The global (class) default is 'enabled' (value set to 1).

called: A string included at the beginning of any error messages produced. Normally, the name of the module or application that called validate; however, this can be any string.

caller: The name of the module or application that called validate. It is the namespace which will be searched for type constraint definitions. If not set, it will be set to the scalar value returned by caller.

on_fail: Override Carp::confess as the subroutine that gets called when a failure occurs.

callbacks: A hash for fine-grained testing of values which goes beyond type checking. The hash keys match the parameter names in the signature. The value of each key is a hash of test names and subroutine references. This allows per-parameter validation callback routines. The callback routine receives 3 parameters - the parameter value, the original values passed to the validate method, a hash containing a list of values that have already been validated or will be validated. Note that values in the third parameter are validated in order of appearance in the signature. The hash is ultimately returned to the caller, if validate is called in scalar context. As a result, values can be inserted into the hash for eventual use by the caller.

Return Value:

In scalar context, the method returns a hash reference with key/value pairs for each parameter that has a value. In list context, this method returns a list of parameter values in the order of appearance in the signature. If extra parameters are passed in (and allowed), they are appended to the list in the order of appearance in @_. Extra positional parameters are named 'p_#' in the returned hash. If a mixed parameter style was used, the list contains positional and named parameters in the order they appear in the signature. If you are using a mixed parameter style, it may be easier to call the validate method in scalar context and use keys to access all parameters. That said, a (somewhat) sane result is returned in list context.

mixed_foo(1, undef, {three => 3, four => 4});
sub mixed_foo
{
    # get hash in scalar context (and use a perl 6-ish signature)
    my $params = $signature->validate(
                    \@_,
                    ["Int $one", "Int $two = 2", "Int :$three", "..."]
                    );
    # $params = { one => 1, two => 2, three => 3, four => 4 }

    # get a list in list context (signature happens to use
    # the "native" signature style)
    my @params = $signature->validate(
                 \@_,
                 ["Int one", "Int two = 2", "named:", "Int three", "..."]
                 );
    # @params = [ 1, 2, 3, 4]
}

bar(1,undef,3)
sub bar
{
    # get hash in scalar context (and use a perl 6-ish signature)
    my $params = $signature->validate(
                    \@_,
                    ["Int $one", "Int $two", "Int $three"]
                    );
    # $params = { one => 1, two => undef, three => 3}

    # get a list in list context 
    my @params = $signature->validate(
                        \@_,
                        ["Int one", "Int two", "Int three"]
                        );
    # @params = [ 1, undef, 3 ]
}

has_extra(1,2,3,4);
sub has_extra
{
    # get hash in scalar context (and use a perl 6-ish signature)
    my $params = $signature->validate(
                    \@_,
                    ["Int one", "Int two", "..."]
                    );
    # $params = { one => 1, two => 2, p_2 => 3, p_3 => 4}

    # get a list in list context 
    my @params = $signature->validate(
                        \@_,
                        ["Int one", "Int two", "..."]
                        );
    # @params = [ 1, 2, 3, 4 ]
}

validate_method

Use validate_method to validate parameters passed to an object method. The first parameter (the object itself) is validated by validate_method. If object validation is successful, validate is called with the remaining parameters.

my $params = $signature->validate_method(
            \@_,
            ["Object self", "Int one", "Int two"]
            );

my ($self, $one, $two) = $signature->validate_method(
            \@_,
            ["Object self", "Int one", "Int two"]
            );

check

This method is used to confirm a value meets a type constraint. The type constraint string can be made up of multiple types.

my $is_ok = $signature->check("Int|Num|Undef", $value)

type: the type constraint, which can be multiple types separated by an or bar (pipe symbol)

value: the value to be checked

Return Value:

In scalar context, returns 1 if the value matches at least one type in the type constraint or 0 otherwise.

In list context, returns (passed, value, msg, tc) where passed is a 1 or 0, value is the tested value, msg is an error message (if there is one), and tc is a reference to the matching type constraint.

PERFORMANCE

The Params::Signature object caches the parsed form of each signature it validates. Re-using the same object to validate subroutine parameters eliminates the need to parse the signature every time. Using a singleton per module or application is recommended for reducing the amount of time it takes to validate parameters. Based on benchmarks, it runs about as fast as the XS version of Params::Validate but is not as fast as Data::Validator.

LIMITATIONS AND CAVEATS

Moo is supported indirectly because Params::Signature's methods expect a type constraint to be either a subroutine (which is what Moo uses) or a Moose::Meta::TypeConstraint-like object. As a happy medium, Type::Tiny can be used to declare type constraints that are objects (which makes Params::Signature happy) which overload &() so that the object can be treated as a subroutine (which makes Moo happy). So, Params::Signature, Type::Tiny and Moo are happy to work together. Of course, you can use subroutine references, if you prefer.

If using threads, it's recommended that your module or application define a singleton to use to validate parameters. Using a separate object per thread should be safe, though this has not been tested.

When "fuzzy" is enabled, validate attempts to automatically determine whether positional or named arguments were passed to the caller. This logic is heuristic and can be ambiguous when argument values happen to match parameter names. fuzzy = 1 is strict and only treats a single hash reference as named input, while fuzzy = 2 is more permissive and may accept raw key/value pairs. Using "fuzzy" to decipher intent is powerful but potentially problematic.

The "fuzzy" logic may need to be improved to handle corner cases I did not think of.

There is no XS version of this module at this time. It's pure perl. Perhaps that's a feature rather than a limitation?

AUTHOR

Sandor Patocs

BUGS

Please report any bugs or feature requests on GitHub at https://github.com/spatocs/params_signature/issues.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Params::Signature

You can also look for information at:

ACKNOWLEDGEMENTS

Params::Validate, MooseX::Method::Signatures and Method::Signatures all served as inspiration for this module.

SEE ALSO

Params::Signature::Manual, Params::Validate, MooseX::Method::Signatures, Method::Signatures, Perl6::Signature

LICENSE AND COPYRIGHT

Copyright 2013 Sandor Patocs.

This program is distributed under the terms of the Artistic License (2.0)