File Coverage

blib/lib/Params/ValidationCompiler/Compiler.pm
Criterion Covered Total %
statement 412 442 93.2
branch 135 166 81.3
condition 45 51 88.2
subroutine 59 61 96.7
pod 0 10 0.0
total 651 730 89.1


line stmt bran cond sub pod time code
1             package Params::ValidationCompiler::Compiler;
2              
3 17     17   660 use strict;
  17         41  
  17         482  
4 17     17   95 use warnings;
  17         39  
  17         765  
5              
6             our $VERSION = '0.31';
7              
8 17     17   99 use Carp qw( croak );
  17         36  
  17         1090  
9 17     17   7029 use Eval::Closure qw( eval_closure );
  17         26087  
  17         947  
10 17     17   123 use List::Util 1.29 qw( pairkeys pairvalues );
  17         403  
  17         1135  
11 17     17   6703 use Params::ValidationCompiler::Exceptions;
  17         60  
  17         619  
12 17     17   124 use Scalar::Util qw( blessed looks_like_number reftype );
  17         42  
  17         843  
13 17     17   100 use overload ();
  17         48  
  17         310  
14 17     17   83 use B qw( perlstring );
  17         36  
  17         3307  
15              
16             our @CARP_NOT = ( 'Params::ValidationCompiler', __PACKAGE__ );
17              
18 0         0 BEGIN {
19             ## no critic (Variables::RequireInitializationForLocalVars)
20 17     17   74 local $@;
21 17         36 my $has_sub_util = eval {
22 17         7629 require Sub::Util;
23 16         4775 Sub::Util->VERSION(1.40);
24 16         467 Sub::Util->import('set_subname');
25 16         46 1;
26             };
27              
28 2     2 0 62 sub HAS_SUB_UTIL () {$has_sub_util}
29              
30 17 100       376 unless ($has_sub_util) {
31             *set_subname = sub {
32 1     1   233 croak
33             'Cannot name a generated validation subroutine. Please install Sub::Util.';
34 1         7 };
35             }
36              
37 17         39 my $has_cxsa = eval {
38 17         7270 require Class::XSAccessor;
39 17         42714 Class::XSAccessor->VERSION(1.17);
40 17         85829 1;
41             };
42              
43 5     5 0 2131 sub HAS_CXSA {$has_cxsa}
44             }
45              
46             my %known
47             = map { $_ => 1 }
48             qw( debug name name_is_optional named_to_list params return_object slurpy );
49              
50             # I'd rather use Moo here but I want to make things relatively high on the
51             # CPAN river like DateTime use this distro, so reducing deps is important.
52             sub new {
53 58     58 0 147 my $class = shift;
54 58         209 my %p = @_;
55              
56 58 100       268 unless ( exists $p{params} ) {
57 1         177 croak
58             q{You must provide a "params" parameter when creating a parameter validator};
59             }
60              
61 57 100       257 if ( ref $p{params} eq 'HASH' ) {
    100          
62             croak q{The "params" hashref must contain at least one key-value pair}
63 36 50       69 unless %{ $p{params} };
  36         112  
64              
65             croak
66             q{"named_to_list" must be used with arrayref params containing key-value pairs}
67 36 50       107 if $p{named_to_list};
68              
69 36         68 $class->_validate_param_spec($_) for values %{ $p{params} };
  36         204  
70             }
71             elsif ( ref $p{params} eq 'ARRAY' ) {
72             croak q{The "params" arrayref must contain at least one element}
73 17 50       31 unless @{ $p{params} };
  17         58  
74              
75             croak q{You can only use "return_object" with named params}
76 17 100       149 if $p{return_object};
77              
78             my @specs
79             = $p{named_to_list}
80 4         23 ? pairvalues @{ $p{params} }
81 16 100       51 : @{ $p{params} };
  12         35  
82              
83 16         73 $class->_validate_param_spec($_) for @specs;
84             }
85             else {
86 4         10 my $type = _describe( $p{params} );
87 4         476 croak
88             qq{The "params" parameter when creating a parameter validator must be a hashref or arrayref, you passed $type};
89             }
90              
91 48 100 100     164 if ( $p{named_to_list} && $p{slurpy} ) {
92 1         130 croak q{You cannot use "named_to_list" and "slurpy" together};
93             }
94              
95 47 100 100     190 if ( exists $p{name} && ( !defined $p{name} || ref $p{name} ) ) {
      100        
96 3         8 my $type = _describe( $p{name} );
97 3         314 croak
98             qq{The "name" parameter when creating a parameter validator must be a scalar, you passed $type};
99             }
100              
101 44 100 100     137 if ( $p{return_object} && $p{slurpy} ) {
102 1         102 croak q{You cannot use "return_object" and "slurpy" together};
103             }
104              
105 43         129 my @unknown = sort grep { !$known{$_} } keys %p;
  61         208  
106 43 100       134 if (@unknown) {
107 1         127 croak
108             "You passed unknown parameters when creating a parameter validator: [@unknown]";
109             }
110              
111 42         120 my $self = bless \%p, $class;
112              
113 42         153 $self->{_source} = [];
114 42         103 $self->{_env} = {};
115              
116 42         165 return $self;
117             }
118              
119             sub _describe {
120 7     7   12 my $thing = shift;
121              
122 7 100       39 if ( !defined $thing ) {
    100          
    100          
123 2         5 return 'an undef';
124             }
125             elsif ( my $class = blessed $thing ) {
126 2 50       10 my $article = $class =~ /^[aeiou]/i ? 'an' : 'a';
127 2         9 return "$article $class object";
128             }
129             elsif ( ref $thing ) {
130 2         8 my $ref = lc ref $thing;
131 2 100       10 my $article = $ref =~ /^[aeiou]/i ? 'an' : 'a';
132 2         8 return "$article $ref" . 'ref';
133             }
134              
135 1         3 return 'a scalar';
136             }
137              
138             {
139             my %known_keys = (
140             default => 1,
141             getter => 1,
142             optional => 1,
143             predicate => 1,
144             type => 1,
145             );
146              
147             sub _validate_param_spec {
148 73     73   142 shift;
149 73         132 my $spec = shift;
150              
151 73         158 my $ref = ref $spec;
152 73 100       197 return unless $ref;
153              
154 49 100       237 croak
155             "Specifications must be a scalar or hashref, but received a $ref"
156             unless $ref eq 'HASH';
157              
158 48         89 my @unknown = sort grep { !$known_keys{$_} } keys %{$spec};
  55         213  
  48         153  
159 48 100       240 if (@unknown) {
160 3         328 croak "Specification contains unknown keys: [@unknown]";
161             }
162             }
163             }
164              
165 4     4 0 17 sub name { $_[0]->{name} }
166 39     39   168 sub _has_name { exists $_[0]->{name} }
167              
168 4     4   37 sub _name_is_optional { $_[0]->{name_is_optional} }
169              
170             # I have no idea why critic thinks _caller isn't used.
171              
172             ## no critic (Subroutines::ProhibitUnusedPrivateSubroutines)
173 0     0   0 sub _caller { $_[0]->{caller} }
174             ## use critic
175 4     4   21 sub _has_caller { exists $_[0]->{caller} }
176              
177 130     130 0 429 sub params { $_[0]->{params} }
178              
179 65     65 0 310 sub slurpy { $_[0]->{slurpy} }
180              
181 279     279   1075 sub _source { $_[0]->{_source} }
182              
183 129     129   2428 sub _env { $_[0]->{_env} }
184              
185 22     22 0 52 sub named_to_list { $_[0]->{named_to_list} }
186              
187 29     29 0 85 sub return_object { $_[0]->{return_object} }
188              
189             sub _inlineable_name {
190             return defined $_[0]->{name}
191             ? $_[0]->{name}
192 105 100   105   871 : 'an un-named validation subroutine';
193             }
194              
195             sub _any_type_has_coercion {
196 9     9   18 my $self = shift;
197              
198 9 50       26 return $self->{_has_coercion} if exists $self->{_has_coercion};
199              
200 9         26 for my $type ( $self->_types ) {
201              
202             # Specio
203 5 100 66     64 if ( $type->can('has_coercions') && $type->has_coercions ) {
    50 33        
204 1         12 return $self->{_has_coercion} = 1;
205             }
206              
207             # Moose and Type::Tiny
208             elsif ( $type->can('has_coercion') && $type->has_coercion ) {
209 0         0 return $self->{_has_coercion} = 1;
210             }
211             }
212              
213 8         143 return $self->{_has_coercion} = 0;
214             }
215              
216             sub _types {
217 9     9   17 my $self = shift;
218              
219 9         15 my @types;
220 9 50       36 if ( ref $self->params eq 'HASH' ) {
    50          
221 0 0       0 @types = map { $_->{type} || () }
222 0         0 grep { ref $_ } values %{ $self->params };
  0         0  
  0         0  
223             }
224             elsif ( ref $self->params eq 'ARRAY' ) {
225 9 50       24 if ( $self->named_to_list ) {
226 0         0 my %p = @{ $self->params };
  0         0  
227 0 0       0 @types = map { $_->{type} || () } grep { ref $_ } values %p;
  0         0  
  0         0  
228             }
229             else {
230             @types
231 9 100       17 = map { $_->{type} || () } grep { ref $_ } @{ $self->params };
  15         184  
  15         35  
  9         19  
232             }
233             }
234              
235 9 100 100     52 push @types, $self->slurpy if $self->slurpy && ref $self->slurpy;
236              
237 9         27 return @types;
238             }
239              
240             sub subref {
241 41     41 0 78 my $self = shift;
242              
243 41         133 $self->_compile;
244              
245 39 50       132 local $ENV{EVAL_CLOSURE_PRINT_SOURCE} = 1 if $self->{debug};
246             my $sub = eval_closure(
247 39         86 source => 'sub { ' . ( join "\n", @{ $self->_source } ) . ' };',
  39         119  
248             environment => $self->_env,
249             );
250              
251 39 100       23308 if ( $self->_has_name ) {
252 4 50       15 my $caller = $self->_has_caller ? $self->_caller : caller(1);
253 4         14 my $name = join '::', $caller, $self->name;
254              
255 4 100 100     15 return $sub if $self->_name_is_optional && !HAS_SUB_UTIL;
256 3         25 set_subname( $name, $sub );
257             }
258              
259 37         253 return $sub;
260             }
261              
262             sub source {
263 1     1 0 2 my $self = shift;
264              
265 1         4 $self->_compile;
266             return (
267 1         2 ( join "\n", @{ $self->_source } ),
  1         4  
268             $self->_env,
269             );
270             }
271              
272             sub _compile {
273 42     42   76 my $self = shift;
274              
275 42 100       118 if ( ref $self->params eq 'HASH' ) {
    50          
276 29         91 $self->_compile_named_args_check;
277             }
278             elsif ( ref $self->params eq 'ARRAY' ) {
279 13 100       35 if ( $self->named_to_list ) {
280 2         7 $self->_compile_named_args_list_check;
281             }
282             else {
283 11         34 $self->_compile_positional_args_check;
284             }
285             }
286             }
287              
288             sub _compile_named_args_check {
289 29     29   57 my $self = shift;
290              
291 29         70 $self->_compile_named_args_check_body( $self->params );
292              
293 29 100       82 if ( $self->return_object ) {
294 4         7 push @{ $self->_source }, $self->_add_return_named_args_object;
  4         19  
295             }
296             else {
297 25         42 push @{ $self->_source }, 'return %args;';
  25         61  
298             }
299              
300 29         66 return;
301             }
302              
303             {
304             my $class_id = 0;
305              
306             sub _add_return_named_args_object {
307 4     4   5 my $self = shift;
308              
309 4         11 my $params = $self->params;
310 4         8 my %getters;
311             my %predicates;
312 4         6 for my $p ( keys %{$params} ) {
  4         10  
313             $getters{
314             ref $params->{$p} && exists $params->{$p}{getter}
315             ? $params->{$p}{getter}
316 8 100 100     37 : $p
317             } = $p;
318             $predicates{ $params->{$p}{predicate} } = $p
319 8 100 100     43 if ref $params->{$p} && exists $params->{$p}{predicate};
320             }
321              
322 4   66     11 my $use_cxsa = HAS_CXSA && !$ENV{TEST_NAMED_ARGS_OBJECT_WITHOUT_CXSA};
323 4 100       25 my $class = sprintf(
324             '%s::OO::Args%d::%s',
325             __PACKAGE__,
326             $class_id++,
327             $use_cxsa ? 'XS' : 'PP',
328             );
329              
330 4 100       8 if ($use_cxsa) {
331 2         8 $self->_create_cxsa_return_class(
332             $class,
333             \%getters,
334             \%predicates,
335             );
336             }
337             else {
338 2         7 $self->_create_pp_return_class( $class, \%getters, \%predicates );
339             }
340              
341 4         37 return sprintf( 'bless \%%args, %s', perlstring($class) );
342             }
343             }
344              
345             sub _create_cxsa_return_class {
346 2     2   4 my $self = shift;
347 2         3 my $class = shift;
348 2         4 my $getters = shift;
349 2         3 my $predicates = shift;
350              
351 2         13 Class::XSAccessor->import(
352             redefine => 1,
353             class => $class,
354             getters => $getters,
355             exists_predicates => $predicates,
356             );
357              
358 2         562 return;
359             }
360              
361             sub _create_pp_return_class {
362 2     2   3 my $self = shift;
363 2         5 my $class = shift;
364 2         2 my $getters = shift;
365 2         5 my $predicates = shift;
366              
367 2         8 my @source = sprintf( 'package %s;', $class );
368 2         5 for my $sub ( keys %{$getters} ) {
  2         6  
369             push @source,
370             sprintf(
371             'sub %s { return $_[0]->{%s} }', $sub,
372 4         21 perlstring( $getters->{$sub} )
373             );
374             }
375 2         4 for my $sub ( keys %{$predicates} ) {
  2         6  
376             push @source,
377             sprintf(
378             'sub %s { return exists $_[0]->{%s} }', $sub,
379 1         5 perlstring( $predicates->{$sub} )
380             );
381             }
382 2         6 push @source, q{1;};
383             ## no critic (BuiltinFunctions::ProhibitStringyEval, ErrorHandling::RequireCheckingReturnValueOfEval)
384 2 50   1   272 eval join q{}, @source
  0     1   0  
  1     1   5  
  1     1   1044  
  1         78  
  1         6  
385             or die $@;
386              
387 2         15 return;
388             }
389              
390             sub _compile_named_args_list_check {
391 2     2   3 my $self = shift;
392              
393 2         3 $self->_compile_named_args_check_body( { @{ $self->params } } );
  2         5  
394              
395 2         6 my @keys = map { perlstring($_) } pairkeys @{ $self->params };
  3         9  
  2         5  
396              
397             # If we don't handle the one-key case specially we end up getting a
398             # warning like "Scalar value @args{"bar"} better written as $args{"bar"}
399             # at ..."
400 2 100       8 if ( @keys == 1 ) {
401 1         2 push @{ $self->_source }, "return \$args{$keys[0]};";
  1         3  
402             }
403             else {
404 1         4 my $keys_str = join q{, }, @keys;
405 1         3 push @{ $self->_source }, "return \@args{$keys_str};";
  1         2  
406             }
407              
408 2         6 return;
409             }
410              
411             sub _compile_named_args_check_body {
412 31     31   57 my $self = shift;
413 31         53 my $params = shift;
414              
415 31         57 push @{ $self->_source }, $self->_set_named_args_hash;
  31         92  
416              
417 31         65 for my $name ( sort keys %{$params} ) {
  31         123  
418 44         96 my $spec = $params->{$name};
419 44 100       136 $spec = { optional => !$spec } unless ref $spec;
420              
421 44         181 my $qname = perlstring($name);
422 44         164 my $access = "\$args{$qname}";
423              
424             # We check exists $spec->{optional} so as not to blow up on a
425             # restricted hash.
426             $self->_add_check_for_required_named_param( $access, $name )
427             unless ( exists $spec->{optional} && $spec->{optional} )
428 44 100 100     320 || exists $spec->{default};
      100        
429              
430             $self->_add_named_default_assignment(
431             $access,
432             $name,
433             $spec->{default}
434 44 100       121 ) if exists $spec->{default};
435              
436             # Same issue with restricted hashes here.
437             $self->_add_type_check( $access, $name, $spec )
438 44 50 66     388 if exists $spec->{type} && $spec->{type};
439             }
440              
441 31 100       105 if ( $self->slurpy ) {
442 2 100       10 $self->_add_check_for_extra_hash_param_types( $self->slurpy, $params )
443             if ref $self->slurpy;
444             }
445             else {
446 29         87 $self->_add_check_for_extra_hash_params($params);
447             }
448              
449 31         54 return;
450             }
451              
452             sub _set_named_args_hash {
453 31     31   58 my $self = shift;
454              
455 31         53 push @{ $self->_source },
  31         63  
456             sprintf( <<'EOF', ( $self->_inlineable_name ) x 4 );
457             my %%args;
458             if ( @_ %% 2 == 0 ) {
459             %%args = @_;
460             }
461             elsif ( @_ == 1 ) {
462             if ( ref $_[0] ) {
463             if ( Scalar::Util::blessed( $_[0] ) ) {
464             if ( overload::Overloaded( $_[0] )
465             && defined overload::Method( $_[0], '%%{}' ) ) {
466              
467             %%args = %%{ $_[0] };
468             }
469             else {
470             Params::ValidationCompiler::Exception::BadArguments->throw(
471             message =>
472             'Expected a hash or hash reference but a single object argument was passed to %s',
473             show_trace => 1,
474             );
475             }
476             }
477             elsif ( ref $_[0] eq 'HASH' ) {
478             %%args = %%{ $_[0] };
479             }
480             else {
481             Params::ValidationCompiler::Exception::BadArguments->throw(
482             message =>
483             'Expected a hash or hash reference but a single '
484             . ( ref $_[0] )
485             . ' reference argument was passed to %s',
486             show_trace => 1,
487             );
488             }
489             }
490             else {
491             Params::ValidationCompiler::Exception::BadArguments->throw(
492             message =>
493             'Expected a hash or hash reference but a single non-reference argument was passed to %s',
494             show_trace => 1,
495             );
496             }
497             }
498             else {
499             Params::ValidationCompiler::Exception::BadArguments->throw(
500             message =>
501             'Expected a hash or hash reference but an odd number of arguments was passed to %s',
502             show_trace => 1,
503             );
504             }
505             EOF
506              
507 31         93 return;
508             }
509              
510             sub _add_check_for_required_named_param {
511 29     29   53 my $self = shift;
512 29         66 my $access = shift;
513 29         57 my $name = shift;
514              
515 29         79 my $qname = perlstring($name);
516 29         56 push @{ $self->_source },
  29         63  
517             sprintf( <<'EOF', $access, $qname, $self->_inlineable_name, $qname );
518             exists %s
519             or Params::ValidationCompiler::Exception::Named::Required->throw(
520             message => %s . ' is a required parameter for %s',
521             parameter => %s,
522             show_trace => 1,
523             );
524             EOF
525              
526 29         72 return;
527             }
528              
529             sub _add_check_for_extra_hash_param_types {
530 1     1   15 my $self = shift;
531 1         3 my $type = shift;
532 1         2 my $params = shift;
533              
534             $self->_env->{'%known'}
535 1         3 = { map { $_ => 1 } keys %{$params} };
  1         6  
  1         4  
536              
537             # We need to set the name argument to something that won't conflict with
538             # names someone would actually use for a parameter.
539 1         4 my $check = join q{}, $self->_type_check(
540             '$args{$key}',
541             '__PCC extra parameters__',
542             $type,
543             );
544 1         3 push @{ $self->_source }, sprintf( <<'EOF', $check );
  1         3  
545             for my $key ( grep { !$known{$_} } keys %%args ) {
546             %s;
547             }
548             EOF
549              
550 1         3 return;
551             }
552              
553             sub _add_check_for_extra_hash_params {
554 29     29   63 my $self = shift;
555 29         53 my $params = shift;
556              
557             $self->_env->{'%known'}
558 29         53 = { map { $_ => 1 } keys %{$params} };
  42         188  
  29         77  
559 29         64 push @{ $self->_source }, sprintf( <<'EOF', $self->_inlineable_name );
  29         67  
560             my @extra = grep { !$known{$_} } keys %%args;
561             if (@extra) {
562             my $u = join ', ', sort @extra;
563             Params::ValidationCompiler::Exception::Named::Extra->throw(
564             message => "Found extra parameters passed to %s: [$u]",
565             parameters => \@extra,
566             show_trace => 1,
567             );
568             }
569             EOF
570              
571 29         73 return;
572             }
573              
574             sub _compile_positional_args_check {
575 11     11   18 my $self = shift;
576              
577 11         31 my @specs = $self->_munge_and_check_positional_params;
578              
579 9         20 my $first_optional_idx = -1;
580 9         32 for my $i ( 0 .. $#specs ) {
581 15 100 100     66 next unless $specs[$i]{optional} || exists $specs[$i]{default};
582 6         12 $first_optional_idx = $i;
583 6         13 last;
584             }
585              
586             # If optional params start anywhere after the first parameter spec then we
587             # must require at least one param. If there are no optional params then
588             # they're all required.
589             $self->_add_check_for_required_positional_params(
590 9 100       49 $first_optional_idx == -1
    50          
591             ? ( scalar @specs )
592             : $first_optional_idx
593             ) if $first_optional_idx != 0;
594              
595 9 100       24 $self->_add_check_for_extra_positional_params( scalar @specs )
596             unless $self->slurpy;
597              
598 9         24 my $access_var = '$_';
599 9         19 my $return_var = '@_';
600 9 100       22 if ( $self->_any_type_has_coercion ) {
601 1         2 push @{ $self->_source }, 'my @copy = @_;';
  1         4  
602 1         2 $access_var = '$copy';
603 1         3 $return_var = '@copy';
604             }
605              
606 9         48 for my $i ( 0 .. $#specs ) {
607 15         48 my $spec = $specs[$i];
608              
609 15         42 my $name = "Parameter $i";
610 15         54 my $access = sprintf( '%s[%i]', $access_var, $i );
611              
612             $self->_add_positional_default_assignment(
613             $i,
614             $access,
615             $name,
616             $spec->{default}
617 15 100       47 ) if exists $spec->{default};
618              
619             $self->_add_type_check( $access, $name, $spec )
620 15 100       46 if $spec->{type};
621             }
622              
623 9 100       25 if ( ref $self->slurpy ) {
624 1         4 $self->_add_check_for_extra_positional_param_types(
625             scalar @specs,
626             $self->slurpy,
627             $access_var,
628             );
629             }
630              
631 9         17 push @{ $self->_source }, sprintf( 'return %s;', $return_var );
  9         22  
632              
633 9         23 return;
634             }
635              
636             sub _munge_and_check_positional_params {
637 11     11   20 my $self = shift;
638              
639 11         19 my @specs;
640 11         24 my $in_optional = 0;
641              
642 11         20 for my $spec ( @{ $self->params } ) {
  11         22  
643 19 100       48 $spec = ref $spec ? $spec : { optional => !$spec };
644 19 100 100     92 if ( $spec->{optional} || exists $spec->{default} ) {
    100          
645 8         16 $in_optional = 1;
646             }
647             elsif ($in_optional) {
648 2         410 croak
649             'Parameter list contains an optional parameter followed by a required parameter.';
650             }
651              
652 17         37 push @specs, $spec;
653             }
654              
655 9         26 return @specs;
656             }
657              
658             sub _add_check_for_required_positional_params {
659 9     9   19 my $self = shift;
660 9         17 my $min = shift;
661              
662 9         16 push @{ $self->_source },
  9         23  
663             sprintf( <<'EOF', ($min) x 2, $self->_inlineable_name, $min );
664             if ( @_ < %d ) {
665             my $got = scalar @_;
666             my $got_n = @_ == 1 ? 'parameter' : 'parameters';
667             Params::ValidationCompiler::Exception::Positional::Required->throw(
668             message => "Got $got $got_n but expected at least %d for %s",
669             minimum => %d,
670             got => scalar @_,
671             show_trace => 1,
672             );
673             }
674             EOF
675              
676 9         22 return;
677             }
678              
679             sub _add_check_for_extra_positional_param_types {
680 1     1   5 my $self = shift;
681 1         2 my $max = shift;
682 1         3 my $type = shift;
683 1         2 my $access_var = shift;
684              
685             # We need to set the name argument to something that won't conflict with
686             # names someone would actually use for a parameter.
687 1         7 my $check = join q{}, $self->_type_check(
688             sprintf( '%s[$i]', $access_var ),
689             '__PCC extra parameters__',
690             $type,
691             );
692 1         3 push @{ $self->_source }, sprintf( <<'EOF', $max, $max, $check );
  1         3  
693             if ( @_ > %d ) {
694             for my $i ( %d .. $#_ ) {
695             %s;
696             }
697             }
698             EOF
699              
700 1         3 return;
701             }
702              
703             sub _add_check_for_extra_positional_params {
704 7     7   14 my $self = shift;
705 7         13 my $max = shift;
706              
707 7         13 push @{ $self->_source },
  7         18  
708             sprintf( <<'EOF', ($max) x 2, $self->_inlineable_name, $max );
709             if ( @_ > %d ) {
710             my $extra = @_ - %d;
711             my $extra_n = $extra == 1 ? 'parameter' : 'parameters';
712             Params::ValidationCompiler::Exception::Positional::Extra->throw(
713             message => "Got $extra extra $extra_n for %s",
714             maximum => %d,
715             got => scalar @_,
716             show_trace => 1,
717             );
718             }
719             EOF
720              
721 7         15 return;
722             }
723              
724             sub _add_positional_default_assignment {
725 4     4   6 my $self = shift;
726 4         9 my $position = shift;
727 4         5 my $access = shift;
728 4         8 my $name = shift;
729 4         5 my $default = shift;
730              
731 4         6 push @{ $self->_source }, "if ( \$#_ < $position ) {";
  4         9  
732 4         12 $self->_add_shared_default_assignment( $access, $name, $default );
733 4         7 push @{ $self->_source }, '}';
  4         7  
734              
735 4         7 return;
736             }
737              
738             sub _add_named_default_assignment {
739 4     4   8 my $self = shift;
740 4         7 my $access = shift;
741 4         6 my $name = shift;
742 4         7 my $default = shift;
743              
744 4         10 my $qname = perlstring($name);
745 4         7 push @{ $self->_source }, "unless ( exists \$args{$qname} ) {";
  4         8  
746 4         14 $self->_add_shared_default_assignment( $access, $name, $default );
747 4         7 push @{ $self->_source }, '}';
  4         11  
748              
749 4         8 return;
750             }
751              
752             sub _add_shared_default_assignment {
753 8     8   14 my $self = shift;
754 8         16 my $access = shift;
755 8         11 my $name = shift;
756 8         16 my $default = shift;
757              
758 8         22 my $qname = perlstring($name);
759              
760 8 50 66     43 croak 'Default must be either a plain scalar or a subroutine reference'
761             if ref $default && reftype($default) ne 'CODE';
762              
763 8 100       22 if ( ref $default ) {
764 3         8 push @{ $self->_source }, "$access = \$defaults{$qname}->();";
  3         8  
765 3         10 $self->_env->{'%defaults'}{$name} = $default;
766             }
767             else {
768 5 100       11 if ( defined $default ) {
769 4 100       23 if ( looks_like_number($default) ) {
770 3         6 push @{ $self->_source }, "$access = $default;";
  3         18  
771             }
772             else {
773 1         2 push @{ $self->_source },
  1         3  
774             "$access = " . perlstring($default) . ';';
775             }
776             }
777             else {
778 1         3 push @{ $self->_source }, "$access = undef;";
  1         2  
779             }
780             }
781              
782 8         17 return;
783             }
784              
785             sub _add_type_check {
786 22     22   228 my $self = shift;
787 22         40 my $access = shift;
788 22         40 my $name = shift;
789 22         37 my $spec = shift;
790              
791 22         47 my $type = $spec->{type};
792 22 50       107 croak "Passed a type that is not an object for $name: $type"
793             unless blessed $type;
794              
795 7         17 push @{ $self->_source }, sprintf( 'if ( exists %s ) {', $access )
796 22 100       69 if $spec->{optional};
797              
798 22         49 push @{ $self->_source },
799 22         44 $self->_type_check( $access, $name, $spec->{type} );
800              
801 7         19 push @{ $self->_source }, '}'
802 22 100       70 if $spec->{optional};
803              
804 22         87 return;
805             }
806              
807             sub _type_check {
808 24     24   57 my $self = shift;
809 24         53 my $access = shift;
810 24         43 my $name = shift;
811 24         43 my $type = shift;
812              
813             # Specio
814 24 0       156 return $type->can('can_inline_coercion_and_check')
    50          
    100          
815             ? $self->_add_specio_check( $access, $name, $type )
816              
817             # Type::Tiny
818             : $type->can('inline_assert')
819             ? $self->_add_type_tiny_check( $access, $name, $type )
820              
821             # Moose
822             : $type->can('can_be_inlined')
823             ? $self->_add_moose_check( $access, $name, $type )
824             : croak 'Unknown type object ' . ref $type;
825             }
826              
827             # From reading through the Type::Tiny source, I can't see any cases where a
828             # Type::Tiny type or coercion needs to provide any environment variables to
829             # compile with.
830             sub _add_type_tiny_check {
831 6     6   162 my $self = shift;
832 6         10 my $access = shift;
833 6         10 my $name = shift;
834 6         9 my $type = shift;
835              
836 6         17 my $qname = perlstring($name);
837              
838 6         10 my @source;
839 6 100       16 if ( $type->has_coercion ) {
840 4         61 my $coercion = $type->coercion;
841 4 100       37 if ( $coercion->can_be_inlined ) {
842 1         102 push @source,
843             "$access = " . $coercion->inline_coercion($access) . ';';
844             }
845             else {
846 3         175 $self->_env->{'%tt_coercions'}{$name}
847             = $coercion->compiled_coercion;
848 3         18 push @source,
849             sprintf(
850             '%s = $tt_coercions{%s}->( %s );',
851             $access, $qname, $access,
852             );
853             }
854             }
855              
856 6 100       366 if ( $type->can_be_inlined ) {
857 3         135 push @source,
858             $type->inline_assert($access);
859             }
860             else {
861 3         83 push @source,
862             sprintf(
863             '$types{%s}->assert_valid( %s );',
864             $qname, $access,
865             );
866 3         9 $self->_env->{'%types'}{$name} = $type;
867             }
868              
869 6         381 return @source;
870             }
871              
872             sub _add_specio_check {
873 18     18   39 my $self = shift;
874 18         36 my $access = shift;
875 18         37 my $name = shift;
876 18         38 my $type = shift;
877              
878 18         76 my $qname = perlstring($name);
879              
880 18         40 my @source;
881              
882 18 100       69 if ( $type->can_inline_coercion_and_check ) {
883 13 100       529 if ( $type->has_coercions ) {
884 1         9 my ( $source, $env ) = $type->inline_coercion_and_check($access);
885 1         417 push @source, sprintf( '%s = %s;', $access, $source );
886 1         5 $self->_add_to_environment(
887             sprintf(
888             'The inline_coercion_and_check for %s ',
889             $type->_description
890             ),
891             $env,
892             );
893             }
894             else {
895 12         117 my ( $source, $env ) = $type->inline_assert($access);
896 12         2013 push @source, $source . ';';
897 12         61 $self->_add_to_environment(
898             sprintf(
899             'The inline_assert for %s ',
900             $type->_description
901             ),
902             $env,
903             );
904             }
905             }
906             else {
907 5         191 my @coercions = $type->coercions;
908 5         36 $self->_env->{'%specio_coercions'}{$name} = \@coercions;
909 5         20 for my $i ( 0 .. $#coercions ) {
910 4         18 my $c = $coercions[$i];
911 4 100       17 if ( $c->can_be_inlined ) {
912 1         20 push @source,
913             sprintf(
914             '%s = %s if %s;',
915             $access,
916             $c->inline_coercion($access),
917             $c->from->inline_check($access)
918             );
919 1         96 $self->_add_to_environment(
920             sprintf(
921             'The inline_coercion for %s ',
922             $c->_description
923             ),
924              
925             # This should really be public in Specio
926             $c->_inline_environment,
927             );
928             }
929             else {
930 3         43 push @source,
931             sprintf(
932             '%s = $specio_coercions{%s}[%s]->coerce(%s) if $specio_coercions{%s}[%s]->from->value_is_valid(%s);',
933             $access,
934             $qname,
935             $i,
936             $access,
937             $qname,
938             $i,
939             $access
940             );
941             }
942             }
943              
944 5         20 push @source,
945             sprintf(
946             '$types{%s}->validate_or_die(%s);',
947             $qname, $access,
948             );
949              
950 5         13 $self->_env->{'%types'}{$name} = $type;
951             }
952              
953 18         111 return @source;
954             }
955              
956             sub _add_moose_check {
957 0     0   0 my $self = shift;
958 0         0 my $access = shift;
959 0         0 my $name = shift;
960 0         0 my $type = shift;
961              
962 0         0 my $qname = perlstring($name);
963              
964 0         0 my @source;
965              
966 0 0       0 if ( $type->has_coercion ) {
967 0         0 $self->_env->{'%moose_coercions'}{$name} = $type->coercion;
968 0         0 push @source,
969             sprintf(
970             '%s = $moose_coercions{%s}->coerce( %s );',
971             $access, $qname, $access,
972             );
973             }
974              
975 0         0 $self->_env->{'%types'}{$name} = $type;
976              
977 0         0 my $code = <<'EOF';
978             if ( !%s ) {
979             my $type = $types{%s};
980             my $param = %s;
981             my $value = %s;
982             my $msg = $param . q{ failed with: } . $type->get_message($value);
983             die
984             Params::ValidationCompiler::Exception::ValidationFailedForMooseTypeConstraint
985             ->new(
986             message => $msg,
987             parameter => $param,
988             value => $value,
989             type => $type,
990             );
991             }
992             EOF
993              
994 0 0       0 my $check
995             = $type->can_be_inlined
996             ? $type->_inline_check($access)
997             : sprintf( '$types{%s}->check( %s )', $qname, $access );
998              
999 0         0 push @source, sprintf(
1000             $code,
1001             $check,
1002             $qname,
1003             $qname,
1004             $access,
1005             );
1006              
1007 0 0       0 if ( $type->can_be_inlined ) {
1008 0         0 $self->_add_to_environment(
1009             sprintf( 'The %s type', $type->name ),
1010             $type->inline_environment,
1011             );
1012             }
1013              
1014 0         0 return @source;
1015             }
1016              
1017             sub _add_to_environment {
1018 14     14   275 my $self = shift;
1019 14         35 my $what = shift;
1020 14         30 my $new_env = shift;
1021              
1022 14         48 my $env = $self->_env;
1023 14         32 for my $key ( keys %{$new_env} ) {
  14         92  
1024 26 50       80 if ( exists $env->{$key} ) {
1025 0         0 croak sprintf(
1026             '%s has an inline environment variable named %s'
1027             . ' that conflicts with a variable already in the environment',
1028             $what, $key
1029             );
1030             }
1031 26         60 $self->_env->{$key} = $new_env->{$key};
1032             }
1033             }
1034              
1035             1;
1036              
1037             # ABSTRACT: Object that implements the check subroutine compilation
1038              
1039             __END__
1040              
1041             =pod
1042              
1043             =encoding UTF-8
1044              
1045             =head1 NAME
1046              
1047             Params::ValidationCompiler::Compiler - Object that implements the check subroutine compilation
1048              
1049             =head1 VERSION
1050              
1051             version 0.31
1052              
1053             =for Pod::Coverage .*
1054              
1055             =head1 SUPPORT
1056              
1057             Bugs may be submitted at L<https://github.com/houseabsolute/Params-ValidationCompiler/issues>.
1058              
1059             =head1 SOURCE
1060              
1061             The source code repository for Params-ValidationCompiler can be found at L<https://github.com/houseabsolute/Params-ValidationCompiler>.
1062              
1063             =head1 AUTHOR
1064              
1065             Dave Rolsky <autarch@urth.org>
1066              
1067             =head1 COPYRIGHT AND LICENSE
1068              
1069             This software is Copyright (c) 2016 - 2023 by Dave Rolsky.
1070              
1071             This is free software, licensed under:
1072              
1073             The Artistic License 2.0 (GPL Compatible)
1074              
1075             The full text of the license can be found in the
1076             F<LICENSE> file included with this distribution.
1077              
1078             =cut