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