1 package MooseX::Params::Validate;
7 use Devel::Caller 'caller_cv';
8 use Scalar::Util 'blessed', 'refaddr', 'reftype';
10 use Moose::Util::TypeConstraints qw( find_type_constraint class_type role_type );
11 use Params::Validate ();
12 use Sub::Exporter -setup => {
14 qw( validated_hash validated_list pos_validated_list validate validatep )
17 default => [qw( validated_hash validated_list pos_validated_list )],
18 deprecated => [qw( validate validatep )],
25 my ( $args, %spec ) = @_;
27 my $cache_key = _cache_key( \%spec );
29 my $allow_extra = delete $spec{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
31 if ( exists $CACHED_SPECS{$cache_key} ) {
32 ( ref $CACHED_SPECS{$cache_key} eq 'HASH' )
34 "I was expecting a HASH-ref in the cached $cache_key parameter"
35 . " spec, you are doing something funky, stop it!";
36 %spec = %{ $CACHED_SPECS{$cache_key} };
39 my $should_cache = delete $spec{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
41 $spec{$_} = _convert_to_param_validate_spec( $spec{$_} )
44 $CACHED_SPECS{$cache_key} = \%spec
49 $instance = shift @$args if blessed $args->[0];
54 && reftype( $args->[0] ) eq 'HASH' ? %{ $args->[0] } : @$args;
56 $args{$_} = $spec{$_}{constraint}->coerce( $args{$_} )
57 for grep { $spec{$_}{coerce} && exists $args{$_} } keys %spec;
59 %args = Params::Validate::validate_with(
62 allow_extra => $allow_extra,
63 called => _caller_name(),
66 return ( ( defined $instance ? $instance : () ), %args );
69 *validate = \&validated_hash;
72 my ( $args, @spec ) = @_;
76 my $cache_key = _cache_key( \%spec );
78 my $allow_extra = delete $spec{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
81 if ( exists $CACHED_SPECS{$cache_key} ) {
82 ( ref $CACHED_SPECS{$cache_key} eq 'ARRAY' )
84 "I was expecting a ARRAY-ref in the cached $cache_key parameter"
85 . " spec, you are doing something funky, stop it!";
86 %spec = %{ $CACHED_SPECS{$cache_key}->[0] };
87 @ordered_spec = @{ $CACHED_SPECS{$cache_key}->[1] };
90 my $should_cache = delete $spec{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
92 @ordered_spec = grep { exists $spec{$_} } @spec;
94 $spec{$_} = _convert_to_param_validate_spec( $spec{$_} )
97 $CACHED_SPECS{$cache_key} = [ \%spec, \@ordered_spec ]
102 $instance = shift @$args if blessed $args->[0];
107 && reftype( $args->[0] ) eq 'HASH' ? %{ $args->[0] } : @$args;
109 $args{$_} = $spec{$_}{constraint}->coerce( $args{$_} )
110 for grep { $spec{$_}{coerce} && exists $args{$_} } keys %spec;
112 %args = Params::Validate::validate_with(
115 allow_extra => $allow_extra,
116 called => _caller_name(),
120 ( defined $instance ? $instance : () ),
125 *validatep = \&validated_list;
127 sub pos_validated_list {
131 push @spec, shift while ref $_[0];
135 my $cache_key = _cache_key( \%extra );
137 my $allow_extra = delete $extra{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
140 if ( exists $CACHED_SPECS{$cache_key} ) {
141 ( ref $CACHED_SPECS{$cache_key} eq 'ARRAY' )
143 "I was expecting an ARRAY-ref in the cached $cache_key parameter"
144 . " spec, you are doing something funky, stop it!";
145 @pv_spec = @{ $CACHED_SPECS{$cache_key} };
148 my $should_cache = exists $extra{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
150 # prepare the parameters ...
151 @pv_spec = map { _convert_to_param_validate_spec($_) } @spec;
153 $CACHED_SPECS{$cache_key} = \@pv_spec
159 $args[$_] = $pv_spec[$_]{constraint}->coerce( $args[$_] )
160 for grep { $pv_spec[$_] && $pv_spec[$_]{coerce} } 0 .. $#args;
162 @args = Params::Validate::validate_with(
165 allow_extra => $allow_extra,
166 called => _caller_name(),
175 if ( exists $spec->{MX_PARAMS_VALIDATE_CACHE_KEY} ) {
176 return delete $spec->{MX_PARAMS_VALIDATE_CACHE_KEY};
179 return refaddr( caller_cv(2) );
183 sub _convert_to_param_validate_spec {
187 $pv_spec{optional} = $spec->{optional}
188 if exists $spec->{optional};
190 $pv_spec{default} = $spec->{default}
191 if exists $spec->{default};
193 $pv_spec{coerce} = $spec->{coerce}
194 if exists $spec->{coerce};
197 if ( defined $spec->{isa} ) {
199 = _is_tc( $spec->{isa} )
200 || Moose::Util::TypeConstraints::find_or_parse_type_constraint(
202 || class_type( $spec->{isa} );
204 elsif ( defined $spec->{does} ) {
206 = _is_tc( $spec->{isa} )
207 || find_type_constraint( $spec->{does} )
208 || role_type( $spec->{does} );
211 $pv_spec{callbacks} = $spec->{callbacks}
212 if exists $spec->{callbacks};
215 $pv_spec{constraint} = $constraint;
218 { 'checking type constraint for ' . $constraint->name }
219 = sub { $constraint->check( $_[0] ) };
222 delete $pv_spec{coerce}
223 unless $pv_spec{constraint} && $pv_spec{constraint}->has_coercion;
229 my $maybe_tc = shift;
234 && $maybe_tc->isa('Moose::Meta::TypeConstraint');
238 my $depth = shift || 0;
240 return ( caller( 2 + $depth ) )[3];
245 # ABSTRACT: an extension of Params::Validate using Moose's types
255 use MooseX::Params::Validate;
258 my ( $self, %params ) = validated_hash(
260 bar => { isa => 'Str', default => 'Moose' },
262 return "Hooray for $params{bar}!";
267 my ( $foo, $baz, $gorch ) = validated_list(
269 foo => { isa => 'Foo' },
270 baz => { isa => 'ArrayRef | HashRef', optional => 1 },
271 gorch => { isa => 'ArrayRef[Int]', optional => 1 }
273 [ $foo, $baz, $gorch ];
278 This module fills a gap in Moose by adding method parameter validation
279 to Moose. This is just one of many developing options, it should not
280 be considered the "official" one by any means though.
282 You might also want to explore C<MooseX::Method::Signatures> and
287 It is not possible to introspect the method parameter specs; they are
288 created as needed when the method is called and cached for subsequent
295 =item B<validated_hash( \@_, %parameter_spec )>
297 This behaves similarly to the standard Params::Validate C<validate>
298 function and returns the captured values in a HASH. The one exception
299 is where if it spots an instance in the C<@_>, then it will handle
300 it appropriately (unlike Params::Validate which forces you to shift
303 The values in C<@_> can either be a set of name-value pairs or a single hash
306 The C<%parameter_spec> accepts the following options:
312 The C<isa> option can be either; class name, Moose type constraint
313 name or an anon Moose type constraint.
317 The C<does> option can be either; role name or an anon Moose type
322 This is the default value to be used if the value is not supplied.
326 As with Params::Validate, all options are considered required unless
327 otherwise specified. This option is passed directly to
332 If this is true and the parameter has a type constraint which has
333 coercions, then the coercion will be called for this parameter. If the
334 type does have coercions, then this parameter is ignored.
338 This function is also available under its old name, C<validate>.
340 =item B<validated_list( \@_, %parameter_spec )>
342 The C<%parameter_spec> accepts the same options as above, but returns
343 the parameters as positional values instead of a HASH. This is best
344 explained by example:
347 my ( $self, $foo, $bar ) = validated_list(
349 foo => { isa => 'Foo' },
350 bar => { isa => 'Bar' },
355 We capture the order in which you defined the parameters and then
356 return them as a list in the same order. If a param is marked optional
357 and not included, then it will be set to C<undef>.
359 The values in C<@_> can either be a set of name-value pairs or a single hash
362 Like C<validated_hash>, if it spots an object instance as the first
363 parameter of C<@_>, it will handle it appropriately, returning it as
366 This function is also available under its old name, C<validatep>.
368 =item B<pos_validated_list( \@_, $spec, $spec, ... )>
370 This function validates a list of positional parameters. Each C<$spec>
371 should validate one of the parameters in the list:
375 my ( $foo, $bar ) = pos_validated_list(
384 Unlike the other functions, this function I<cannot> find C<$self> in
385 the argument list. Make sure to shift it off yourself before doing
388 The values in C<@_> must be a list of values. You cannot pass the values as an
389 array reference, because this cannot be distinguished from passing one value
390 which is itself an array reference.
392 If a parameter is marked as optional and is not present, it will
393 simply not be returned.
395 If you want to pass in any of the cache control parameters described
396 below, simply pass them after the list of parameter validation specs:
400 my ( $foo, $bar ) = pos_validated_list(
404 MX_PARAMS_VALIDATE_NO_CACHE => 1,
412 =head1 ALLOWING EXTRA PARAMETERS
414 By default, any parameters not mentioned in the parameter spec cause this
415 module to throw an error. However, you can have have this module simply ignore
416 them by setting C<MX_PARAMS_VALIDATE_ALLOW_EXTRA> to a true value when calling
417 a validation subroutine.
419 When calling C<validated_hash> or C<pos_validated_list> the extra parameters
420 are simply returned in the hash or list as appropriate. However, when you call
421 C<validated_list> the extra parameters will not be returned at all. You can
422 get them by looking at the original value of C<@_>.
426 By default, this module exports the C<validated_hash>,
427 C<validated_list>, and C<pos_validated_list>.
429 If you would prefer to import the now deprecated functions C<validate>
430 and C<validatep> instead, you can use the C<:deprecated> tag to import
433 =head1 IMPORTANT NOTE ON CACHING
435 When a validation subroutine is called the first time, the parameter spec is
436 prepared and cached to avoid unnecessary regeneration. It uses the fully
437 qualified name of the subroutine (package + subname) as the cache key. In
438 99.999% of the use cases for this module, that will be the right thing to do.
440 However, I have (ab)used this module occasionally to handle dynamic
441 sets of parameters. In this special use case you can do a couple
442 things to better control the caching behavior.
448 Passing in the C<MX_PARAMS_VALIDATE_NO_CACHE> flag in the parameter
449 spec this will prevent the parameter spec from being cached.
452 my ( $self, %params ) = validated_hash(
454 foo => { isa => 'Foo' },
455 MX_PARAMS_VALIDATE_NO_CACHE => 1,
462 Passing in C<MX_PARAMS_VALIDATE_CACHE_KEY> with a value to be used as
463 the cache key will bypass the normal cache key generation.
466 my ( $self, %params ) = validated_hash(
468 foo => { isa => 'Foo' },
469 MX_PARAMS_VALIDATE_CACHE_KEY => 'foo-42',
478 Dave Rolsky E<lt>autarch@urth.orgE<gt>
482 Please submit bugs to the CPAN RT system at
483 http://rt.cpan.org/NoAuth/ReportBug.html?Queue=moosex-params-validate or via
484 email at bug-moosex-params-validate@rt.cpan.org.