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