Remove whitespace at end of line
[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
a2433d98 51 my %args
954fa4ca 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
a2433d98 104 my %args
954fa4ca 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
ddc128be 157 my @args = @$args;
d9d1529d 158
159 $args[$_] = $pv_spec[$_]{constraint}->coerce( $args[$_] )
160 for grep { $pv_spec[$_] && $pv_spec[$_]{coerce} } 0 .. $#args;
161
162 @args = Params::Validate::validate_with(
df3c7e86 163 params => \@args,
164 spec => \@pv_spec,
165 allow_extra => $allow_extra,
166 called => _caller_name(),
d9d1529d 167 );
168
169 return @args;
170}
171
172sub _cache_key {
173 my $spec = shift;
174
175 if ( exists $spec->{MX_PARAMS_VALIDATE_CACHE_KEY} ) {
176 return delete $spec->{MX_PARAMS_VALIDATE_CACHE_KEY};
177 }
178 else {
179 return refaddr( caller_cv(2) );
180 }
181}
182
183sub _convert_to_param_validate_spec {
184 my ($spec) = @_;
185 my %pv_spec;
186
187 $pv_spec{optional} = $spec->{optional}
188 if exists $spec->{optional};
189
190 $pv_spec{default} = $spec->{default}
191 if exists $spec->{default};
192
193 $pv_spec{coerce} = $spec->{coerce}
194 if exists $spec->{coerce};
195
196 my $constraint;
197 if ( defined $spec->{isa} ) {
198 $constraint
199 = _is_tc( $spec->{isa} )
200 || Moose::Util::TypeConstraints::find_or_parse_type_constraint(
201 $spec->{isa} )
202 || class_type( $spec->{isa} );
203 }
204 elsif ( defined $spec->{does} ) {
205 $constraint
206 = _is_tc( $spec->{isa} )
207 || find_type_constraint( $spec->{does} )
208 || role_type( $spec->{does} );
209 }
210
211 $pv_spec{callbacks} = $spec->{callbacks}
212 if exists $spec->{callbacks};
213
214 if ($constraint) {
215 $pv_spec{constraint} = $constraint;
216
217 $pv_spec{callbacks}
218 { 'checking type constraint for ' . $constraint->name }
219 = sub { $constraint->check( $_[0] ) };
220 }
221
222 delete $pv_spec{coerce}
223 unless $pv_spec{constraint} && $pv_spec{constraint}->has_coercion;
224
225 return \%pv_spec;
226}
227
228sub _is_tc {
229 my $maybe_tc = shift;
230
231 return $maybe_tc
232 if defined $maybe_tc
233 && blessed $maybe_tc
234 && $maybe_tc->isa('Moose::Meta::TypeConstraint');
235}
236
237sub _caller_name {
238 my $depth = shift || 0;
239
240 return ( caller( 2 + $depth ) )[3];
241}
242
2431;
244
c8c6d6c9 245# ABSTRACT: an extension of Params::Validate using Moose's types
246
d9d1529d 247__END__
248
249=pod
250
d9d1529d 251=head1 SYNOPSIS
252
253 package Foo;
254 use Moose;
255 use MooseX::Params::Validate;
256
257 sub foo {
258 my ( $self, %params ) = validated_hash(
259 \@_,
260 bar => { isa => 'Str', default => 'Moose' },
261 );
f5e350c1 262 return "Hooray for $params{bar}!";
d9d1529d 263 }
264
265 sub bar {
266 my $self = shift;
267 my ( $foo, $baz, $gorch ) = validated_list(
268 \@_,
269 foo => { isa => 'Foo' },
270 baz => { isa => 'ArrayRef | HashRef', optional => 1 },
271 gorch => { isa => 'ArrayRef[Int]', optional => 1 }
272 );
273 [ $foo, $baz, $gorch ];
274 }
275
276=head1 DESCRIPTION
277
278This module fills a gap in Moose by adding method parameter validation
279to Moose. This is just one of many developing options, it should not
280be considered the "official" one by any means though.
281
282You might also want to explore C<MooseX::Method::Signatures> and
f5e350c1 283C<MooseX::Declare>.
d9d1529d 284
285=head1 CAVEATS
286
f5e350c1 287It is not possible to introspect the method parameter specs; they are
d9d1529d 288created as needed when the method is called and cached for subsequent
289calls.
290
291=head1 EXPORTS
292
293=over 4
294
295=item B<validated_hash( \@_, %parameter_spec )>
296
f5e350c1 297This behaves similarly to the standard Params::Validate C<validate>
d9d1529d 298function and returns the captured values in a HASH. The one exception
f5e350c1 299is where if it spots an instance in the C<@_>, then it will handle
d9d1529d 300it appropriately (unlike Params::Validate which forces you to shift
301you C<$self> first).
302
c8399757 303The values in C<@_> can either be a set of name-value pairs or a single hash
304reference.
305
d9d1529d 306The C<%parameter_spec> accepts the following options:
307
308=over 4
309
310=item I<isa>
311
312The C<isa> option can be either; class name, Moose type constraint
313name or an anon Moose type constraint.
314
315=item I<does>
316
317The C<does> option can be either; role name or an anon Moose type
318constraint.
319
320=item I<default>
321
322This is the default value to be used if the value is not supplied.
323
324=item I<optional>
325
326As with Params::Validate, all options are considered required unless
327otherwise specified. This option is passed directly to
328Params::Validate.
329
330=item I<coerce>
331
332If this is true and the parameter has a type constraint which has
333coercions, then the coercion will be called for this parameter. If the
334type does have coercions, then this parameter is ignored.
335
336=back
337
338This function is also available under its old name, C<validate>.
339
340=item B<validated_list( \@_, %parameter_spec )>
341
342The C<%parameter_spec> accepts the same options as above, but returns
343the parameters as positional values instead of a HASH. This is best
344explained by example:
345
346 sub foo {
347 my ( $self, $foo, $bar ) = validated_list(
348 \@_,
349 foo => { isa => 'Foo' },
350 bar => { isa => 'Bar' },
351 );
352 $foo->baz($bar);
353 }
354
355We capture the order in which you defined the parameters and then
356return them as a list in the same order. If a param is marked optional
357and not included, then it will be set to C<undef>.
358
c8399757 359The values in C<@_> can either be a set of name-value pairs or a single hash
360reference.
361
d9d1529d 362Like C<validated_hash>, if it spots an object instance as the first
363parameter of C<@_>, it will handle it appropriately, returning it as
364the first argument.
365
366This function is also available under its old name, C<validatep>.
367
368=item B<pos_validated_list( \@_, $spec, $spec, ... )>
369
370This function validates a list of positional parameters. Each C<$spec>
371should validate one of the parameters in the list:
372
373 sub foo {
374 my $self = shift;
375 my ( $foo, $bar ) = pos_validated_list(
376 \@_,
377 { isa => 'Foo' },
378 { isa => 'Bar' },
379 );
380
381 ...
382 }
383
384Unlike the other functions, this function I<cannot> find C<$self> in
385the argument list. Make sure to shift it off yourself before doing
386validation.
387
ddc128be 388The values in C<@_> must be a list of values. You cannot pass the values as an
389array reference, because this cannot be distinguished from passing one value
390which is itself an array reference.
c8399757 391
d9d1529d 392If a parameter is marked as optional and is not present, it will
393simply not be returned.
394
395If you want to pass in any of the cache control parameters described
396below, simply pass them after the list of parameter validation specs:
397
398 sub foo {
399 my $self = shift;
400 my ( $foo, $bar ) = pos_validated_list(
401 \@_,
402 { isa => 'Foo' },
403 { isa => 'Bar' },
404 MX_PARAMS_VALIDATE_NO_CACHE => 1,
405 );
406
407 ...
408 }
409
410=back
411
df3c7e86 412=head1 ALLOWING EXTRA PARAMETERS
413
414By default, any parameters not mentioned in the parameter spec cause this
415module to throw an error. However, you can have have this module simply ignore
416them by setting C<MX_PARAMS_VALIDATE_ALLOW_EXTRA> to a true value when calling
417a validation subroutine.
418
419When calling C<validated_hash> or C<pos_validated_list> the extra parameters
420are simply returned in the hash or list as appropriate. However, when you call
421C<validated_list> the extra parameters will not be returned at all. You can
422get them by looking at the original value of C<@_>.
423
d9d1529d 424=head1 EXPORTS
425
426By default, this module exports the C<validated_hash>,
427C<validated_list>, and C<pos_validated_list>.
428
429If you would prefer to import the now deprecated functions C<validate>
430and C<validatep> instead, you can use the C<:deprecated> tag to import
431them.
432
433=head1 IMPORTANT NOTE ON CACHING
434
df3c7e86 435When a validation subroutine is called the first time, the parameter spec is
436prepared and cached to avoid unnecessary regeneration. It uses the fully
437qualified name of the subroutine (package + subname) as the cache key. In
43899.999% of the use cases for this module, that will be the right thing to do.
d9d1529d 439
440However, I have (ab)used this module occasionally to handle dynamic
441sets of parameters. In this special use case you can do a couple
442things to better control the caching behavior.
443
444=over 4
445
446=item *
447
448Passing in the C<MX_PARAMS_VALIDATE_NO_CACHE> flag in the parameter
449spec this will prevent the parameter spec from being cached.
450
451 sub foo {
452 my ( $self, %params ) = validated_hash(
453 \@_,
454 foo => { isa => 'Foo' },
455 MX_PARAMS_VALIDATE_NO_CACHE => 1,
456 );
457
458 }
459
460=item *
461
462Passing in C<MX_PARAMS_VALIDATE_CACHE_KEY> with a value to be used as
463the cache key will bypass the normal cache key generation.
464
465 sub foo {
466 my ( $self, %params ) = validated_hash(
467 \@_,
468 foo => { isa => 'Foo' },
469 MX_PARAMS_VALIDATE_CACHE_KEY => 'foo-42',
470 );
471
472 }
473
474=back
475
3a905ae7 476=head1 MAINTAINER
d9d1529d 477
478Dave Rolsky E<lt>autarch@urth.orgE<gt>
479
c8c6d6c9 480=head1 BUGS
d9d1529d 481
c8c6d6c9 482Please submit bugs to the CPAN RT system at
483http://rt.cpan.org/NoAuth/ReportBug.html?Queue=moosex-params-validate or via
484email at bug-moosex-params-validate@rt.cpan.org.
d9d1529d 485
486=cut