Doc that each sub accepts references for values
[gitmo/MooseX-Params-Validate.git] / lib / MooseX / Params / Validate.pm
CommitLineData
d9d1529d 1package MooseX::Params::Validate;
2
3use strict;
4use warnings;
5
6use Carp 'confess';
7use Devel::Caller 'caller_cv';
954fa4ca 8use Scalar::Util 'blessed', 'refaddr', 'reftype';
d9d1529d 9
10use Moose::Util::TypeConstraints qw( find_type_constraint class_type role_type );
11use Params::Validate ();
12use Sub::Exporter -setup => {
13 exports => [
14 qw( validated_hash validated_list pos_validated_list validate validatep )
15 ],
16 groups => {
17 default => [qw( validated_hash validated_list pos_validated_list )],
18 deprecated => [qw( validate validatep )],
19 },
20};
21
d9d1529d 22my %CACHED_SPECS;
23
24sub validated_hash {
25 my ( $args, %spec ) = @_;
26
27 my $cache_key = _cache_key( \%spec );
28
df3c7e86 29 my $allow_extra = delete $spec{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
30
d9d1529d 31 if ( exists $CACHED_SPECS{$cache_key} ) {
32 ( ref $CACHED_SPECS{$cache_key} eq 'HASH' )
33 || confess
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} };
37 }
38 else {
39 my $should_cache = delete $spec{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
40
41 $spec{$_} = _convert_to_param_validate_spec( $spec{$_} )
42 foreach keys %spec;
43
44 $CACHED_SPECS{$cache_key} = \%spec
45 if $should_cache;
46 }
47
48 my $instance;
49 $instance = shift @$args if blessed $args->[0];
50
954fa4ca 51 my %args
52 = @$args == 1
53 && ref $args->[0]
54 && reftype( $args->[0] ) eq 'HASH' ? %{ $args->[0] } : @$args;
d9d1529d 55
56 $args{$_} = $spec{$_}{constraint}->coerce( $args{$_} )
57 for grep { $spec{$_}{coerce} && exists $args{$_} } keys %spec;
58
59 %args = Params::Validate::validate_with(
df3c7e86 60 params => \%args,
61 spec => \%spec,
62 allow_extra => $allow_extra,
63 called => _caller_name(),
d9d1529d 64 );
65
66 return ( ( defined $instance ? $instance : () ), %args );
67}
68
69*validate = \&validated_hash;
70
71sub validated_list {
72 my ( $args, @spec ) = @_;
73
74 my %spec = @spec;
75
76 my $cache_key = _cache_key( \%spec );
77
df3c7e86 78 my $allow_extra = delete $spec{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
79
d9d1529d 80 my @ordered_spec;
81 if ( exists $CACHED_SPECS{$cache_key} ) {
82 ( ref $CACHED_SPECS{$cache_key} eq 'ARRAY' )
83 || confess
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] };
88 }
89 else {
90 my $should_cache = delete $spec{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
91
92 @ordered_spec = grep { exists $spec{$_} } @spec;
93
94 $spec{$_} = _convert_to_param_validate_spec( $spec{$_} )
95 foreach keys %spec;
96
97 $CACHED_SPECS{$cache_key} = [ \%spec, \@ordered_spec ]
98 if $should_cache;
99 }
100
101 my $instance;
102 $instance = shift @$args if blessed $args->[0];
103
954fa4ca 104 my %args
105 = @$args == 1
106 && ref $args->[0]
107 && reftype( $args->[0] ) eq 'HASH' ? %{ $args->[0] } : @$args;
d9d1529d 108
109 $args{$_} = $spec{$_}{constraint}->coerce( $args{$_} )
110 for grep { $spec{$_}{coerce} && exists $args{$_} } keys %spec;
111
112 %args = Params::Validate::validate_with(
df3c7e86 113 params => \%args,
114 spec => \%spec,
115 allow_extra => $allow_extra,
116 called => _caller_name(),
d9d1529d 117 );
118
119 return (
120 ( defined $instance ? $instance : () ),
121 @args{@ordered_spec}
122 );
123}
124
125*validatep = \&validated_list;
126
127sub pos_validated_list {
128 my $args = shift;
129
130 my @spec;
131 push @spec, shift while ref $_[0];
132
133 my %extra = @_;
134
135 my $cache_key = _cache_key( \%extra );
136
df3c7e86 137 my $allow_extra = delete $extra{MX_PARAMS_VALIDATE_ALLOW_EXTRA};
138
d9d1529d 139 my @pv_spec;
140 if ( exists $CACHED_SPECS{$cache_key} ) {
141 ( ref $CACHED_SPECS{$cache_key} eq 'ARRAY' )
142 || confess
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} };
146 }
147 else {
148 my $should_cache = exists $extra{MX_PARAMS_VALIDATE_NO_CACHE} ? 0 : 1;
149
150 # prepare the parameters ...
151 @pv_spec = map { _convert_to_param_validate_spec($_) } @spec;
152
153 $CACHED_SPECS{$cache_key} = \@pv_spec
154 if $should_cache;
155 }
156
954fa4ca 157 my @args
158 = @$args == 1
159 && ref $args->[0]
160 && reftype( $args->[0] ) eq 'ARRAY' ? @{ $args->[0] } : @$args;
d9d1529d 161
162 $args[$_] = $pv_spec[$_]{constraint}->coerce( $args[$_] )
163 for grep { $pv_spec[$_] && $pv_spec[$_]{coerce} } 0 .. $#args;
164
165 @args = Params::Validate::validate_with(
df3c7e86 166 params => \@args,
167 spec => \@pv_spec,
168 allow_extra => $allow_extra,
169 called => _caller_name(),
d9d1529d 170 );
171
172 return @args;
173}
174
175sub _cache_key {
176 my $spec = shift;
177
178 if ( exists $spec->{MX_PARAMS_VALIDATE_CACHE_KEY} ) {
179 return delete $spec->{MX_PARAMS_VALIDATE_CACHE_KEY};
180 }
181 else {
182 return refaddr( caller_cv(2) );
183 }
184}
185
186sub _convert_to_param_validate_spec {
187 my ($spec) = @_;
188 my %pv_spec;
189
190 $pv_spec{optional} = $spec->{optional}
191 if exists $spec->{optional};
192
193 $pv_spec{default} = $spec->{default}
194 if exists $spec->{default};
195
196 $pv_spec{coerce} = $spec->{coerce}
197 if exists $spec->{coerce};
198
199 my $constraint;
200 if ( defined $spec->{isa} ) {
201 $constraint
202 = _is_tc( $spec->{isa} )
203 || Moose::Util::TypeConstraints::find_or_parse_type_constraint(
204 $spec->{isa} )
205 || class_type( $spec->{isa} );
206 }
207 elsif ( defined $spec->{does} ) {
208 $constraint
209 = _is_tc( $spec->{isa} )
210 || find_type_constraint( $spec->{does} )
211 || role_type( $spec->{does} );
212 }
213
214 $pv_spec{callbacks} = $spec->{callbacks}
215 if exists $spec->{callbacks};
216
217 if ($constraint) {
218 $pv_spec{constraint} = $constraint;
219
220 $pv_spec{callbacks}
221 { 'checking type constraint for ' . $constraint->name }
222 = sub { $constraint->check( $_[0] ) };
223 }
224
225 delete $pv_spec{coerce}
226 unless $pv_spec{constraint} && $pv_spec{constraint}->has_coercion;
227
228 return \%pv_spec;
229}
230
231sub _is_tc {
232 my $maybe_tc = shift;
233
234 return $maybe_tc
235 if defined $maybe_tc
236 && blessed $maybe_tc
237 && $maybe_tc->isa('Moose::Meta::TypeConstraint');
238}
239
240sub _caller_name {
241 my $depth = shift || 0;
242
243 return ( caller( 2 + $depth ) )[3];
244}
245
2461;
247
c8c6d6c9 248# ABSTRACT: an extension of Params::Validate using Moose's types
249
d9d1529d 250__END__
251
252=pod
253
d9d1529d 254=head1 SYNOPSIS
255
256 package Foo;
257 use Moose;
258 use MooseX::Params::Validate;
259
260 sub foo {
261 my ( $self, %params ) = validated_hash(
262 \@_,
263 bar => { isa => 'Str', default => 'Moose' },
264 );
f5e350c1 265 return "Hooray for $params{bar}!";
d9d1529d 266 }
267
268 sub bar {
269 my $self = shift;
270 my ( $foo, $baz, $gorch ) = validated_list(
271 \@_,
272 foo => { isa => 'Foo' },
273 baz => { isa => 'ArrayRef | HashRef', optional => 1 },
274 gorch => { isa => 'ArrayRef[Int]', optional => 1 }
275 );
276 [ $foo, $baz, $gorch ];
277 }
278
279=head1 DESCRIPTION
280
281This module fills a gap in Moose by adding method parameter validation
282to Moose. This is just one of many developing options, it should not
283be considered the "official" one by any means though.
284
285You might also want to explore C<MooseX::Method::Signatures> and
f5e350c1 286C<MooseX::Declare>.
d9d1529d 287
288=head1 CAVEATS
289
f5e350c1 290It is not possible to introspect the method parameter specs; they are
d9d1529d 291created as needed when the method is called and cached for subsequent
292calls.
293
294=head1 EXPORTS
295
296=over 4
297
298=item B<validated_hash( \@_, %parameter_spec )>
299
f5e350c1 300This behaves similarly to the standard Params::Validate C<validate>
d9d1529d 301function and returns the captured values in a HASH. The one exception
f5e350c1 302is where if it spots an instance in the C<@_>, then it will handle
d9d1529d 303it appropriately (unlike Params::Validate which forces you to shift
304you C<$self> first).
305
c8399757 306The values in C<@_> can either be a set of name-value pairs or a single hash
307reference.
308
d9d1529d 309The C<%parameter_spec> accepts the following options:
310
311=over 4
312
313=item I<isa>
314
315The C<isa> option can be either; class name, Moose type constraint
316name or an anon Moose type constraint.
317
318=item I<does>
319
320The C<does> option can be either; role name or an anon Moose type
321constraint.
322
323=item I<default>
324
325This is the default value to be used if the value is not supplied.
326
327=item I<optional>
328
329As with Params::Validate, all options are considered required unless
330otherwise specified. This option is passed directly to
331Params::Validate.
332
333=item I<coerce>
334
335If this is true and the parameter has a type constraint which has
336coercions, then the coercion will be called for this parameter. If the
337type does have coercions, then this parameter is ignored.
338
339=back
340
341This function is also available under its old name, C<validate>.
342
343=item B<validated_list( \@_, %parameter_spec )>
344
345The C<%parameter_spec> accepts the same options as above, but returns
346the parameters as positional values instead of a HASH. This is best
347explained by example:
348
349 sub foo {
350 my ( $self, $foo, $bar ) = validated_list(
351 \@_,
352 foo => { isa => 'Foo' },
353 bar => { isa => 'Bar' },
354 );
355 $foo->baz($bar);
356 }
357
358We capture the order in which you defined the parameters and then
359return them as a list in the same order. If a param is marked optional
360and not included, then it will be set to C<undef>.
361
c8399757 362The values in C<@_> can either be a set of name-value pairs or a single hash
363reference.
364
d9d1529d 365Like C<validated_hash>, if it spots an object instance as the first
366parameter of C<@_>, it will handle it appropriately, returning it as
367the first argument.
368
369This function is also available under its old name, C<validatep>.
370
371=item B<pos_validated_list( \@_, $spec, $spec, ... )>
372
373This function validates a list of positional parameters. Each C<$spec>
374should validate one of the parameters in the list:
375
376 sub foo {
377 my $self = shift;
378 my ( $foo, $bar ) = pos_validated_list(
379 \@_,
380 { isa => 'Foo' },
381 { isa => 'Bar' },
382 );
383
384 ...
385 }
386
387Unlike the other functions, this function I<cannot> find C<$self> in
388the argument list. Make sure to shift it off yourself before doing
389validation.
390
c8399757 391The values in C<@_> can either be a list of values or a single array
392reference.
393
d9d1529d 394If a parameter is marked as optional and is not present, it will
395simply not be returned.
396
397If you want to pass in any of the cache control parameters described
398below, simply pass them after the list of parameter validation specs:
399
400 sub foo {
401 my $self = shift;
402 my ( $foo, $bar ) = pos_validated_list(
403 \@_,
404 { isa => 'Foo' },
405 { isa => 'Bar' },
406 MX_PARAMS_VALIDATE_NO_CACHE => 1,
407 );
408
409 ...
410 }
411
412=back
413
df3c7e86 414=head1 ALLOWING EXTRA PARAMETERS
415
416By default, any parameters not mentioned in the parameter spec cause this
417module to throw an error. However, you can have have this module simply ignore
418them by setting C<MX_PARAMS_VALIDATE_ALLOW_EXTRA> to a true value when calling
419a validation subroutine.
420
421When calling C<validated_hash> or C<pos_validated_list> the extra parameters
422are simply returned in the hash or list as appropriate. However, when you call
423C<validated_list> the extra parameters will not be returned at all. You can
424get them by looking at the original value of C<@_>.
425
d9d1529d 426=head1 EXPORTS
427
428By default, this module exports the C<validated_hash>,
429C<validated_list>, and C<pos_validated_list>.
430
431If you would prefer to import the now deprecated functions C<validate>
432and C<validatep> instead, you can use the C<:deprecated> tag to import
433them.
434
435=head1 IMPORTANT NOTE ON CACHING
436
df3c7e86 437When a validation subroutine is called the first time, the parameter spec is
438prepared and cached to avoid unnecessary regeneration. It uses the fully
439qualified name of the subroutine (package + subname) as the cache key. In
44099.999% of the use cases for this module, that will be the right thing to do.
d9d1529d 441
442However, I have (ab)used this module occasionally to handle dynamic
443sets of parameters. In this special use case you can do a couple
444things to better control the caching behavior.
445
446=over 4
447
448=item *
449
450Passing in the C<MX_PARAMS_VALIDATE_NO_CACHE> flag in the parameter
451spec this will prevent the parameter spec from being cached.
452
453 sub foo {
454 my ( $self, %params ) = validated_hash(
455 \@_,
456 foo => { isa => 'Foo' },
457 MX_PARAMS_VALIDATE_NO_CACHE => 1,
458 );
459
460 }
461
462=item *
463
464Passing in C<MX_PARAMS_VALIDATE_CACHE_KEY> with a value to be used as
465the cache key will bypass the normal cache key generation.
466
467 sub foo {
468 my ( $self, %params ) = validated_hash(
469 \@_,
470 foo => { isa => 'Foo' },
471 MX_PARAMS_VALIDATE_CACHE_KEY => 'foo-42',
472 );
473
474 }
475
476=back
477
3a905ae7 478=head1 MAINTAINER
d9d1529d 479
480Dave Rolsky E<lt>autarch@urth.orgE<gt>
481
c8c6d6c9 482=head1 BUGS
d9d1529d 483
c8c6d6c9 484Please submit bugs to the CPAN RT system at
485http://rt.cpan.org/NoAuth/ReportBug.html?Queue=moosex-params-validate or via
486email at bug-moosex-params-validate@rt.cpan.org.
d9d1529d 487
488=cut