File Coverage

blib/lib/Text/Xslate/Bridge/TypeDeclaration.pm
Criterion Covered Total %
statement 86 86 100.0
branch 28 30 93.3
condition 11 14 78.5
subroutine 22 22 100.0
pod 0 1 0.0
total 147 153 96.0


line stmt bran cond sub pod time code
1             package Text::Xslate::Bridge::TypeDeclaration;
2 10     10   331260 use strict;
  10         27  
  10         303  
3 10     10   55 use warnings;
  10         22  
  10         311  
4 10     10   470 use parent qw(Text::Xslate::Bridge);
  10         408  
  10         86  
5              
6 10     10   34772 use Carp qw(croak);
  10         33  
  10         490  
7 10     10   3163 use Data::Dumper;
  10         41773  
  10         1000  
8 10     10   3561 use List::MoreUtils qw(all);
  10         74284  
  10         118  
9 10     10   7255 use Scalar::Util qw(blessed);
  10         30  
  10         810  
10 10     10   78 use Text::Xslate qw(mark_raw);
  10         32  
  10         656  
11 10     10   4213 use Text::Xslate::Bridge::TypeDeclaration::Registry;
  10         40  
  10         205  
12 10     10   1402 use Type::Registry ();
  10         28  
  10         191  
13 10     10   57 use Type::Tiny qw();
  10         26  
  10         199  
14 10     10   4995 use Types::Standard qw(Any Dict slurpy);
  10         482533  
  10         146  
15              
16             our $VERSION = '0.13';
17              
18             # Set truthy value to skip validation for local scope.
19             our $DISABLE_VALIDATION = 0;
20              
21             our $IMPORT_DEFAULT_ARGS = {
22             method => 'declare',
23             validate => 1,
24             print => 1,
25             on_mismatch => 'die', # Cannot give a subroutine reference >_<
26             registry_class => undef, # Class name for Type::Registry to lookup types
27             };
28              
29             sub export_into_xslate {
30 14     14 0 37289 my $class = shift;
31 14         30 my $funcs_ref = shift;
32              
33 14 50       70 my $args = @_ == 1 ? shift : { @_ };
34 14 50 33     118 croak sprintf '%s can receive either a hash or a hashref.', $class
35             unless ref $args && ref($args) eq 'HASH';
36              
37 14         72 for my $key (keys %$IMPORT_DEFAULT_ARGS) {
38 70 100       218 $args->{$key} = $IMPORT_DEFAULT_ARGS->{$key} unless defined $args->{$key};
39             }
40              
41             my $registry = defined $args->{registry_class}
42             ? Type::Registry->for_class($args->{registry_class})
43 14 100       149 : Text::Xslate::Bridge::TypeDeclaration::Registry->new;
44              
45 14         85 $class->bridge(function => { $args->{method} => _declare_func($args, $registry) });
46 14         258 $class->SUPER::export_into_xslate($funcs_ref, @_);
47             }
48              
49             sub _declare_func {
50 14     14   43 my ($args, $registry) = @_;
51              
52             return sub {
53 53 100 100 53   625903 return if $DISABLE_VALIDATION || !$args->{validate};
54              
55 50         278 while (my ($key, $declaration) = splice(@_, 0, 2)) {
56 66         626 my $type = _type($declaration, $registry);
57 66         26337 my $value = Text::Xslate->current_vars->{$key};
58              
59 66 100       248 unless ($type->check($value)) {
60 36         367 my $msg = sprintf(
61             "Declaration mismatch for `%s`\n %s\n", $key, $type->get_message($value)
62             );
63 36 100       1779 _print($msg) if $args->{print};
64 36 100       131 last if _on_mismatch($msg, $args->{on_mismatch});
65             }
66             };
67              
68 50         2170 return;
69 14         269 };
70             }
71              
72             # This treats unknown types as a declaration error.
73             sub _get_invalid_type {
74 32     32   2604 my ($name_or_struct) = @_;
75              
76 32         83 local $Data::Dumper::Indent = 0;
77 32         91 local $Data::Dumper::Maxdepth = 2;
78 32         80 local $Data::Dumper::Sortkeys = 1;
79 32         65 local $Data::Dumper::Terse = 1;
80 32         66 local $Data::Dumper::Useqq = 1;
81              
82 32         138 my $name = Dumper($name_or_struct);
83             return Type::Tiny->new(
84             display_name => $name,
85       24     constraint => sub { },
86 15     15   1342 message => sub { "$name is not a known type" },
87 32         2314 );
88             }
89              
90             # returns: Type::Tiny
91             sub _type {
92 186     186   8622 my ($name_or_struct, $registry) = @_;
93              
94 186 100 100     1126 return _get_invalid_type($name_or_struct)
95             if !defined $name_or_struct || $name_or_struct eq '';
96              
97 177 100       598 if (my $ref = ref $name_or_struct) {
98 33 100       191 return _hash_structure($name_or_struct, $registry) if $ref eq 'HASH';
99 5         22 return _get_invalid_type($name_or_struct);
100             } else {
101 144         298 my $type = eval { $registry->lookup($name_or_struct) };
  144         724  
102 144 100 66     37281 return ($type && blessed($type) && $type->can('check'))
103             ? $type : _get_invalid_type($name_or_struct);
104             }
105             }
106              
107             sub _hash_structure {
108 52     52   320 my ($hash, $registry) = @_;
109              
110             return Dict[
111 52         241 (map { $_ => _type($hash->{$_}, $registry) } keys %$hash),
  99         27904  
112             slurpy Any
113             ];
114             }
115              
116             sub _print {
117 35     35   92 my ($msg) = @_;
118              
119 35   100     247 my $is_html = (Text::Xslate->current_engine->{type} || '') ne 'text';
120              
121 35 100       232 my @outputs = $is_html
122             ? (mark_raw("
\n"), $msg, mark_raw("
\n"))
123             : (mark_raw($msg));
124              
125 35         233 Text::Xslate->print(@outputs);
126             }
127              
128             sub _on_mismatch {
129 36     36   108 my ($msg, $func) = @_;
130              
131             my $h = +{
132             die => [ 'die_handler', 1 ],
133             warn => [ 'warn_handler', 0 ],
134             none => [ undef, 0 ]
135 36         224 }->{$func};
136              
137 36 100       146 if ($h->[0]) {
138 33         119 my $handler = Text::Xslate->current_engine->{$h->[0]};
139 33 100       1942 $handler->($msg) if $handler;
140             }
141 36         215 return $h->[1];
142             }
143              
144             1;
145              
146             __END__