File Coverage

blib/lib/Data/Sah/Params.pm
Criterion Covered Total %
statement 79 81 97.5
branch 30 38 78.9
condition n/a
subroutine 12 12 100.0
pod 1 4 25.0
total 122 135 90.3


line stmt bran cond sub pod time code
1             package Data::Sah::Params;
2              
3             our $DATE = '2016-05-30'; # DATE
4             our $VERSION = '0.003'; # VERSION
5              
6 1     1   425 use 5.010001;
  1         3  
7 1     1   3 use strict 'subs', 'vars';
  1         1  
  1         21  
8 1     1   3 use warnings;
  1         1  
  1         18  
9              
10 1     1   3 use Exporter qw(import);
  1         1  
  1         529  
11             our @EXPORT_OK = qw(compile Slurpy Optional Named);
12              
13             sub Optional($) {
14 2     2 0 1069 bless [$_[0]], "_Optional";
15             }
16              
17             sub Slurpy($) {
18 1     1 0 786 bless [$_[0]], "_Slurpy";
19             }
20              
21             sub Named {
22 5 100   5 0 1395 @_ or die "Need at least one pair for Named";
23 4 100       13 @_ % 2 == 0 or die "Odd number of elements for Named";
24 3         13 bless {@_}, "_Named";
25             }
26              
27             sub compile {
28 7 100   7 1 1325 my $opts = ref($_[0]) eq 'HASH' ? shift : {};
29 7 50       15 die "Please specify some params" unless @_;
30              
31             # we currently use Perinci::Sub::ValidateArgs to generate the validator, so
32             # we create rinci metadata from params specification.
33 7         17 my $meta = {v=>1.1, args=>{}, result_naked=>1};
34 7 100       12 if (ref($_[0]) eq '_Named') {
35 2 100       10 die "Cannot mixed Named and other params" unless @_ == 1;
36 1         2 $meta->{args_as} = 'hash';
37 1         2 for my $arg_name (keys %{$_[0]}) {
  1         3  
38 3         3 my $arg_schema = $_[0]{$arg_name};
39 3         2 my $req = 1;
40 3         4 while (1) {
41 4         3 my $ref = ref($arg_schema);
42 4 100       6 if ($ref eq '_Optional') {
43 1         2 $req = 0;
44 1         1 $arg_schema = $arg_schema->[0];
45 1         1 next;
46             }
47 3 50       4 if ($ref eq '_Slurpy') {
48             # noop
49 0         0 $arg_schema = $arg_schema->[0];
50 0         0 next;
51             }
52 3         3 last;
53             }
54 3         7 $meta->{args}{$arg_name} = {
55             schema => $arg_schema,
56             req => $req,
57             };
58             }
59             } else {
60 5         6 $meta->{args_as} = 'array';
61 5         15 for my $pos (0..$#_) {
62 9         12 my $arg_name = "arg$pos";
63 9         8 my $arg_schema = $_[$pos];
64 9         7 my $req = 1;
65 9         4 my $slurpy;
66 9         21 while (1) {
67 11         14 my $ref = ref($arg_schema);
68 11 100       17 if ($ref eq '_Named') {
69 1         9 die "Cannot mixed Named and other params";
70             }
71 10 100       17 if ($ref eq '_Optional') {
72 1         1 $req = 0;
73 1         2 $arg_schema = $arg_schema->[0];
74 1         1 next;
75             }
76 9 100       13 if ($ref eq '_Slurpy') {
77 1 50       3 die "Slurpy parameter must be the last parameter"
78             unless $pos == $#_;
79 1         2 $slurpy = 1;
80 1         1 $arg_schema = $arg_schema->[0];
81 1         1 next;
82             }
83 8         9 last;
84             }
85 8         33 $meta->{args}{$arg_name} = {
86             schema => $arg_schema,
87             req => $req,
88             pos => $pos,
89             (greedy => $slurpy) x !!$slurpy,
90             };
91             }
92             }
93              
94 5         588 require Perinci::Sub::ValidateArgs;
95 5         2837 my $src = Perinci::Sub::ValidateArgs::gen_args_validator(
96             meta=>$meta, source=>1, die=>1);
97              
98             # do some munging
99 5 100       93959 if ($meta->{args_as} eq 'hash') {
100 1 50       11 $src =~ s/^(\s*my \$args = )shift;/${1}{\@_};/m
101             or die "BUG: Can't replace #1a";
102 1 50       15 $src =~ s/(\A.+^\s*return )undef;/${1}\$args;/ms
103             or die "BUG: Can't replace #2a";
104             } else {
105 4 50       37 $src =~ s/^(\s*my \$args = )shift;/${1}[\@_];/m
106             or die "BUG: Can't replace #1b";
107 4 50       40 $src =~ s/(\A.+^\s*return )undef;/${1}\@\$args;/ms
108             or die "BUG: Can't replace #2b";
109             }
110 5 100       19 return $src if $opts->{want_source};
111              
112 1     1   5 my $code = eval $src;
  1     1   1  
  1     1   418  
  1     1   4  
  1         1  
  1         261  
  1         4  
  1         1  
  1         385  
  1         5  
  1         1  
  1         362  
  4         221  
113             #use Eval::Closure; my $code = eval_closure(source => $src);
114 4 50       10 die if $@;
115 4         23 $code;
116             }
117              
118             1;
119             # ABSTRACT: Validate function arguments using Sah schemas
120              
121             __END__
122              
123             =pod
124              
125             =encoding UTF-8
126              
127             =head1 NAME
128              
129             Data::Sah::Params - Validate function arguments using Sah schemas
130              
131             =head1 VERSION
132              
133             This document describes version 0.003 of Data::Sah::Params (from Perl distribution Data-Sah-Params), released on 2016-05-30.
134              
135             =head1 SYNOPSIS
136              
137             use Data::Sah::Params qw(compile Optional Slurpy Named);
138              
139             # positional parameters, some optional
140             sub f1 {
141             state $check = compile(
142             ["str*"],
143             ["int*", min=>1, max=>10, default=>5],
144             Optional [array => of=>"int*"],
145             );
146             my ($foo, $bar, $baz) = $check->(@_);
147             ...
148             }
149             f1(); # dies, missing required argument $foo
150             f1(undef); # dies, $foo must not be undef
151             f1("a"); # dies, missing required argument $bar
152             f1("a", undef); # ok, $bar = 5, $baz = undef
153             f1("a", 1); # ok, $bar = 1, $baz = undef
154             f1("a", "x"); # dies, $bar is not an int
155             f1("a", 3, [1,2,3]); # ok
156              
157             # positional parameters, slurpy last parameter
158             sub f2 {
159             state $check = compile(
160             ["str*"],
161             ["int*", min=>1, max=>10, default=>5],
162             Slurpy [array => of=>"int*"],
163             );
164             my ($foo, $bar, $baz) = $check->(@_);
165             ...
166             }
167             f1("a", 3, 1,2,3); # ok, $foo="a", $bar=3, $baz=[1,2,3]
168             f1("a", 3, 1,2,"b"); # dies, third element of $baz not an integer
169              
170             # named parameters, some optional
171             sub f3 {
172             state $check = compile(Named
173             foo => ["str*"],
174             bar => ["int*", min=>1, max=>10, default=>5],
175             baz => Optional [array => of=>"int*"],
176             );
177             my $args = $check->(@_);
178             ...
179             }
180             f1(foo => "a"); # dies, missing argument 'bar'
181             f1(foo => "a", bar=>1); # ok
182             f1(foo => "a", bar=>1, baz=>2); # dies, baz not an array
183              
184             =head1 DESCRIPTION
185              
186             Experimental.
187              
188             Currently mixing positional and named parameters not yet supported.
189              
190             =for Pod::Coverage ^(Optional|Slurpy|Named)$
191              
192             =head1 FUNCTIONS
193              
194             =head2 compile([ \%opts, ] $schema, ...) => coderef
195              
196             Create a validator. Accepts a list of schemas. Each schema can be prefixed with
197             C<Optional> or C<Slurpy>. Or, if your function will accept named arguments
198             (C<%args>) you can use: C<< Named(PARAM1=>$schema1, PARAM2=>$schema2, ...) >>
199             instead.
200              
201             Known options:
202              
203             =over
204              
205             =item * want_source => bool
206              
207             If set to 1, will return validator source code string instead of compiled code
208             (coderef). Useful for debugging.
209              
210             =back
211              
212             =head1 HOMEPAGE
213              
214             Please visit the project's homepage at L<https://metacpan.org/release/Data-Sah-Params>.
215              
216             =head1 SOURCE
217              
218             Source repository is at L<https://github.com/perlancar/perl-Data-Sah-Params>.
219              
220             =head1 BUGS
221              
222             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Sah-Params>
223              
224             When submitting a bug or request, please include a test-file or a
225             patch to an existing test-file that illustrates the bug or desired
226             feature.
227              
228             =head1 SEE ALSO
229              
230             L<Sah> for the schema language.
231              
232             Similar modules: L<Type::Params>, L<Params::Validate>, L<Params::CheckCompiler>.
233              
234             If you put your schemas in L<Rinci> function metadata (I recommend it, for the
235             convenience of specifying other stuffs besides argument schemas), take a look at
236             L<Perinci::Sub::ValidateArgs>.
237              
238             L<Params::Sah>. I've actually implemented something similar the year before
239             (albeit with a slightly different interface), before absent-mindedly
240             reimplemented later :-) We'll see which one will thrive.
241              
242             =head1 AUTHOR
243              
244             perlancar <perlancar@cpan.org>
245              
246             =head1 COPYRIGHT AND LICENSE
247              
248             This software is copyright (c) 2016 by perlancar@cpan.org.
249              
250             This is free software; you can redistribute it and/or modify it under
251             the same terms as the Perl 5 programming language system itself.
252              
253             =cut