File Coverage

blib/lib/Data/Sah/Tiny.pm
Criterion Covered Total %
statement 79 80 98.7
branch 48 52 92.3
condition 5 6 83.3
subroutine 8 8 100.0
pod 1 1 100.0
total 141 147 95.9


line stmt bran cond sub pod time code
1             package Data::Sah::Tiny;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-08-01'; # DATE
5             our $DIST = 'Data-Sah-Tiny'; # DIST
6             our $VERSION = '0.000001'; # VERSION
7              
8 1     1   79786 use 5.010001;
  1         13  
9 1     1   5 use strict 'refs', 'vars';
  1         1  
  1         52  
10 1     1   7 use warnings;
  1         2  
  1         43  
11 1     1   1869 use Log::ger;
  1         62  
  1         5  
12              
13 1     1   866 use Data::Sah::Normalize qw(normalize_schema);
  1         1679  
  1         102  
14              
15 1     1   10 use Exporter qw(import);
  1         2  
  1         944  
16             our @EXPORT_OK = qw(gen_validator normalize_schema);
17              
18             # data_term must already be set
19             sub _gen_expr {
20 23     23   56 my ($schema0, $opts) = @_;
21              
22             my $nschema = $opts->{schema_is_normalized} ?
23 23 100       153 $schema0 : normalize_schema($schema0);
24 23         1477 log_trace "normalized schema: %s", $nschema;
25 23         112 my $type = $nschema->[0];
26 22         43 my $clset = { %{$nschema->[1]} };
  22         76  
27 22         57 my $dt = $opts->{data_term};
28              
29 22         42 my ($default_expr, $success_if_undef_expr, @check_exprs);
30              
31 22         937 require Data::Dmp;
32              
33             # first, handle 'default'
34 22 100       2358 if (exists $clset->{default}) {
35             $default_expr = "$dt = defined($dt) ? $dt : ".
36 1         8 Data::Dmp::dmp($clset->{default});
37 1         43 delete $clset->{default};
38             }
39              
40             # then handle 'req' & 'forbidden'
41 22 100       112 if (delete $clset->{req}) {
    100          
42 4         64 push @check_exprs, "defined($dt)";
43             } elsif (delete $clset->{forbidden}) {
44 1         4 $success_if_undef_expr = "!defined($dt)";
45 1         4 push @check_exprs, "!defined($dt)";
46             } else {
47 17         69 $success_if_undef_expr = "!defined($dt)";
48             }
49              
50             PROCESS_BUILTIN_TYPES: {
51 22 100       44 if ($type eq 'int') {
  22 100       100  
    100          
52 13         58 push @check_exprs, "!ref($dt) && $dt =~ /\\A-?[0-9]+\\z/";
53 13 100       51 if (defined(my $val = delete $clset->{min})) { push @check_exprs, "$dt >= $val" }
  1         4  
54 13 100       45 if (defined(my $val = delete $clset->{max})) { push @check_exprs, "$dt <= $val" }
  1         5  
55             } elsif ($type eq 'str') {
56 2         7 push @check_exprs, "!ref($dt)";
57 2 100       9 if (defined(my $val = delete $clset->{min_len})) { push @check_exprs, "length $dt >= $val" }
  1         4  
58 2 100       9 if (defined(my $val = delete $clset->{max_len})) { push @check_exprs, "length $dt <= $val" }
  1         5  
59             } elsif ($type eq 'array') {
60 6         24 push @check_exprs, "ref($dt) eq 'ARRAY'";
61 6 100       26 if (defined(my $val = delete $clset->{min_len})) { push @check_exprs, "\@{$dt} >= $val" }
  1         4  
62 6 100       22 if (defined(my $val = delete $clset->{max_len})) { push @check_exprs, "\@{$dt} <= $val" }
  1         5  
63 6 100       25 if (defined(my $val = delete $clset->{of})) {
64 3         38 my $expr = _gen_expr($val, {data_term => "\$_dst_elem"});
65 3         16 push @check_exprs, "do { my \$ok=1; for my \$_dst_elem (\@{$dt}) { (\$ok=0, last) unless $expr } \$ok }";
66             }
67             } else {
68 1         18 die "Unknown type '$type'";
69             }
70              
71 21 100       83 if (keys %$clset) {
72 1         20 die "Unknown clause(s) for type '$type': ".
73             join(", ", sort keys %$clset);
74             }
75             }
76              
77             my $expr = join(
78             "",
79             ($default_expr ? "( (($default_expr), 1), " : ""),
80             ($success_if_undef_expr ? "$success_if_undef_expr || (" : ""),
81 20 100       108 join(" && ", map { "($_)" } @check_exprs),
  34 100       175  
    100          
    100          
82             ($success_if_undef_expr ? ")" : ""),
83             ($default_expr ? ")" : ""),
84             );
85              
86 20 50       64 if ($opts->{hash}) {
87             return {
88 0         0 v => 2,
89             result => $expr,
90             modules => [],
91             vars => {},
92             };
93             } else {
94 20         81 return $expr;
95             }
96             }
97              
98             sub gen_validator {
99 21     21 1 46601 my ($schema, $opts0) = @_;
100 21   100     135 $opts0 //= {};
101              
102 21         47 my $opts = {};
103 21         74 $opts->{schema_is_normalized} = delete $opts0->{schema_is_normalized};
104 21         47 $opts->{source} = delete $opts0->{source};
105 21         53 $opts->{hash} = delete $opts0->{hash};
106 21   100     102 $opts->{return_type} = delete $opts0->{return_type} // "bool_valid";
107 21 50       177 $opts->{return_type} =~ /\A(bool_valid\+val|bool_valid)\z/
108             or die "return_type must be bool_valid or bool_valid+val";
109 21   50     121 $opts->{data_term} = delete $opts0->{data_term} // '$tmp';
110 21 100       107 keys %$opts0 and die "Unknown option(s): ".join(", ", sort keys %$opts0);
111              
112 20         51 my $dt = $opts->{data_term};
113              
114 20         73 my $expr = _gen_expr($schema, $opts);
115 17 50       50 return $expr if $opts->{hash};
116             my $src = join(
117             "",
118             "sub { ",
119             "my $dt = shift; ",
120 17 100       89 ($opts->{return_type} eq 'bool_valid+val' ? "my \$_dst_res = $expr; [\$_dst_res, $dt]" : $expr),
121             " }",
122             );
123 17 100       57 return $src if $opts->{source};
124              
125 16         3097 my $code = eval $src;
126 16 50       70 die if $@;
127 16         146 $code;
128             }
129              
130             1;
131             # ABSTRACT: Validate Sah schemas with as little code as possible
132              
133             __END__