1 package Mouse::Util::TypeConstraints;
7 use Scalar::Util qw/blessed looks_like_number openhandle/;
10 use Mouse::Meta::TypeConstraint;
13 as where message from via type subtype coerce class_type role_type enum
26 return(where => $_[0])
29 return(message => $_[0])
36 no warnings 'uninitialized';
41 !defined($_[0]) || $_[0] eq "" || "$_[0]" eq '1' || "$_[0]" eq '0'
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]) },
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' },
60 ref($_[0]) eq 'GLOB' && openhandle($_[0])
62 blessed($_[0]) && $_[0]->isa("IO::Handle")
65 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
67 while (my ($name, $code) = each %TYPE) {
68 $TYPE{$name} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $name );
71 sub optimized_constraints { \%TYPE }
72 my @TYPE_KEYS = keys %TYPE;
73 sub list_all_builtin_type_constraints { @TYPE_KEYS }
75 @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
80 my($name, %conf) = @_;
82 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
83 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
85 my $constraint = $conf{where} || do {
86 my $as = delete $conf{as} || 'Any';
87 if (! exists $TYPE{$as}) {
88 $TYPE{$as} = _build_type_constraint($as);
93 $TYPE_SOURCE{$name} = $pkg;
94 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
96 _compiled_type_constraint => sub {
98 if (ref $constraint eq 'CODE') {
101 $constraint->check($_[0])
113 if(@_ % 2){ # odd number of arguments
119 $name = $conf{name} || '__ANON__';
122 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
123 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
125 my $constraint = delete $conf{where};
126 my $as_constraint = find_or_create_isa_type_constraint(delete $conf{as} || 'Any');
128 $TYPE_SOURCE{$name} = $pkg;
129 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
131 _compiled_type_constraint => (
135 $as_constraint->check($_[0]) && $constraint->($_[0])
139 $as_constraint->check($_[0]);
151 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
154 unless ($COERCE{$name}) {
156 $COERCE_KEYS{$name} = [];
159 while (my($type, $code) = splice @_, 0, 2) {
160 Carp::croak "A coercion action already exists for '$type'"
161 if $COERCE{$name}->{$type};
163 if (! $TYPE{$type}) {
164 # looks parameterized
165 if ($type =~ /^[^\[]+\[.+\]$/) {
166 $TYPE{$type} = _build_type_constraint($type);
168 Carp::croak "Could not find the type constraint ($type) to coerce from"
172 push @{ $COERCE_KEYS{$name} }, $type;
173 $COERCE{$name}->{$type} = $code;
179 my($name, $conf) = @_;
180 if ($conf && $conf->{class}) {
181 # No, you're using this wrong
182 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
183 subtype($name, as => $conf->{class});
186 $name => where => sub { $_->isa($name) }
192 my($name, $conf) = @_;
193 my $role = $conf->{role};
195 $name => where => sub {
196 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
197 $_->meta->does_role($role);
202 # this is an original method for Mouse
203 sub typecast_constraints {
204 my($class, $pkg, $types, $value) = @_;
205 Carp::croak("wrong arguments count") unless @_==4;
208 for my $type ( split /\|/, $types ) {
209 next unless $COERCE{$type};
210 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
212 next unless $TYPE{$coerce_type}->check($value);
214 $_ = $COERCE{$type}->{$coerce_type}->($value);
215 return $_ if $types->check($_);
223 # enum ['small', 'medium', 'large']
224 if (ref($_[0]) eq 'ARRAY') {
225 my @elements = @{ shift @_ };
227 my $name = 'Mouse::Util::TypeConstaints::Enum::Serial::'
229 enum($name, @elements);
233 # enum size => 'small', 'medium', 'large'
235 my %is_valid = map { $_ => 1 } @_;
238 $name => where => sub { $is_valid{$_} }
242 sub _build_type_constraint {
247 if ($spec =~ /^([^\[]+)\[(.+)\]$/) {
252 if ($constraint eq 'Maybe') {
253 $parent = _build_type_constraint('Undef');
255 $parent = _build_type_constraint($constraint);
257 my $child = _build_type_constraint($param);
258 if ($constraint eq 'ArrayRef') {
260 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
262 " if (\$parent->check(\$_[0])) {\n" .
263 " foreach my \$e (\@{\$_[0]}) {\n" .
264 " return () unless \$child->check(\$e);\n" .
271 $code = eval $code_str or Carp::confess("Failed to generate inline type constraint: $@");
272 } elsif ($constraint eq 'HashRef') {
274 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
276 " if (\$parent->check(\$_[0])) {\n" .
277 " foreach my \$e (values \%{\$_[0]}) {\n" .
278 " return () unless \$child->check(\$e);\n" .
285 $code = eval $code_str or Carp::confess($@);
286 } elsif ($constraint eq 'Maybe') {
288 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
290 " return \$child->check(\$_[0]) || \$parent->check(\$_[0]);\n" .
293 $code = eval $code_str or Carp::confess($@);
295 Carp::confess("Support for parameterized types other than Maybe, ArrayRef or HashRef is not implemented yet");
297 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
299 $code = $TYPE{ $spec };
301 # is $spec a known role? If so, constrain with 'does' instead of 'isa'
302 require Mouse::Meta::Role;
303 my $check = Mouse::Meta::Role->_metaclass_cache($spec)?
306 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
308 " Scalar::Util::blessed(\$_[0]) && \$_[0]->$check('$spec');\n" .
311 $code = eval $code_str or Carp::confess($@);
312 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
315 return Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
318 sub find_type_constraint {
319 my $type_constraint = shift;
320 return $TYPE{$type_constraint};
323 sub find_or_create_isa_type_constraint {
324 my $type_constraint = shift;
326 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)")
327 if $type_constraint =~ /\A ( [^\[]+ ) \[\.+\] \z/xms &&
335 $type_constraint =~ s/\s+//g;
337 $code = $TYPE{$type_constraint};
339 my @type_constraints = split /\|/, $type_constraint;
340 if (@type_constraints == 1) {
341 $code = $TYPE{$type_constraints[0]} ||
342 _build_type_constraint($type_constraints[0]);
344 my @code_list = map {
345 $TYPE{$_} || _build_type_constraint($_)
347 $code = Mouse::Meta::TypeConstraint->new(
348 _compiled_type_constraint => sub {
350 for my $code (@code_list) {
351 return 1 if $code->check($_[0]);
355 name => $type_constraint,
368 Mouse::Util::TypeConstraints - Type constraint system for Mouse
372 use Mouse::Util::TypeConstraints;
378 subtype 'NaturalLessThanTen'
381 => message { "This number ($_) is not less than ten!" };
387 enum 'RGBColors' => qw(red green blue);
389 no Mouse::Util::TypeConstraints;
393 This module provides Mouse with the ability to create custom type
394 constraints to be used in attribute definition.
396 =head2 Important Caveat
398 This is B<NOT> a type system for Perl 5. These are type constraints,
399 and they are not used by Mouse unless you tell it to. No type
400 inference is performed, expressions are not typed, etc. etc. etc.
402 A type constraint is at heart a small "check if a value is valid"
403 function. A constraint can be associated with an attribute. This
404 simplifies parameter validation, and makes your code clearer to read,
405 because you can refer to constraints by name.
407 =head2 Slightly Less Important Caveat
409 It is B<always> a good idea to quote your type names.
411 This prevents Perl from trying to execute the call as an indirect
412 object call. This can be an issue when you have a subtype with the
413 same name as a valid class.
417 subtype DateTime => as Object => where { $_->isa('DateTime') };
419 will I<just work>, while this:
422 subtype DateTime => as Object => where { $_->isa('DateTime') };
424 will fail silently and cause many headaches. The simple way to solve
425 this, as well as future proof your subtypes from classes which have
426 yet to have been created, is to quote the type name:
429 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
431 =head2 Default Type Constraints
433 This module also provides a simple hierarchy for Perl 5 types, here is
434 that hierarchy represented visually.
459 B<NOTE:> Any type followed by a type parameter C<[`a]> can be
460 parameterized, this means you can say:
462 ArrayRef[Int] # an array of integers
463 HashRef[CodeRef] # a hash of str to CODE ref mappings
464 Maybe[Str] # value may be a string, may be undefined
466 If Mouse finds a name in brackets that it does not recognize as an
467 existing type, it assumes that this is a class name, for example
468 C<ArrayRef[DateTime]>.
470 B<NOTE:> Unless you parameterize a type, then it is invalid to include
471 the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
472 name, I<not> as a parameterization of C<ArrayRef>.
474 B<NOTE:> The C<Undef> type constraint for the most part works
475 correctly now, but edge cases may still exist, please use it
478 B<NOTE:> The C<ClassName> type constraint does a complex package
479 existence check. This means that your class B<must> be loaded for this
480 type constraint to pass.
482 B<NOTE:> The C<RoleName> constraint checks a string is a I<package
483 name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
484 constraint checks that an I<object does> the named role.
486 =head2 Type Constraint Naming
488 Type name declared via this module can only contain alphanumeric
489 characters, colons (:), and periods (.).
491 Since the types created by this module are global, it is suggested
492 that you namespace your types just as you would namespace your
493 modules. So instead of creating a I<Color> type for your
494 B<My::Graphics> module, you would call the type
495 I<My::Graphics::Types::Color> instead.
497 =head2 Use with Other Constraint Modules
499 This module can play nicely with other constraint modules with some
500 slight tweaking. The C<where> clause in types is expected to be a
501 C<CODE> reference which checks it's first argument and returns a
502 boolean. Since most constraint modules work in a similar way, it
503 should be simple to adapt them to work with Mouse.
505 For instance, this is how you could use it with
506 L<Declare::Constraints::Simple> to declare a completely new type.
508 type 'HashOfArrayOfObjects',
512 -values => IsArrayRef(IsObject)
516 Here is an example of using L<Test::Deep> and it's non-test
517 related C<eq_deeply> function.
519 type 'ArrayOfHashOfBarsAndRandomNumbers'
522 array_each(subhashof({
524 random_number => ignore()
530 =head2 optimized_constraints -> HashRef[CODE]
532 Returns the simple type constraints that Mouse understands.
538 =item B<subtype 'Name' => as 'Parent' => where { } ...>
540 =item B<subtype as 'Parent' => where { } ...>
542 =item B<class_type ($class, ?$options)>
544 =item B<role_type ($role, ?$options)>
546 =item B<enum (\@values)>
552 Much of this documentation was taken from L<Moose::Util::TypeConstraints>