s/Moose/Mouse/ in a test
[gitmo/Mouse.git] / lib / Mouse / Util / TypeConstraints.pm
CommitLineData
3b46bd49 1package Mouse::Util::TypeConstraints;
d60c78b9 2use strict;
3use warnings;
f3bb863f 4
5use Exporter;
9baf5d6b 6
f5ee065f 7use Carp qw(confess);
6c169c50 8use Scalar::Util qw/blessed looks_like_number openhandle/;
6d28c5cf 9
2efc0af1 10use Mouse::Util qw(does_role not_supported);
25f83ae1 11use Mouse::Meta::Module; # get_metaclass_by_name()
684db121 12use Mouse::Meta::TypeConstraint;
d60c78b9 13
f3bb863f 14our @ISA = qw(Exporter);
139d92d2 15our @EXPORT = qw(
d44f0d03 16 as where message from via type subtype coerce class_type role_type enum
ccf44227 17 find_type_constraint
139d92d2 18);
19
cceb0e25 20my %TYPE;
8a7f2a8a 21my %COERCE;
22my %COERCE_KEYS;
4188b837 23
139d92d2 24sub as ($) {
cd2b9201 25 return(as => $_[0]);
61a02a3a 26}
139d92d2 27sub where (&) {
cd2b9201 28 return(where => $_[0])
61a02a3a 29}
0f1dae9a 30sub message (&) {
cd2b9201 31 return(message => $_[0])
61a02a3a 32}
33
73766a27 34sub from { @_ }
cd2b9201 35sub via (&) { $_[0] }
61a02a3a 36
321e5271 37BEGIN {
993e62a7 38 my %builtins = (
f5ee065f 39 Any => undef, # null check
40 Item => undef, # null check
41 Maybe => undef, # null check
73766a27 42
43 Bool => sub { $_[0] ? $_[0] eq '1' : 1 },
c91d12e0 44 Undef => sub { !defined($_[0]) },
45 Defined => sub { defined($_[0]) },
46 Value => sub { defined($_[0]) && !ref($_[0]) },
47 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
48 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
49 Str => sub { defined($_[0]) && !ref($_[0]) },
c91d12e0 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' },
73766a27 66
67 ClassName => sub { Mouse::Util::is_class_loaded($_[0]) },
d4571def 68 RoleName => sub { (Mouse::Util::find_meta($_[0]) || return 0)->isa('Mouse::Meta::Role') },
8a7f2a8a 69 );
993e62a7 70
71 while (my ($name, $code) = each %builtins) {
73766a27 72 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
3b89ea91 73 name => $name,
74 optimized => $code,
73766a27 75 );
78b13827 76 }
d3982c7e 77
f5ee065f 78 sub optimized_constraints {
79 Carp::cluck('optimized_constraints() has been deprecated');
80 return \%TYPE;
81 }
d4571def 82
993e62a7 83 my @builtins = keys %TYPE;
84 sub list_all_builtin_type_constraints { @builtins }
85
86 sub list_all_type_constraints { keys %TYPE }
381f326a 87}
d3982c7e 88
f5ee065f 89sub _create_type{
90 my $mode = shift;
91
73766a27 92 my $name;
f5ee065f 93 my %args;
73766a27 94
f5ee065f 95 if(@_ == 1 && ref $_[0]){ # @_ : { name => $name, where => ... }
96 %args = %{$_[0]};
73766a27 97 }
f5ee065f 98 elsif(@_ == 2 && ref $_[1]){ # @_ : $name => { where => ... }
73766a27 99 $name = $_[0];
f5ee065f 100 %args = %{$_[1]};
73766a27 101 }
f5ee065f 102 elsif(@_ % 2){ # @_ : $name => ( where => ... )
103 ($name, %args) = @_;
73766a27 104 }
f5ee065f 105 else{ # @_ : (name => $name, where => ...)
106 %args = @_;
73766a27 107 }
108
f5ee065f 109 if(!defined $name){
110 if(!defined($name = $args{name})){
111 $name = '__ANON__';
112 }
cd2b9201 113 }
d4571def 114
f5ee065f 115 $args{name} = $name;
7dbebb1b 116
f5ee065f 117 my $package_defined_in = $args{package_defined_in} ||= caller(1);
d4571def 118
f5ee065f 119 my $existing = $TYPE{$name};
120 if($existing && $existing->{package_defined_in} ne $package_defined_in){
121 confess("The type constraint '$name' has already been created in "
122 . "$existing->{package_defined_in} and cannot be created again in $package_defined_in");
123 }
d4571def 124
24410e3a 125 $args{constraint} = delete($args{where}) if exists $args{where};
126 $args{optimized} = delete $args{optimized_as} if exists $args{optimized_as};
0d9fea22 127
f5ee065f 128 my $constraint;
129 if($mode eq 'subtype'){
24410e3a 130 my $parent = delete($args{as})
131 or confess('A subtype cannot consist solely of a name, it must have a parent');
7a50b450 132
24410e3a 133 $constraint = find_or_create_isa_type_constraint($parent)->create_child_type(%args);
7a50b450 134 }
135 else{
f5ee065f 136 $constraint = Mouse::Meta::TypeConstraint->new(%args);
73766a27 137 }
7dbebb1b 138
f5ee065f 139 return $TYPE{$name} = $constraint;
140}
7dbebb1b 141
f5ee065f 142sub type {
143 return _create_type('type', @_);
144}
d4571def 145
f5ee065f 146sub subtype {
147 return _create_type('subtype', @_);
4188b837 148}
149
139d92d2 150sub coerce {
ffbbf459 151 my $type_name = shift;
61a02a3a 152
ffbbf459 153 my $type = find_type_constraint($type_name)
154 or confess("Cannot find type '$type_name', perhaps you forgot to load it.");
61a02a3a 155
ffbbf459 156 $type->_add_type_coercions(@_);
cd2b9201 157 return;
4188b837 158}
159
139d92d2 160sub class_type {
ecc6e3b1 161 my($name, $conf) = @_;
d9f8c878 162 if ($conf && $conf->{class}) {
163 # No, you're using this wrong
164 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
f5ee065f 165 _create_type 'type', $name => (
166 as => $conf->{class},
167
168 type => 'Class',
169 );
a497c7d3 170 }
171 else {
f5ee065f 172 _create_type 'type', $name => (
173 optimized_as => sub { blessed($_[0]) && $_[0]->isa($name) },
174
175 type => 'Class',
d9f8c878 176 );
177 }
ecc6e3b1 178}
179
139d92d2 180sub role_type {
47f36c05 181 my($name, $conf) = @_;
f5ee065f 182 my $role = ($conf && $conf->{role}) ? $conf->{role} : $name;
183 _create_type 'type', $name => (
184 optimized_as => sub { blessed($_[0]) && does_role($_[0], $role) },
185
186 type => 'Role',
61a02a3a 187 );
47f36c05 188}
189
4188b837 190sub typecast_constraints {
ffbbf459 191 my($class, $pkg, $type, $value) = @_;
2efc0af1 192 Carp::croak("wrong arguments count") unless @_ == 4;
eec1bb49 193
e763d56e 194 Carp::cluck("typecast_constraints() has been deprecated, which was an internal utility anyway");
195
ffbbf459 196 return $type->coerce($value);
4188b837 197}
198
d44f0d03 199sub enum {
f5ee065f 200 my($name, %valid);
201
01904723 202 # enum ['small', 'medium', 'large']
203 if (ref($_[0]) eq 'ARRAY') {
f5ee065f 204 %valid = map{ $_ => undef } @{ $_[0] };
205 $name = sprintf '(%s)', join '|', sort @{$_[0]};
206 }
207 # enum size => 'small', 'medium', 'large'
208 else{
209 $name = shift;
210 %valid = map{ $_ => undef } @_;
211 }
212 return _create_type 'type', $name => (
213 optimized_as => sub{ defined($_[0]) && !ref($_[0]) && exists $valid{$_[0]} },
214
215 type => 'Enum',
216 );
217}
218
219sub _find_or_create_regular_type{
220 my($spec) = @_;
221
222 return $TYPE{$spec} if exists $TYPE{$spec};
01904723 223
25f83ae1 224 my $meta = Mouse::Meta::Module::get_metaclass_by_name($spec);
f5ee065f 225
226 if(!$meta){
227 return;
01904723 228 }
229
f5ee065f 230 my $check;
231 my $type;
57f0e313 232 if($meta->isa('Mouse::Meta::Role')){
f5ee065f 233 $check = sub{
234 return blessed($_[0]) && $_[0]->does($spec);
235 };
236 $type = 'Role';
237 }
238 else{
239 $check = sub{
240 return blessed($_[0]) && $_[0]->isa($spec);
241 };
242 $type = 'Class';
243 }
244
f5ee065f 245 return $TYPE{$spec} = Mouse::Meta::TypeConstraint->new(
3b89ea91 246 name => $spec,
247 optimized => $check,
f5ee065f 248
3b89ea91 249 type => $type,
d44f0d03 250 );
251}
252
f5ee065f 253$TYPE{ArrayRef}{constraint_generator} = sub {
254 my($type_parameter) = @_;
3b89ea91 255 my $check = $type_parameter->_compiled_type_constraint;
321e5271 256
f5ee065f 257 return sub{
258 foreach my $value (@{$_}) {
259 return undef unless $check->($value);
260 }
261 return 1;
262 }
263};
264$TYPE{HashRef}{constraint_generator} = sub {
265 my($type_parameter) = @_;
3b89ea91 266 my $check = $type_parameter->_compiled_type_constraint;
f5ee065f 267
268 return sub{
269 foreach my $value(values %{$_}){
270 return undef unless $check->($value);
271 }
272 return 1;
273 };
274};
2efc0af1 275
f5ee065f 276# 'Maybe' type accepts 'Any', so it requires parameters
277$TYPE{Maybe}{constraint_generator} = sub {
278 my($type_parameter) = @_;
3b89ea91 279 my $check = $type_parameter->_compiled_type_constraint;
2efc0af1 280
f5ee065f 281 return sub{
282 return !defined($_) || $check->($_);
283 };
284};
285
286sub _find_or_create_parameterized_type{
287 my($base, $param) = @_;
288
289 my $name = sprintf '%s[%s]', $base->name, $param->name;
290
291 $TYPE{$name} ||= do{
f5ee065f 292 my $generator = $base->{constraint_generator};
293
294 if(!$generator){
295 confess("The $name constraint cannot be used, because $param doesn't subtype from a parameterizable type");
2efc0af1 296 }
f5ee065f 297
298 Mouse::Meta::TypeConstraint->new(
299 name => $name,
300 parent => $base,
301 constraint => $generator->($param),
302
303 type => 'Parameterized',
304 );
305 }
306}
307sub _find_or_create_union_type{
57f0e313 308 my @types = map{ $_->{type_constraints} ? @{$_->{type_constraints}} : $_ } @_;
f5ee065f 309
310 my $name = join '|', map{ $_->name } @types;
311
312 $TYPE{$name} ||= do{
f5ee065f 313 return Mouse::Meta::TypeConstraint->new(
3b89ea91 314 name => $name,
315 type_constraints => \@types,
f5ee065f 316
3b89ea91 317 type => 'Union',
f5ee065f 318 );
319 };
320}
321
322# The type parser
323sub _parse_type{
324 my($spec, $start) = @_;
325
326 my @list;
327 my $subtype;
328
329 my $len = length $spec;
330 my $i;
331
332 for($i = $start; $i < $len; $i++){
333 my $char = substr($spec, $i, 1);
334
335 if($char eq '['){
57f0e313 336 my $base = _find_or_create_regular_type( substr($spec, $start, $i - $start) )
f5ee065f 337 or return;
338
339 ($i, $subtype) = _parse_type($spec, $i+1)
340 or return;
341 $start = $i+1; # reset
342
343 push @list, _find_or_create_parameterized_type($base => $subtype);
321e5271 344 }
f5ee065f 345 elsif($char eq ']'){
346 $len = $i+1;
347 last;
321e5271 348 }
f5ee065f 349 elsif($char eq '|'){
57f0e313 350 my $type = _find_or_create_regular_type( substr($spec, $start, $i - $start) );
351
57f0e313 352 if(!defined $type){
3b89ea91 353 # XXX: Mouse creates a new class type, but Moose does not.
354 $type = class_type( substr($spec, $start, $i - $start) );
57f0e313 355 }
f5ee065f 356
357 push @list, $type;
358
359 ($i, $subtype) = _parse_type($spec, $i+1)
360 or return;
361
362 $start = $i+1; # reset
363
364 push @list, $subtype;
321e5271 365 }
366 }
f5ee065f 367 if($i - $start){
368 push @list, _find_or_create_regular_type(substr $spec, $start, $i - $start);
369 }
321e5271 370
f5ee065f 371 if(@list == 0){
372 return;
373 }
374 elsif(@list == 1){
375 return ($len, $list[0]);
993e62a7 376 }
377 else{
f5ee065f 378 return ($len, _find_or_create_union_type(@list));
993e62a7 379 }
321e5271 380}
381
f5ee065f 382
383sub find_type_constraint {
384 my($spec) = @_;
e98220ab 385 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
f5ee065f 386
387 $spec =~ s/\s+//g;
388 return $TYPE{$spec};
2efc0af1 389}
390
f5ee065f 391sub find_or_parse_type_constraint {
392 my($spec) = @_;
e98220ab 393 return $spec if blessed($spec) && $spec->isa('Mouse::Meta::TypeConstraint');
9c85e9dc 394
f5ee065f 395 $spec =~ s/\s+//g;
396 return $TYPE{$spec} || do{
397 my($pos, $type) = _parse_type($spec, 0);
398 $type;
399 };
400}
321e5271 401
f5ee065f 402sub find_or_create_does_type_constraint{
403 my $type = find_or_parse_type_constriant(@_) || role_type(@_);
94593ae8 404
f5ee065f 405 if($type->{type} && $type->{type} ne 'Role'){
406 Carp::cluck("$type is not a role type");
321e5271 407 }
f5ee065f 408 return $type;
409}
410
411sub find_or_create_isa_type_constraint {
412 return find_or_parse_type_constraint(@_) || class_type(@_);
321e5271 413}
414
d60c78b9 4151;
416
6feb83f1 417__END__
418
419=head1 NAME
420
5893ee36 421Mouse::Util::TypeConstraints - Type constraint system for Mouse
422
423=head2 SYNOPSIS
424
425 use Mouse::Util::TypeConstraints;
426
427 subtype 'Natural'
428 => as 'Int'
429 => where { $_ > 0 };
430
431 subtype 'NaturalLessThanTen'
432 => as 'Natural'
433 => where { $_ < 10 }
434 => message { "This number ($_) is not less than ten!" };
435
436 coerce 'Num'
437 => from 'Str'
438 => via { 0+$_ };
439
440 enum 'RGBColors' => qw(red green blue);
441
442 no Mouse::Util::TypeConstraints;
443
444=head1 DESCRIPTION
445
446This module provides Mouse with the ability to create custom type
447constraints to be used in attribute definition.
448
449=head2 Important Caveat
450
451This is B<NOT> a type system for Perl 5. These are type constraints,
452and they are not used by Mouse unless you tell it to. No type
453inference is performed, expressions are not typed, etc. etc. etc.
454
455A type constraint is at heart a small "check if a value is valid"
456function. A constraint can be associated with an attribute. This
457simplifies parameter validation, and makes your code clearer to read,
458because you can refer to constraints by name.
459
460=head2 Slightly Less Important Caveat
461
462It is B<always> a good idea to quote your type names.
463
464This prevents Perl from trying to execute the call as an indirect
465object call. This can be an issue when you have a subtype with the
466same name as a valid class.
467
468For instance:
469
470 subtype DateTime => as Object => where { $_->isa('DateTime') };
471
472will I<just work>, while this:
473
474 use DateTime;
475 subtype DateTime => as Object => where { $_->isa('DateTime') };
476
477will fail silently and cause many headaches. The simple way to solve
478this, as well as future proof your subtypes from classes which have
479yet to have been created, is to quote the type name:
480
481 use DateTime;
482 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
483
484=head2 Default Type Constraints
485
486This module also provides a simple hierarchy for Perl 5 types, here is
487that hierarchy represented visually.
488
489 Any
490 Item
491 Bool
492 Maybe[`a]
493 Undef
494 Defined
495 Value
496 Num
497 Int
498 Str
499 ClassName
500 RoleName
501 Ref
502 ScalarRef
503 ArrayRef[`a]
504 HashRef[`a]
505 CodeRef
506 RegexpRef
507 GlobRef
508 FileHandle
509 Object
5893ee36 510
511B<NOTE:> Any type followed by a type parameter C<[`a]> can be
512parameterized, this means you can say:
513
514 ArrayRef[Int] # an array of integers
515 HashRef[CodeRef] # a hash of str to CODE ref mappings
516 Maybe[Str] # value may be a string, may be undefined
517
518If Mouse finds a name in brackets that it does not recognize as an
519existing type, it assumes that this is a class name, for example
520C<ArrayRef[DateTime]>.
521
522B<NOTE:> Unless you parameterize a type, then it is invalid to include
523the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
524name, I<not> as a parameterization of C<ArrayRef>.
525
526B<NOTE:> The C<Undef> type constraint for the most part works
527correctly now, but edge cases may still exist, please use it
528sparingly.
529
530B<NOTE:> The C<ClassName> type constraint does a complex package
531existence check. This means that your class B<must> be loaded for this
532type constraint to pass.
533
534B<NOTE:> The C<RoleName> constraint checks a string is a I<package
535name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
536constraint checks that an I<object does> the named role.
537
538=head2 Type Constraint Naming
539
540Type name declared via this module can only contain alphanumeric
541characters, colons (:), and periods (.).
542
543Since the types created by this module are global, it is suggested
544that you namespace your types just as you would namespace your
545modules. So instead of creating a I<Color> type for your
546B<My::Graphics> module, you would call the type
547I<My::Graphics::Types::Color> instead.
548
549=head2 Use with Other Constraint Modules
550
551This module can play nicely with other constraint modules with some
552slight tweaking. The C<where> clause in types is expected to be a
553C<CODE> reference which checks it's first argument and returns a
554boolean. Since most constraint modules work in a similar way, it
555should be simple to adapt them to work with Mouse.
556
557For instance, this is how you could use it with
558L<Declare::Constraints::Simple> to declare a completely new type.
559
560 type 'HashOfArrayOfObjects',
561 {
562 where => IsHashRef(
563 -keys => HasLength,
564 -values => IsArrayRef(IsObject)
565 )
566 };
567
568Here is an example of using L<Test::Deep> and it's non-test
569related C<eq_deeply> function.
570
571 type 'ArrayOfHashOfBarsAndRandomNumbers'
572 => where {
573 eq_deeply($_,
574 array_each(subhashof({
575 bar => isa('Bar'),
576 random_number => ignore()
577 })))
578 };
6feb83f1 579
580=head1 METHODS
581
24410e3a 582=head2 C<< list_all_builtin_type_constraints -> (Names) >>
6feb83f1 583
24410e3a 584Returns the names of builtin type constraints.
585
586=head2 C<< list_all_type_constraints -> (Names) >>
587
588Returns the names of all the type constraints.
6feb83f1 589
c91d12e0 590=head1 FUNCTIONS
591
592=over 4
593
1820fffe 594=item C<< subtype 'Name' => as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 595
1820fffe 596=item C<< subtype as 'Parent' => where { } ... -> Mouse::Meta::TypeConstraint >>
c91d12e0 597
1820fffe 598=item C<< class_type ($class, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 599
1820fffe 600=item C<< role_type ($role, ?$options) -> Mouse::Meta::TypeConstraint >>
c91d12e0 601
1820fffe 602=item C<< enum (\@values) -> Mouse::Meta::TypeConstraint >>
603
604=back
605
606=over 4
607
608=item C<< find_type_constraint(Type) -> Mouse::Meta::TypeConstraint >>
c91d12e0 609
610=back
611
5893ee36 612=head1 THANKS
613
1820fffe 614Much of this documentation was taken from C<Moose::Util::TypeConstraints>
615
616=head1 SEE ALSO
617
618L<Moose::Util::TypeConstraints>
5893ee36 619
6feb83f1 620=cut
621
622