added typeconstraint's customizable error message support.
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
CommitLineData
3b46bd49 1package Mouse::Util::TypeConstraints;
d60c78b9 2use strict;
3use warnings;
139d92d2 4use base 'Exporter';
9baf5d6b 5
61a02a3a 6use Carp ();
6c169c50 7use Scalar::Util qw/blessed looks_like_number openhandle/;
684db121 8use Mouse::Meta::TypeConstraint;
d60c78b9 9
139d92d2 10our @EXPORT = qw(
d44f0d03 11 as where message from via type subtype coerce class_type role_type enum
ccf44227 12 find_type_constraint
139d92d2 13);
14
cceb0e25 15my %TYPE;
7dbebb1b 16my %TYPE_SOURCE;
8a7f2a8a 17my %COERCE;
18my %COERCE_KEYS;
4188b837 19
139d92d2 20sub as ($) {
61a02a3a 21 as => $_[0]
22}
139d92d2 23sub where (&) {
61a02a3a 24 where => $_[0]
25}
0f1dae9a 26sub message (&) {
61a02a3a 27 message => $_[0]
28}
29
139d92d2 30sub from { @_ }
31sub via (&) {
61a02a3a 32 $_[0]
33}
34
321e5271 35BEGIN {
381f326a 36 no warnings 'uninitialized';
cceb0e25 37 %TYPE = (
381f326a 38 Any => sub { 1 },
39 Item => sub { 1 },
40 Bool => sub {
c91d12e0 41 !defined($_[0]) || $_[0] eq "" || "$_[0]" eq '1' || "$_[0]" eq '0'
381f326a 42 },
c91d12e0 43 Undef => sub { !defined($_[0]) },
44 Defined => sub { defined($_[0]) },
45 Value => sub { defined($_[0]) && !ref($_[0]) },
46 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
47 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
48 Str => sub { defined($_[0]) && !ref($_[0]) },
49 ClassName => sub { Mouse::is_class_loaded($_[0]) },
50 Ref => sub { ref($_[0]) },
51
52 ScalarRef => sub { ref($_[0]) eq 'SCALAR' },
53 ArrayRef => sub { ref($_[0]) eq 'ARRAY' },
54 HashRef => sub { ref($_[0]) eq 'HASH' },
55 CodeRef => sub { ref($_[0]) eq 'CODE' },
56 RegexpRef => sub { ref($_[0]) eq 'Regexp' },
57 GlobRef => sub { ref($_[0]) eq 'GLOB' },
381f326a 58
59 FileHandle => sub {
c91d12e0 60 ref($_[0]) eq 'GLOB' && openhandle($_[0])
381f326a 61 or
c91d12e0 62 blessed($_[0]) && $_[0]->isa("IO::Handle")
abe4e887 63 },
381f326a 64
c91d12e0 65 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
8a7f2a8a 66 );
684db121 67 while (my ($name, $code) = each %TYPE) {
68 $TYPE{$name} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $name );
78b13827 69 }
d3982c7e 70
cceb0e25 71 sub optimized_constraints { \%TYPE }
72 my @TYPE_KEYS = keys %TYPE;
73 sub list_all_builtin_type_constraints { @TYPE_KEYS }
7dbebb1b 74
75 @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
381f326a 76}
d3982c7e 77
139d92d2 78sub type {
0d9fea22 79 my $pkg = caller(0);
80 my($name, %conf) = @_;
0d062abb 81 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 82 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
0d9fea22 83 };
321e5271 84 my $constraint = $conf{where} || do {
85 my $as = delete $conf{as} || 'Any';
86 if (! exists $TYPE{$as}) {
87 $TYPE{$as} = _build_type_constraint($as);
88 }
89 $TYPE{$as};
90 };
7dbebb1b 91
92 $TYPE_SOURCE{$name} = $pkg;
684db121 93 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
94 name => $name,
95 _compiled_type_constraint => sub {
96 local $_ = $_[0];
97 if (ref $constraint eq 'CODE') {
98 $constraint->($_[0])
99 } else {
100 $constraint->check($_[0])
101 }
102 }
103 );
0d9fea22 104}
105
139d92d2 106sub subtype {
4188b837 107 my $pkg = caller(0);
61a02a3a 108 my($name, %conf) = @_;
0d062abb 109 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
7dbebb1b 110 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
61a02a3a 111 };
29607c02 112 my $constraint = delete $conf{where};
113 my $as_constraint = find_or_create_isa_type_constraint(delete $conf{as} || 'Any');
7dbebb1b 114
115 $TYPE_SOURCE{$name} = $pkg;
684db121 116 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
117 name => $name,
118 _compiled_type_constraint => (
119 $constraint ?
120 sub {
121 local $_ = $_[0];
122 $as_constraint->check($_[0]) && $constraint->($_[0])
123 } :
124 sub {
125 local $_ = $_[0];
126 $as_constraint->check($_[0]);
127 }
128 ),
29607c02 129 %conf
684db121 130 );
7dbebb1b 131
d9f8c878 132 return $name;
4188b837 133}
134
139d92d2 135sub coerce {
61a02a3a 136 my($name, %conf) = @_;
137
138 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
cceb0e25 139 unless $TYPE{$name};
61a02a3a 140
8a7f2a8a 141 unless ($COERCE{$name}) {
142 $COERCE{$name} = {};
143 $COERCE_KEYS{$name} = [];
144 }
61a02a3a 145 while (my($type, $code) = each %conf) {
146 Carp::croak "A coercion action already exists for '$type'"
8a7f2a8a 147 if $COERCE{$name}->{$type};
61a02a3a 148
310ad28b 149 if (! $TYPE{$type}) {
150 # looks parameterized
151 if ($type =~ /^[^\[]+\[.+\]$/) {
94593ae8 152 $TYPE{$type} = _build_type_constraint($type);
310ad28b 153 } else {
154 Carp::croak "Could not find the type constraint ($type) to coerce from"
155 }
156 }
61a02a3a 157
daa8612f 158 unshift @{ $COERCE_KEYS{$name} }, $type;
8a7f2a8a 159 $COERCE{$name}->{$type} = $code;
61a02a3a 160 }
4188b837 161}
162
139d92d2 163sub class_type {
ecc6e3b1 164 my($name, $conf) = @_;
d9f8c878 165 if ($conf && $conf->{class}) {
166 # No, you're using this wrong
167 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
168 subtype($name, as => $conf->{class});
169 } else {
170 subtype(
171 $name => where => sub { $_->isa($name) }
172 );
173 }
ecc6e3b1 174}
175
139d92d2 176sub role_type {
47f36c05 177 my($name, $conf) = @_;
178 my $role = $conf->{role};
139d92d2 179 subtype(
61a02a3a 180 $name => where => sub {
181 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
182 $_->meta->does_role($role);
183 }
184 );
47f36c05 185}
186
684db121 187# this is an original method for Mouse
4188b837 188sub typecast_constraints {
684db121 189 my($class, $pkg, $types, $value) = @_;
86b99892 190 Carp::croak("wrong arguments count") unless @_==4;
eec1bb49 191
b3b74cc6 192 local $_;
684db121 193 for my $type ( split /\|/, $types ) {
8a7f2a8a 194 next unless $COERCE{$type};
8a7f2a8a 195 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
b3b74cc6 196 $_ = $value;
684db121 197 next unless $TYPE{$coerce_type}->check($value);
b3b74cc6 198 $_ = $value;
c91d12e0 199 $_ = $COERCE{$type}->{$coerce_type}->($value);
684db121 200 return $_ if $types->check($_);
4188b837 201 }
202 }
4188b837 203 return $value;
204}
205
01904723 206my $serial_enum = 0;
d44f0d03 207sub enum {
01904723 208 # enum ['small', 'medium', 'large']
209 if (ref($_[0]) eq 'ARRAY') {
210 my @elements = @{ shift @_ };
211
212 my $name = 'Mouse::Util::TypeConstaints::Enum::Serial::'
213 . ++$serial_enum;
214 enum($name, @elements);
215 return $name;
216 }
217
218 # enum size => 'small', 'medium', 'large'
d44f0d03 219 my $name = shift;
220 my %is_valid = map { $_ => 1 } @_;
221
222 subtype(
223 $name => where => sub { $is_valid{$_} }
224 );
225}
226
321e5271 227sub _build_type_constraint {
228
229 my $spec = shift;
230 my $code;
94593ae8 231 $spec =~ s/\s+//g;
321e5271 232 if ($spec =~ /^([^\[]+)\[(.+)\]$/) {
233 # parameterized
234 my $constraint = $1;
235 my $param = $2;
236 my $parent;
237 if ($constraint eq 'Maybe') {
238 $parent = _build_type_constraint('Undef');
239 } else {
240 $parent = _build_type_constraint($constraint);
241 }
242 my $child = _build_type_constraint($param);
243 if ($constraint eq 'ArrayRef') {
244 my $code_str =
245 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
246 "sub {\n" .
684db121 247 " if (\$parent->check(\$_[0])) {\n" .
321e5271 248 " foreach my \$e (\@{\$_[0]}) {\n" .
684db121 249 " return () unless \$child->check(\$e);\n" .
321e5271 250 " }\n" .
251 " return 1;\n" .
252 " }\n" .
253 " return ();\n" .
254 "};\n"
255 ;
256 $code = eval $code_str or Carp::confess("Failed to generate inline type constraint: $@");
257 } elsif ($constraint eq 'HashRef') {
258 my $code_str =
259 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
260 "sub {\n" .
684db121 261 " if (\$parent->check(\$_[0])) {\n" .
321e5271 262 " foreach my \$e (values \%{\$_[0]}) {\n" .
684db121 263 " return () unless \$child->check(\$e);\n" .
321e5271 264 " }\n" .
265 " return 1;\n" .
266 " }\n" .
267 " return ();\n" .
268 "};\n"
269 ;
270 $code = eval $code_str or Carp::confess($@);
271 } elsif ($constraint eq 'Maybe') {
272 my $code_str =
273 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
274 "sub {\n" .
684db121 275 " return \$child->check(\$_[0]) || \$parent->check(\$_[0]);\n" .
321e5271 276 "};\n"
277 ;
278 $code = eval $code_str or Carp::confess($@);
279 } else {
766534c2 280 Carp::confess("Support for parameterized types other than Maybe, ArrayRef or HashRef is not implemented yet");
321e5271 281 }
684db121 282 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 283 } else {
284 $code = $TYPE{ $spec };
285 if (! $code) {
5c5a61e0 286 # is $spec a known role? If so, constrain with 'does' instead of 'isa'
287 require Mouse::Meta::Role;
288 my $check = Mouse::Meta::Role->_metaclass_cache($spec)?
289 'does' : 'isa';
321e5271 290 my $code_str =
291 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
292 "sub {\n" .
5c5a61e0 293 " Scalar::Util::blessed(\$_[0]) && \$_[0]->$check('$spec');\n" .
321e5271 294 "}"
295 ;
296 $code = eval $code_str or Carp::confess($@);
684db121 297 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 298 }
299 }
684db121 300 return Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
321e5271 301}
302
303sub find_type_constraint {
304 my $type_constraint = shift;
305 return $TYPE{$type_constraint};
306}
307
308sub find_or_create_isa_type_constraint {
309 my $type_constraint = shift;
310
311 my $code;
312
313 $type_constraint =~ s/\s+//g;
94593ae8 314
315 $code = $TYPE{$type_constraint};
316 if (! $code) {
317 my @type_constraints = split /\|/, $type_constraint;
318 if (@type_constraints == 1) {
319 $code = $TYPE{$type_constraints[0]} ||
320 _build_type_constraint($type_constraints[0]);
321 } else {
322 my @code_list = map {
323 $TYPE{$_} || _build_type_constraint($_)
324 } @type_constraints;
684db121 325 $code = Mouse::Meta::TypeConstraint->new(
326 _compiled_type_constraint => sub {
327 my $i = 0;
328 for my $code (@code_list) {
329 return 1 if $code->check($_[0]);
330 }
331 return 0;
332 },
333 name => $type_constraint,
334 );
94593ae8 335 }
321e5271 336 }
337 return $code;
338}
339
d60c78b9 3401;
341
6feb83f1 342__END__
343
344=head1 NAME
345
5893ee36 346Mouse::Util::TypeConstraints - Type constraint system for Mouse
347
348=head2 SYNOPSIS
349
350 use Mouse::Util::TypeConstraints;
351
352 subtype 'Natural'
353 => as 'Int'
354 => where { $_ > 0 };
355
356 subtype 'NaturalLessThanTen'
357 => as 'Natural'
358 => where { $_ < 10 }
359 => message { "This number ($_) is not less than ten!" };
360
361 coerce 'Num'
362 => from 'Str'
363 => via { 0+$_ };
364
365 enum 'RGBColors' => qw(red green blue);
366
367 no Mouse::Util::TypeConstraints;
368
369=head1 DESCRIPTION
370
371This module provides Mouse with the ability to create custom type
372constraints to be used in attribute definition.
373
374=head2 Important Caveat
375
376This is B<NOT> a type system for Perl 5. These are type constraints,
377and they are not used by Mouse unless you tell it to. No type
378inference is performed, expressions are not typed, etc. etc. etc.
379
380A type constraint is at heart a small "check if a value is valid"
381function. A constraint can be associated with an attribute. This
382simplifies parameter validation, and makes your code clearer to read,
383because you can refer to constraints by name.
384
385=head2 Slightly Less Important Caveat
386
387It is B<always> a good idea to quote your type names.
388
389This prevents Perl from trying to execute the call as an indirect
390object call. This can be an issue when you have a subtype with the
391same name as a valid class.
392
393For instance:
394
395 subtype DateTime => as Object => where { $_->isa('DateTime') };
396
397will I<just work>, while this:
398
399 use DateTime;
400 subtype DateTime => as Object => where { $_->isa('DateTime') };
401
402will fail silently and cause many headaches. The simple way to solve
403this, as well as future proof your subtypes from classes which have
404yet to have been created, is to quote the type name:
405
406 use DateTime;
407 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
408
409=head2 Default Type Constraints
410
411This module also provides a simple hierarchy for Perl 5 types, here is
412that hierarchy represented visually.
413
414 Any
415 Item
416 Bool
417 Maybe[`a]
418 Undef
419 Defined
420 Value
421 Num
422 Int
423 Str
424 ClassName
425 RoleName
426 Ref
427 ScalarRef
428 ArrayRef[`a]
429 HashRef[`a]
430 CodeRef
431 RegexpRef
432 GlobRef
433 FileHandle
434 Object
435 Role
436
437B<NOTE:> Any type followed by a type parameter C<[`a]> can be
438parameterized, this means you can say:
439
440 ArrayRef[Int] # an array of integers
441 HashRef[CodeRef] # a hash of str to CODE ref mappings
442 Maybe[Str] # value may be a string, may be undefined
443
444If Mouse finds a name in brackets that it does not recognize as an
445existing type, it assumes that this is a class name, for example
446C<ArrayRef[DateTime]>.
447
448B<NOTE:> Unless you parameterize a type, then it is invalid to include
449the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
450name, I<not> as a parameterization of C<ArrayRef>.
451
452B<NOTE:> The C<Undef> type constraint for the most part works
453correctly now, but edge cases may still exist, please use it
454sparingly.
455
456B<NOTE:> The C<ClassName> type constraint does a complex package
457existence check. This means that your class B<must> be loaded for this
458type constraint to pass.
459
460B<NOTE:> The C<RoleName> constraint checks a string is a I<package
461name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
462constraint checks that an I<object does> the named role.
463
464=head2 Type Constraint Naming
465
466Type name declared via this module can only contain alphanumeric
467characters, colons (:), and periods (.).
468
469Since the types created by this module are global, it is suggested
470that you namespace your types just as you would namespace your
471modules. So instead of creating a I<Color> type for your
472B<My::Graphics> module, you would call the type
473I<My::Graphics::Types::Color> instead.
474
475=head2 Use with Other Constraint Modules
476
477This module can play nicely with other constraint modules with some
478slight tweaking. The C<where> clause in types is expected to be a
479C<CODE> reference which checks it's first argument and returns a
480boolean. Since most constraint modules work in a similar way, it
481should be simple to adapt them to work with Mouse.
482
483For instance, this is how you could use it with
484L<Declare::Constraints::Simple> to declare a completely new type.
485
486 type 'HashOfArrayOfObjects',
487 {
488 where => IsHashRef(
489 -keys => HasLength,
490 -values => IsArrayRef(IsObject)
491 )
492 };
493
494Here is an example of using L<Test::Deep> and it's non-test
495related C<eq_deeply> function.
496
497 type 'ArrayOfHashOfBarsAndRandomNumbers'
498 => where {
499 eq_deeply($_,
500 array_each(subhashof({
501 bar => isa('Bar'),
502 random_number => ignore()
503 })))
504 };
6feb83f1 505
506=head1 METHODS
507
508=head2 optimized_constraints -> HashRef[CODE]
509
510Returns the simple type constraints that Mouse understands.
511
c91d12e0 512=head1 FUNCTIONS
513
514=over 4
515
516=item B<subtype 'Name' => as 'Parent' => where { } ...>
517
518=item B<subtype as 'Parent' => where { } ...>
519
520=item B<class_type ($class, ?$options)>
521
522=item B<role_type ($role, ?$options)>
523
524=item B<enum (\@values)>
525
526=back
527
5893ee36 528=head1 THANKS
529
530Much of this documentation was taken from L<Moose::Util::TypeConstraints>
531
6feb83f1 532=cut
533
534