File Coverage

lib/Attribute/Validate.pm
Criterion Covered Total %
statement 182 196 92.8
branch 30 38 78.9
condition 1 3 33.3
subroutine 47 47 100.0
pod 1 8 12.5
total 261 292 89.3


line stmt bran cond sub pod time code
1             package Attribute::Validate;
2              
3 5     5   39535 use v5.16.0;
  5         19  
4              
5 5     5   27 use strict;
  5         9  
  5         185  
6 5     5   25 use warnings;
  5         10  
  5         362  
7              
8 5     5   4926 use Attribute::Handlers;
  5         40302  
  5         38  
9 5     5   4408 use Type::Params qw/signature/;
  5         866642  
  5         64  
10 5     5   5629 use Carp::Always;
  5         4861  
  5         46  
11 5     5   348 use Carp qw/confess/;
  5         15  
  5         384  
12              
13 5     5   2727 use parent 'Exporter'; # inherit all of Exporter's methods
  5         1790  
  5         36  
14             our @EXPORT_OK = qw(anon_requires);
15              
16             our $VERSION = "0.0.9";
17              
18             sub UNIVERSAL::Requires : ATTR(CODE) {
19 5     5   573 no warnings 'redefine';
  5         10  
  5         353  
20 5     5   29 no strict 'refs';
  5         8  
  5         949  
21             my (
22 5     5 0 103527 $package, $symbol, $referent, $attr,
23             $data, $phase, $filename, $linenum
24             ) = @_;
25 5 100       19 if ( $symbol eq 'ANON' ) {
26 1         4 local $Carp::Internal{'Attribute::Validate'} = 1;
27 1         15 confess "Unable to add signature to anon subroutine";
28             }
29 4         4 my $orig_sub = *{$symbol}{CODE};
  4         6  
30 4         12 my $compiled = _requires_compile_types(@$data);
31 4         77710 *{$symbol} = _requires_new_sub( $compiled, $orig_sub );
  4         39  
32 5     5   32 }
  5         9  
  5         70  
33              
34             sub UNIVERSAL::ScalarContext : ATTR(CODE) {
35 5     5   2124 no warnings 'redefine';
  5         9  
  5         266  
36 5     5   25 no strict 'refs';
  5         8  
  5         1269  
37             my (
38 1     1 0 144029 $package, $symbol, $referent, $attr,
39             $data, $phase, $filename, $linenum
40             ) = @_;
41 1 50       7 if ( $symbol eq 'ANON' ) {
42 0         0 local $Carp::Internal{'Attribute::Validate'} = 1;
43 0         0 confess "Unable to add validation to anon subroutine";
44             }
45 1         2 my $orig_sub = *{$symbol}{CODE};
  1         3  
46 1         5 *{$symbol} = sub {
47 4     4   262922 local $Carp::Internal{'Attribute::Validate'} = 1;
48 4 100       18 if ( !defined wantarray ) {
49 2         39 confess 'The return of this sub must be used in scalar context';
50             }
51 2 100       6 if (wantarray) {
52 1         14 confess 'The return of this sub must be used in scalar context';
53             }
54 1         7 goto &$orig_sub;
55             }
56 5     5   32 }
  5         43  
  5         54  
  1         7  
57              
58             sub UNIVERSAL::NoScalarContext : ATTR(CODE) {
59 5     5   1960 no warnings 'redefine';
  5         10  
  5         284  
60 5     5   27 no strict 'refs';
  5         8  
  5         1421  
61             my (
62 1     1 0 1281 $package, $symbol, $referent, $attr,
63             $data, $phase, $filename, $linenum
64             ) = @_;
65 1 50       5 if ( $symbol eq 'ANON' ) {
66 0         0 local $Carp::Internal{'Attribute::Validate'} = 1;
67 0         0 confess "Unable to add validation to anon subroutine";
68             }
69 1         3 my $orig_sub = *{$symbol}{CODE};
  1         3  
70 1         5 *{$symbol} = sub {
71 4     4   5421 local $Carp::Internal{'Attribute::Validate'} = 1;
72 4 100       15 if ( !defined wantarray ) {
73 1         6 goto &$orig_sub;
74             }
75 3 100       11 if (wantarray) {
76 1         6 goto &$orig_sub;
77             }
78 2         26 confess 'The return of this sub must never be used in scalar context';
79             }
80 5     5   34 }
  5         9  
  5         22  
  1         6  
81              
82             sub UNIVERSAL::ListContext : ATTR(CODE) {
83 5     5   1934 no warnings 'redefine';
  5         10  
  5         294  
84 5     5   27 no strict 'refs';
  5         20  
  5         1074  
85             my (
86 1     1 0 151628 $package, $symbol, $referent, $attr,
87             $data, $phase, $filename, $linenum
88             ) = @_;
89 1 50       7 if ( $symbol eq 'ANON' ) {
90 0         0 local $Carp::Internal{'Attribute::Validate'} = 1;
91 0         0 confess "Unable to add validation to anon subroutine";
92             }
93 1         2 my $orig_sub = *{$symbol}{CODE};
  1         4  
94 1         6 *{$symbol} = sub {
95 4     4   275540 local $Carp::Internal{'Attribute::Validate'} = 1;
96 4 100       19 if ( !wantarray ) {
97 3         51 confess 'The return of this sub must be used in list context';
98             }
99 1         7 goto &$orig_sub;
100             }
101 5     5   32 }
  5         7  
  5         53  
  1         6  
102              
103             sub UNIVERSAL::NoListContext : ATTR(CODE) {
104 5     5   1913 no warnings 'redefine';
  5         9  
  5         304  
105 5     5   58 no strict 'refs';
  5         10  
  5         1159  
106             my (
107 1     1 0 3634 $package, $symbol, $referent, $attr,
108             $data, $phase, $filename, $linenum
109             ) = @_;
110 1 50       7 if ( $symbol eq 'ANON' ) {
111 0         0 local $Carp::Internal{'Attribute::Validate'} = 1;
112 0         0 confess "Unable to add validation to anon subroutine";
113             }
114 1         2 my $orig_sub = *{$symbol}{CODE};
  1         3  
115 1         6 *{$symbol} = sub {
116 4     4   5597 local $Carp::Internal{'Attribute::Validate'} = 1;
117 4 100       15 if (wantarray) {
118 2         31 confess 'The return of this sub must never be used in list context';
119             }
120 2         11 goto &$orig_sub;
121             }
122 5     5   34 }
  5         8  
  5         36  
  1         6  
123              
124             sub UNIVERSAL::NoVoidContext : ATTR(CODE) {
125 5     5   2023 no warnings 'redefine';
  5         10  
  5         260  
126 5     5   26 no strict 'refs';
  5         16  
  5         1118  
127             my (
128 1     1 0 895 $package, $symbol, $referent, $attr,
129             $data, $phase, $filename, $linenum
130             ) = @_;
131 1 50       4 if ( $symbol eq 'ANON' ) {
132 0         0 local $Carp::Internal{'Attribute::Validate'} = 1;
133 0         0 confess "Unable to add validation to anon subroutine";
134             }
135 1         2 my $orig_sub = *{$symbol}{CODE};
  1         2  
136 1         3 *{$symbol} = sub {
137 5     5   1759 local $Carp::Internal{'Attribute::Validate'} = 1;
138 5 100       13 if ( !defined wantarray ) {
139 2         18 confess 'The return of this sub must be used or stored';
140             }
141 3         8 goto &$orig_sub;
142             }
143 5     5   33 }
  5         7  
  5         40  
  1         4  
144              
145             sub UNIVERSAL::VoidContext : ATTR(CODE) {
146 5     5   2109 no warnings 'redefine';
  5         11  
  5         314  
147 5     5   30 no strict 'refs';
  5         8  
  5         1251  
148             my (
149 1     1 0 139276 $package, $symbol, $referent, $attr,
150             $data, $phase, $filename, $linenum
151             ) = @_;
152 1 50       7 if ( $symbol eq 'ANON' ) {
153 0         0 local $Carp::Internal{'Attribute::Validate'} = 1;
154 0         0 confess "Unable to add validation to anon subroutine";
155             }
156 1         2 my $orig_sub = *{$symbol}{CODE};
  1         4  
157 1         6 *{$symbol} = sub {
158 5     5   175080 local $Carp::Internal{'Attribute::Validate'} = 1;
159 5 100       12 if ( defined wantarray ) {
160 4         73 confess 'It is forbidden to store or use the return of this sub';
161             }
162 1         6 goto &$orig_sub;
163             }
164 5     5   54 }
  5         69  
  5         28  
  1         7  
165              
166             sub _requires_compile_types {
167 8     8   14 my $data = [];
168 8         15 @$data = @_;
169 8         13 my %extra_options;
170 8 100       24 if ( 'HASH' eq ref $data->[0] ) {
171 3         4 %extra_options = %{ shift @$data };
  3         10  
172             }
173 8         36 return signature( %extra_options, positional => $data );
174             }
175              
176             sub anon_requires {
177 4     4 1 315937 my $orig_sub = shift;
178 4 50 33     62 if ( !defined $orig_sub || 'CODE' ne ref $orig_sub ) {
179 0         0 die 'Anon requires didn\'t receive a sub';
180             }
181 4         14 my $compiled = _requires_compile_types(@_);
182 4         123368 return _requires_new_sub( $compiled, $orig_sub );
183             }
184              
185             sub _requires_new_sub {
186 8     8   19 my ( $compiled, $orig_sub ) = @_;
187 8 50       26 if ( !defined $orig_sub ) {
188 0         0 die 'Didn\'t receive a sub';
189             }
190             return sub {
191 10     10   197466 local $Carp::Internal{'Attribute::Validate'} = 1;
192 10         19 eval { $compiled->(@_); };
  10         43  
193 10 100       1473 if ($@) {
194 6         57 confess _filter_error("$@");
195             }
196 4         22 goto &$orig_sub;
197 8         36 };
198             }
199              
200             sub _filter_error {
201 6     6   1177 my $error = shift;
202 6         54 $error =~ s{at lib/Attribute/Validate.pm line \d+}{}g;
203 6         151 return $error;
204             }
205             1;
206              
207             =encoding utf8
208              
209             =head1 NAME
210              
211             Attribute::Validate - Validate your subs with attributes
212              
213             =head1 SYNOPSIS
214              
215             use Attribute::Validate;
216              
217             use Types::Standard qw/Maybe InstanceOf ArrayRef Str/
218              
219             use feature 'signatures';
220              
221             sub install_gentoo: Requires(Maybe[ArrayRef[InstanceOf['Linux::Capable::Computer']]], Str) ($maybe_computers, $hostname) {
222             # Do something here
223             }
224              
225             install_gentoo([$computer1, $computer2], 'Tux');
226              
227             =head1 DESCRIPTION
228              
229             This module allows you to validate your non-anonymous subs using the powerful attribute syntax of Perl, bringing easy type-checks to
230             your code, thanks to L you can create your own types to enforce your program using the data you expect it to use.
231              
232             =head1 INSTANCE METHODS
233              
234             This module cannot and shouldn't be instanced.
235              
236             =head1 ATTRIBUTES
237              
238             =head2 Requires
239              
240             sub say_word: Requires(Str) {
241             say shift;
242             }
243              
244             sub say_word_with_spec: Requires(\%spec, Str) {
245             say shift;
246             }
247              
248             Receives a list of L types and enforces those types into the arguments, the first argument may be a HashRef containing the
249             spec of L to change the behavior of this module, for example {strictness => 0} as the first argument will allow the user
250             to have more arguments than the ones declared.
251              
252             =head2 VoidContext
253              
254             sub doesnt_return: VoidContext {
255             }
256             my $lawless = doesnt_return(); # Dies
257             doesnt_return(); # Works
258              
259             Enforces the caller to use this sub in Void Context and do nothing with the return to avoid programmer errors and incorrect assumptions.
260              
261             =head2 NoVoidContext
262              
263             sub returns: NoVoidContext {
264             }
265             my $lawful = returns(); # Works
266             returns(); # Dies
267              
268             Enforces the caller to do something with the return of a sub to avoid programmer errors and assumptions.
269              
270             =head2 ListContext
271              
272             sub only_use_in_list_context: ListContext {
273             return (0..10);
274             }
275             my $list = only_use_in_list_context(); # Dies
276             only_use_in_list_context(); # Dies
277             my @list = only_use_in_list_context(); # Works
278              
279             Enforces the caller to use the subroutine in List Context to prevent errors and misunderstandings.
280              
281             =head2 NoListContext
282              
283             sub never_use_in_list_context: NoListContext {
284             return 'scalar_or_void';
285             }
286             my $list = never_use_in_list_context(); # Works
287             never_use_in_list_context(); # Works
288             my @list = never_use_in_list_context(); # Dies
289              
290             Enforces the caller to never use the subroutine in List Context to prevent errors and misunderstandings.
291              
292             =head2 ScalarContext
293              
294             sub only_use_in_scalar_context: ScalarContext {
295             return 'hey';
296             }
297             my @scalar = only_use_in_scalar_context(); # Dies
298             only_use_in_scalar_context(); # Dies
299             my $scalar = only_use_in_scalar_context(); # Works
300              
301             Enforces the caller to use the subroutine in Scalar Context to prevent errors and misunderstandings.
302              
303             =head2 NoScalarContext
304              
305             sub never_scalar_context: NoScalarContext {
306             return @array;
307             }
308             my @list = never_scalar_context(); # Works
309             never_scalar_context(); # Works
310             my $scalar = never_scalar_context(); # Dies
311              
312             Enforces the caller to never use the subroutine in Scalar Context to prevent errors and misunderstandings.
313              
314             =head1 EXPORTABLE SUBROUTINES
315              
316             =head2 anon_requires
317              
318             my $say_thing = anon_requires(sub($thing) {
319             say $thing;
320             ), Str);
321              
322             my $say_thing = anon_requires(sub($thing) {
323             say $thing;
324             }, \%spec, Str);
325              
326             Enforces types into anonymous subroutines since those cannot be enchanted using attributes.
327              
328             =head1 DEPENDENCIES
329              
330             The module will pull all the dependencies it needs on install, the minimum supported Perl is v5.16.3, although latest versions are mostly tested for 5.38.2
331              
332             =head1 CONFIGURATION AND ENVIRONMENT
333              
334             If your OS Perl is too old perlbrew can be used instead.
335              
336             =head1 BUGS AND LIMITATIONS
337              
338             Enchanting anonymous subroutines with attributes won't allow them to be used by this module because of limitations of the language.
339              
340             =head1 LICENSE AND COPYRIGHT
341              
342             This software is Copyright (c) 2025 by Sergio Iglesias.
343              
344             This is free software, licensed under:
345              
346             The MIT (X11) License
347              
348             =head1 CREDITS
349              
350             Thanks to MultiSafePay and the Tech Leader of MultiSafePay for agreeing in creating this CPAN module inspired in a similar feature in their codebase, this code was inspired by code found there, but was
351             written without the code in front from scratch.
352              
353             MultiSafePay is searching for Perl Developers for working in their offices on Estepona on Spain next to the beach, if you apply and do not get a reply and you think you are a
354             experienced/capable enough Perl Developer drop me a e-mail so I can try to help you get a job L.
355              
356             =head1 INCOMPATIBILITIES
357              
358             None known.
359              
360             =head1 VERSION
361              
362             0.0.x
363              
364             =head1 AUTHOR
365              
366             Sergio Iglesias
367              
368             =cut