1 package MooseX::Params::Validate;
7 use Devel::Caller 'caller_cv';
8 use Scalar::Util 'blessed', 'refaddr';
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];
53 $args{$_} = $spec{$_}{constraint}->coerce( $args{$_} )
54 for grep { $spec{$_}{coerce} && exists $args{$_} } keys %spec;
56 %args = Params::Validate::validate_with(
59 allow_extra => $allow_extra,
60 called => _caller_name(),
63 return ( ( defined $instance ? $instance : () ), %args );
66 *validate = \&validated_hash;
69 my ( $args, @spec ) = @_;
73 my $cache_key = _cache_key( \%spec );
75 my $allow_extra = delete $spec{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
78 if ( exists $CACHED_SPECS{$cache_key} ) {
79 ( ref $CACHED_SPECS{$cache_key} eq 'ARRAY' )
81 "I was expecting a ARRAY-ref in the cached $cache_key parameter"
82 . " spec, you are doing something funky, stop it!";
83 %spec = %{ $CACHED_SPECS{$cache_key}->[0] };
84 @ordered_spec = @{ $CACHED_SPECS{$cache_key}->[1] };
87 my $should_cache = delete $spec{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
89 @ordered_spec = grep { exists $spec{$_} } @spec;
91 $spec{$_} = _convert_to_param_validate_spec( $spec{$_} )
94 $CACHED_SPECS{$cache_key} = [ \%spec, \@ordered_spec ]
99 $instance = shift @$args if blessed $args->[0];
103 $args{$_} = $spec{$_}{constraint}->coerce( $args{$_} )
104 for grep { $spec{$_}{coerce} && exists $args{$_} } keys %spec;
106 %args = Params::Validate::validate_with(
109 allow_extra => $allow_extra,
110 called => _caller_name(),
114 ( defined $instance ? $instance : () ),
119 *validatep = \&validated_list;
121 sub pos_validated_list {
125 push @spec, shift while ref $_[0];
129 my $cache_key = _cache_key( \%extra );
131 my $allow_extra = delete $extra{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
134 if ( exists $CACHED_SPECS{$cache_key} ) {
135 ( ref $CACHED_SPECS{$cache_key} eq 'ARRAY' )
137 "I was expecting an ARRAY-ref in the cached $cache_key parameter"
138 . " spec, you are doing something funky, stop it!";
139 @pv_spec = @{ $CACHED_SPECS{$cache_key} };
142 my $should_cache = exists $extra{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
144 # prepare the parameters ...
145 @pv_spec = map { _convert_to_param_validate_spec($_) } @spec;
147 $CACHED_SPECS{$cache_key} = \@pv_spec
153 $args[$_] = $pv_spec[$_]{constraint}->coerce( $args[$_] )
154 for grep { $pv_spec[$_] && $pv_spec[$_]{coerce} } 0 .. $#args;
156 @args = Params::Validate::validate_with(
159 allow_extra => $allow_extra,
160 called => _caller_name(),
169 if ( exists $spec->{MX_PARAMS_VALIDATE_CACHE_KEY} ) {
170 return delete $spec->{MX_PARAMS_VALIDATE_CACHE_KEY};
173 return refaddr( caller_cv(2) );
177 sub _convert_to_param_validate_spec {
181 $pv_spec{optional} = $spec->{optional}
182 if exists $spec->{optional};
184 $pv_spec{default} = $spec->{default}
185 if exists $spec->{default};
187 $pv_spec{coerce} = $spec->{coerce}
188 if exists $spec->{coerce};
191 if ( defined $spec->{isa} ) {
193 = _is_tc( $spec->{isa} )
194 || Moose::Util::TypeConstraints::find_or_parse_type_constraint(
196 || class_type( $spec->{isa} );
198 elsif ( defined $spec->{does} ) {
200 = _is_tc( $spec->{isa} )
201 || find_type_constraint( $spec->{does} )
202 || role_type( $spec->{does} );
205 $pv_spec{callbacks} = $spec->{callbacks}
206 if exists $spec->{callbacks};
209 $pv_spec{constraint} = $constraint;
212 { 'checking type constraint for ' . $constraint->name }
213 = sub { $constraint->check( $_[0] ) };
216 delete $pv_spec{coerce}
217 unless $pv_spec{constraint} && $pv_spec{constraint}->has_coercion;
223 my $maybe_tc = shift;
228 && $maybe_tc->isa('Moose::Meta::TypeConstraint');
232 my $depth = shift || 0;
234 return ( caller( 2 + $depth ) )[3];
239 # ABSTRACT: an extension of Params::Validate using Moose's types
249 use MooseX::Params::Validate;
252 my ( $self, %params ) = validated_hash(
254 bar => { isa => 'Str', default => 'Moose' },
256 return "Hooray for $params{bar}!";
261 my ( $foo, $baz, $gorch ) = validated_list(
263 foo => { isa => 'Foo' },
264 baz => { isa => 'ArrayRef | HashRef', optional => 1 },
265 gorch => { isa => 'ArrayRef[Int]', optional => 1 }
267 [ $foo, $baz, $gorch ];
272 This module fills a gap in Moose by adding method parameter validation
273 to Moose. This is just one of many developing options, it should not
274 be considered the "official" one by any means though.
276 You might also want to explore C<MooseX::Method::Signatures> and
281 It is not possible to introspect the method parameter specs; they are
282 created as needed when the method is called and cached for subsequent
289 =item B<validated_hash( \@_, %parameter_spec )>
291 This behaves similarly to the standard Params::Validate C<validate>
292 function and returns the captured values in a HASH. The one exception
293 is where if it spots an instance in the C<@_>, then it will handle
294 it appropriately (unlike Params::Validate which forces you to shift
297 The C<%parameter_spec> accepts the following options:
303 The C<isa> option can be either; class name, Moose type constraint
304 name or an anon Moose type constraint.
308 The C<does> option can be either; role name or an anon Moose type
313 This is the default value to be used if the value is not supplied.
317 As with Params::Validate, all options are considered required unless
318 otherwise specified. This option is passed directly to
323 If this is true and the parameter has a type constraint which has
324 coercions, then the coercion will be called for this parameter. If the
325 type does have coercions, then this parameter is ignored.
329 This function is also available under its old name, C<validate>.
331 =item B<validated_list( \@_, %parameter_spec )>
333 The C<%parameter_spec> accepts the same options as above, but returns
334 the parameters as positional values instead of a HASH. This is best
335 explained by example:
338 my ( $self, $foo, $bar ) = validated_list(
340 foo => { isa => 'Foo' },
341 bar => { isa => 'Bar' },
346 We capture the order in which you defined the parameters and then
347 return them as a list in the same order. If a param is marked optional
348 and not included, then it will be set to C<undef>.
350 Like C<validated_hash>, if it spots an object instance as the first
351 parameter of C<@_>, it will handle it appropriately, returning it as
354 This function is also available under its old name, C<validatep>.
356 =item B<pos_validated_list( \@_, $spec, $spec, ... )>
358 This function validates a list of positional parameters. Each C<$spec>
359 should validate one of the parameters in the list:
363 my ( $foo, $bar ) = pos_validated_list(
372 Unlike the other functions, this function I<cannot> find C<$self> in
373 the argument list. Make sure to shift it off yourself before doing
376 If a parameter is marked as optional and is not present, it will
377 simply not be returned.
379 If you want to pass in any of the cache control parameters described
380 below, simply pass them after the list of parameter validation specs:
384 my ( $foo, $bar ) = pos_validated_list(
388 MX_PARAMS_VALIDATE_NO_CACHE => 1,
396 =head1 ALLOWING EXTRA PARAMETERS
398 By default, any parameters not mentioned in the parameter spec cause this
399 module to throw an error. However, you can have have this module simply ignore
400 them by setting C<MX_PARAMS_VALIDATE_ALLOW_EXTRA> to a true value when calling
401 a validation subroutine.
403 When calling C<validated_hash> or C<pos_validated_list> the extra parameters
404 are simply returned in the hash or list as appropriate. However, when you call
405 C<validated_list> the extra parameters will not be returned at all. You can
406 get them by looking at the original value of C<@_>.
410 By default, this module exports the C<validated_hash>,
411 C<validated_list>, and C<pos_validated_list>.
413 If you would prefer to import the now deprecated functions C<validate>
414 and C<validatep> instead, you can use the C<:deprecated> tag to import
417 =head1 IMPORTANT NOTE ON CACHING
419 When a validation subroutine is called the first time, the parameter spec is
420 prepared and cached to avoid unnecessary regeneration. It uses the fully
421 qualified name of the subroutine (package + subname) as the cache key. In
422 99.999% of the use cases for this module, that will be the right thing to do.
424 However, I have (ab)used this module occasionally to handle dynamic
425 sets of parameters. In this special use case you can do a couple
426 things to better control the caching behavior.
432 Passing in the C<MX_PARAMS_VALIDATE_NO_CACHE> flag in the parameter
433 spec this will prevent the parameter spec from being cached.
436 my ( $self, %params ) = validated_hash(
438 foo => { isa => 'Foo' },
439 MX_PARAMS_VALIDATE_NO_CACHE => 1,
446 Passing in C<MX_PARAMS_VALIDATE_CACHE_KEY> with a value to be used as
447 the cache key will bypass the normal cache key generation.
450 my ( $self, %params ) = validated_hash(
452 foo => { isa => 'Foo' },
453 MX_PARAMS_VALIDATE_CACHE_KEY => 'foo-42',
462 Dave Rolsky E<lt>autarch@urth.orgE<gt>
466 Please submit bugs to the CPAN RT system at
467 http://rt.cpan.org/NoAuth/ReportBug.html?Queue=moosex-params-validate or via
468 email at bug-moosex-params-validate@rt.cpan.org.