File Coverage

blib/lib/Coat/Types.pm
Criterion Covered Total %
statement 112 114 98.2
branch 27 36 75.0
condition 12 21 57.1
subroutine 37 38 97.3
pod 13 19 68.4
total 201 228 88.1


line stmt bran cond sub pod time code
1             package Coat::Types;
2              
3 44     44   235972 use strict;
  44         85  
  44         1532  
4 44     44   219 use warnings;
  44         91  
  44         1291  
5              
6 44     44   218 use Carp 'confess';
  44         81  
  44         2686  
7 44     44   230 use base 'Exporter';
  44         87  
  44         6081  
8 44     44   294 use vars qw(@EXPORT);
  44         126  
  44         2491  
9              
10 44     44   31480 use Coat::Meta::TypeConstraint;
  44         113  
  44         55757  
11              
12             # Moose/Coat keywords
13             sub as ($);
14             sub from ($);
15             sub where (&);
16             sub message (&);
17             sub type ($$;$);
18             sub subtype ($$;$$);
19             sub enum ($;@);
20             sub via (&);
21             sub coerce ($@);
22              
23             @EXPORT = qw(
24             type subtype enum coerce
25             from as where via message
26            
27             register_type_constraint
28             find_type_constraint
29             find_or_create_type_constraint
30            
31             list_all_type_constraints
32             list_all_builtin_type_constraints
33            
34             create_parameterized_type_constraint
35             find_or_create_parameterized_type_constraint
36             );
37              
38 757     757 1 3637 sub as ($) { $_[0] }
39 5     5 1 49 sub from ($) { $_[0] }
40 847     847 1 2590 sub where (&) { $_[0] }
41 5     5 1 24 sub via (&) { $_[0] }
42 3     3 1 10 sub message (&) { $_[0] }
43              
44             # {{{ - Registry
45             # singleton for storing Coat::Meta::Typeconstrain objects
46              
47             my $REGISTRY = { };
48              
49             sub register_type_constraint {
50 931     931 1 1402 my ($tc) = @_;
51              
52 931 50       2364 confess "can't register an unnamed type constraint"
53             unless defined $tc->name;
54              
55 931         2498 $REGISTRY->{$tc->name} = $tc;
56             }
57              
58 912     912 1 2973 sub find_type_constraint { $REGISTRY->{$_[0]} }
59 45     45 1 365 sub list_all_type_constraints { keys %$REGISTRY }
60 0     0 0 0 sub get_type_constraint_registry { $REGISTRY }
61              
62             sub find_or_create_type_constraint {
63 251     251 0 412 my ($type_name) = @_;
64            
65 251         599 my $tc = find_type_constraint( $type_name );
66 251 100       760 return $tc if defined $tc;
67              
68             return register_type_constraint( Coat::Meta::TypeConstraint->new(
69             name => $type_name,
70             parent => 'Object',
71 45     45   366 validation => sub { $_->isa($type_name) },
72 12     12   93 message => sub { "Value is not a member of class '$type_name' ($_)" },
73 84         9042 ));
74             }
75              
76             # }}}
77              
78             # {{{ - macro (type, subtype, coerce, enum)
79              
80             sub type($$;$) {
81 90     90 1 239 my ($type_name, $validation_code, $message) = @_;
82            
83 90         463 register_type_constraint( new Coat::Meta::TypeConstraint(
84             name => $type_name,
85             parent => undef,
86             validation => $validation_code,
87             message => $message) );
88             }
89              
90             sub subtype ($$;$$) {
91 757     757 1 1153 my ($type_name, $parent, $validation_code, $message) = @_;
92              
93 757         2329 register_type_constraint( new Coat::Meta::TypeConstraint(
94             name => $type_name,
95             parent => $parent,
96             validation => $validation_code,
97             message => $message ) );
98             }
99              
100             sub enum ($;@) {
101 1     1 1 3 my ($type_name, @values) = @_;
102 1 50       4 confess "You must have at least two values to enumerate through"
103             unless (scalar @values >= 2);
104              
105 1         4 my $regexp = join( '|', @values );
106            
107             subtype $type_name
108             => as 'Str'
109 1     2   3 => where { /^$regexp$/i };
  2         61  
110             }
111              
112             sub coerce($@) {
113 5     5 1 26 my ($type_name, %coercion_map) = @_;
114 5         19 my $tc = find_or_create_type_constraint($type_name);
115              
116 5 100       30 if ($tc->has_coercion) {
117 1         3 my $map = { %{ $tc->coercion_map }, %coercion_map };
  1         4  
118 1         5 $tc->coercion_map ( $map );
119             }
120             else {
121 4         18 $tc->coercion_map ( \%coercion_map );
122             }
123             }
124              
125             # }}}
126              
127             # {{{ - exported functions
128              
129             sub export_type_constraints_as_functions {
130 1     1 1 86 my $caller = caller;
131 1         5 foreach my $t ( list_all_type_constraints() ) {
132 19         37 my $constraint = find_type_constraint( $t );
133 19         37 my $constraint_symbol = "${caller}::${t}";
134             my $constraint_sub = sub {
135 53     53   127 my ($value) = @_;
136 53         85 local $_ = $value;
137 53 100       205 return $constraint->validation->($value) ? 1 : undef;
138 19         97 };
139             {
140 44     44   546 no strict 'refs';
  44         96  
  44         1769  
  19         26  
141 44     44   243 no warnings 'redefine', 'prototype';
  44         96  
  44         101549  
142 19         79 *$constraint_symbol = $constraint_sub;
143             }
144             }
145             }
146              
147             sub validate {
148 185     185 0 5659 my ($class, $attr, $attribute, $value, $type_name) = @_;
149 185   33     870 $type_name ||= $attr->{isa};
150              
151             # Exception if not defined and required attribute
152 185 100 100     1580 confess "Attribute \($attribute\) is required and cannot be undef"
153             if ($attr->{required} && ! defined $value);
154              
155             # Bypass the type check if not defined and not required
156 179 50 66     454 return $value if (! defined $value && ! $attr->{required});
157              
158             # get the current TypeConstraint object (or create it if not defined)
159 176 100       404 my $tc = (_is_parameterized_type_constraint( $type_name ))
160             ? find_or_create_parameterized_type_constraint( $type_name )
161             : find_or_create_type_constraint( $type_name ) ;
162            
163             # look for coercion : if the constraint has coercion and
164             # current value is of a supported coercion source type, coerce.
165 176 100       649 if ($attr->{coerce}) {
166 7 50       26 (not $tc->has_coercion) &&
167             confess "Coercion is not available for type '".$tc->name."'";
168             # coercing...
169 7         28 $value = $tc->coerce($value);
170             }
171              
172             # validate the value through the type-constraint
173 176         3038 $tc->validate( $value );
174              
175 140         390 return $value;
176             }
177              
178             # }}}
179              
180             # {{{ - parameterized type constraints
181              
182             sub find_or_create_parameterized_type_constraint ($) {
183 14     14 0 21 my ($type_name) = @_;
184 14   66     72 $REGISTRY->{$type_name} ||= create_parameterized_type_constraint( $type_name );
185             }
186              
187             sub create_parameterized_type_constraint ($) {
188 6     6 0 13 my ($type_name) = @_;
189            
190 6         20 my ($base_type, $type_parameter) =
191             _parse_parameterized_type_constraint($type_name);
192            
193 6 50 33     48 (defined $base_type && defined $type_parameter)
194             || confess "Could not parse type name ($type_name) correctly";
195              
196 6         30 my $tc_base = find_type_constraint( $base_type );
197 6 50       20 (defined $tc_base)
198             || confess "Could not locate the base type ($base_type)";
199            
200 6 50 66     19 confess "Unsupported base type ($base_type)"
201             if (! _base_type_is_arrayref($base_type) &&
202             ! _base_type_is_hashref($base_type) );
203              
204 6         38 my $tc_param = find_type_constraint( $type_parameter );
205              
206             my $tc = Coat::Meta::TypeConstraint->new (
207             name => $type_name,
208             parent => $base_type,
209 6     7   71 message => sub { "Validation failed with value $_" });
  7         30  
210              
211             # now add parameterized type constraint validation code
212             # depending on the base type
213 6 100       23 if (_base_type_is_arrayref( $base_type )) {
    50          
214             $tc->validation( sub {
215 7     7   20 foreach my $e (@$_) {
216 19         27 eval { $tc_param->validate( $e )};
  19         56  
217 19 100       1000 return 0 if $@;
218             }
219 5         22 return 1;
220 4         38 });
221             }
222             elsif (_base_type_is_hashref( $base_type )) {
223             $tc->validation( sub {
224 4   33 4   12 my $value = $_ || $_[0];
225              
226 4         11 foreach my $k (keys %$value) {
227 8         11 eval { $tc_param->validate( $value->{$k} )};
  8         24  
228 8 100       992 return 0 if $@;
229             }
230 2         9 return 1;
231 2         14 });
232             }
233              
234             # the type-constraint object is ready!
235 6         30 return $tc;
236             }
237              
238             # private subs for parameterized type constraints handling
239              
240             sub _base_type_is_arrayref ($) {
241 12     12   19 my ($type) = @_;
242 12         74 return $type =~ /^ArrayRef|ARRAY$/;
243             }
244              
245             sub _base_type_is_hashref ($) {
246 4     4   8 my ($type) = @_;
247 4         18 return $type =~ /^HashRef|HASH$/;
248             }
249              
250             sub _parse_parameterized_type_constraint ($) {
251 6     6   11 my ($type_name) = @_;
252              
253 6 50       34 if ($type_name =~ /^(\w+)\[([\w:_\d]+)\]$/) {
254 6         50 return ($1, $2);
255             }
256             else {
257 0         0 return (undef, undef);
258             }
259             }
260              
261             sub _is_parameterized_type_constraint ($) {
262 176     176   279 my ($type_name) = @_;
263 176         829 return $type_name =~ /^\w+\[[\w:_\d]+\]$/;
264             }
265              
266             # }}}
267              
268             # {{{ - built-in types and subtypes
269              
270             ## --------------------------------------------------------
271             ## some basic built-in types (mostly taken from Moose)
272             ## --------------------------------------------------------
273              
274             type 'Any' => where { 1 }; # meta-type including all
275             type 'Item' => where { 1 }; # base-type
276              
277             subtype 'Undef' => as 'Item' => where { !defined($_) };
278             subtype 'Defined' => as 'Item' => where { defined($_) };
279              
280             subtype 'Bool'
281             => as 'Item'
282             => where { !defined($_) || $_ eq "" || "$_" eq '1' || "$_" eq '0' };
283              
284             subtype 'Value'
285             => as 'Defined'
286             => where { !ref($_) };
287            
288             subtype 'Ref'
289             => as 'Defined'
290             => where { ref($_) };
291              
292             subtype 'Str'
293             => as 'Value'
294             => where { 1 };
295              
296             subtype 'Num'
297             => as 'Value'
298             => where { "$_" =~ /^-?[\d\.]+$/ };
299            
300             subtype 'Int'
301             => as 'Num'
302             => where { "$_" =~ /^-?[0-9]+$/ };
303              
304             subtype 'ScalarRef' => as 'Ref' => where { ref($_) eq 'SCALAR' };
305             subtype 'ArrayRef' => as 'Ref' => where { ref($_) eq 'ARRAY' };
306             subtype 'HashRef' => as 'Ref' => where { ref($_) eq 'HASH' };
307             subtype 'CodeRef' => as 'Ref' => where { ref($_) eq 'CODE' };
308             subtype 'RegexpRef' => as 'Ref' => where { ref($_) eq 'Regexp' };
309             subtype 'GlobRef' => as 'Ref' => where { ref($_) eq 'GLOB' };
310              
311             subtype 'FileHandle'
312             => as 'GlobRef'
313             => where { ref($_) eq 'GLOB' };
314              
315             subtype 'Object'
316             => as 'Ref'
317             => where { ref($_) &&
318             ref($_) ne 'Regexp' &&
319             ref($_) ne 'ARRAY' &&
320             ref($_) ne 'SCALAR' &&
321             ref($_) ne 'CODE' &&
322             ref($_) ne 'HASH'};
323              
324             subtype 'ClassName'
325             => as 'Str'
326             => where { ref($_[0]) && ref($_[0]) eq $_[1] };
327              
328             # accesor to all the built-in types
329             {
330             my @BUILTINS = list_all_type_constraints();
331 84     84 0 7582 sub list_all_builtin_type_constraints { @BUILTINS }
332             }
333              
334             # }}}
335              
336             1;
337             __END__