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