1 package Mouse::Util::TypeConstraints;
7 use Scalar::Util qw/blessed looks_like_number openhandle/;
8 use Mouse::Meta::TypeConstraint;
11 as where message from via type subtype coerce class_type role_type enum
24 return(where => $_[0])
27 return(message => $_[0])
34 no warnings 'uninitialized';
39 !defined($_[0]) || $_[0] eq "" || "$_[0]" eq '1' || "$_[0]" eq '0'
41 Undef => sub { !defined($_[0]) },
42 Defined => sub { defined($_[0]) },
43 Value => sub { defined($_[0]) && !ref($_[0]) },
44 Num => sub { !ref($_[0]) && looks_like_number($_[0]) },
45 Int => sub { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ },
46 Str => sub { defined($_[0]) && !ref($_[0]) },
47 ClassName => sub { Mouse::is_class_loaded($_[0]) },
48 Ref => sub { ref($_[0]) },
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' },
58 ref($_[0]) eq 'GLOB' && openhandle($_[0])
60 blessed($_[0]) && $_[0]->isa("IO::Handle")
63 Object => sub { blessed($_[0]) && blessed($_[0]) ne 'Regexp' },
65 while (my ($name, $code) = each %TYPE) {
66 $TYPE{$name} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $name );
69 sub optimized_constraints { \%TYPE }
70 my @TYPE_KEYS = keys %TYPE;
71 sub list_all_builtin_type_constraints { @TYPE_KEYS }
73 @TYPE_SOURCE{@TYPE_KEYS} = (__PACKAGE__) x @TYPE_KEYS;
78 my($name, %conf) = @_;
80 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
81 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
83 my $constraint = $conf{where} || do {
84 my $as = delete $conf{as} || 'Any';
85 if (! exists $TYPE{$as}) {
86 $TYPE{$as} = _build_type_constraint($as);
91 $TYPE_SOURCE{$name} = $pkg;
92 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
94 _compiled_type_constraint => sub {
96 if (ref $constraint eq 'CODE') {
99 $constraint->check($_[0])
111 if(@_ % 2){ # odd number of arguments
117 $name = $conf{name} || '__ANON__';
120 if ($TYPE{$name} && $TYPE_SOURCE{$name} ne $pkg) {
121 Carp::croak "The type constraint '$name' has already been created in $TYPE_SOURCE{$name} and cannot be created again in $pkg";
123 my $constraint = delete $conf{where};
124 my $as_constraint = find_or_create_isa_type_constraint(delete $conf{as} || 'Any');
126 $TYPE_SOURCE{$name} = $pkg;
127 $TYPE{$name} = Mouse::Meta::TypeConstraint->new(
129 _compiled_type_constraint => (
133 $as_constraint->check($_[0]) && $constraint->($_[0])
137 $as_constraint->check($_[0]);
149 Carp::croak "Cannot find type '$name', perhaps you forgot to load it."
152 unless ($COERCE{$name}) {
154 $COERCE_KEYS{$name} = [];
157 while (my($type, $code) = splice @_, 0, 2) {
158 Carp::croak "A coercion action already exists for '$type'"
159 if $COERCE{$name}->{$type};
161 if (! $TYPE{$type}) {
162 # looks parameterized
163 if ($type =~ /^[^\[]+\[.+\]$/) {
164 $TYPE{$type} = _build_type_constraint($type);
166 Carp::croak "Could not find the type constraint ($type) to coerce from"
170 push @{ $COERCE_KEYS{$name} }, $type;
171 $COERCE{$name}->{$type} = $code;
177 my($name, $conf) = @_;
178 if ($conf && $conf->{class}) {
179 # No, you're using this wrong
180 warn "class_type() should be class_type(ClassName). Perhaps you're looking for subtype $name => as '$conf->{class}'?";
181 subtype($name, as => $conf->{class});
184 $name => where => sub { $_->isa($name) }
190 my($name, $conf) = @_;
191 my $role = $conf->{role};
193 $name => where => sub {
194 return unless defined $_ && ref($_) && $_->isa('Mouse::Object');
195 $_->meta->does_role($role);
200 # this is an original method for Mouse
201 sub typecast_constraints {
202 my($class, $pkg, $types, $value) = @_;
203 Carp::croak("wrong arguments count") unless @_==4;
206 for my $type ( split /\|/, $types ) {
207 next unless $COERCE{$type};
208 for my $coerce_type (@{ $COERCE_KEYS{$type}}) {
210 next unless $TYPE{$coerce_type}->check($value);
212 $_ = $COERCE{$type}->{$coerce_type}->($value);
213 return $_ if $types->check($_);
221 # enum ['small', 'medium', 'large']
222 if (ref($_[0]) eq 'ARRAY') {
223 my @elements = @{ shift @_ };
225 my $name = 'Mouse::Util::TypeConstaints::Enum::Serial::'
227 enum($name, @elements);
231 # enum size => 'small', 'medium', 'large'
233 my %is_valid = map { $_ => 1 } @_;
236 $name => where => sub { $is_valid{$_} }
240 sub _build_type_constraint {
245 if ($spec =~ /^([^\[]+)\[(.+)\]$/) {
250 if ($constraint eq 'Maybe') {
251 $parent = _build_type_constraint('Undef');
253 $parent = _build_type_constraint($constraint);
255 my $child = _build_type_constraint($param);
256 if ($constraint eq 'ArrayRef') {
258 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
260 " if (\$parent->check(\$_[0])) {\n" .
261 " foreach my \$e (\@{\$_[0]}) {\n" .
262 " return () unless \$child->check(\$e);\n" .
269 $code = eval $code_str or Carp::confess("Failed to generate inline type constraint: $@");
270 } elsif ($constraint eq 'HashRef') {
272 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
274 " if (\$parent->check(\$_[0])) {\n" .
275 " foreach my \$e (values \%{\$_[0]}) {\n" .
276 " return () unless \$child->check(\$e);\n" .
283 $code = eval $code_str or Carp::confess($@);
284 } elsif ($constraint eq 'Maybe') {
286 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
288 " return \$child->check(\$_[0]) || \$parent->check(\$_[0]);\n" .
291 $code = eval $code_str or Carp::confess($@);
293 Carp::confess("Support for parameterized types other than Maybe, ArrayRef or HashRef is not implemented yet");
295 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
297 $code = $TYPE{ $spec };
299 # is $spec a known role? If so, constrain with 'does' instead of 'isa'
300 require Mouse::Meta::Role;
301 my $check = Mouse::Meta::Role->_metaclass_cache($spec)?
304 "#line " . __LINE__ . ' "' . __FILE__ . "\"\n" .
306 " Scalar::Util::blessed(\$_[0]) && \$_[0]->$check('$spec');\n" .
309 $code = eval $code_str or Carp::confess($@);
310 $TYPE{$spec} = Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
313 return Mouse::Meta::TypeConstraint->new( _compiled_type_constraint => $code, name => $spec );
316 sub find_type_constraint {
317 my $type_constraint = shift;
318 return $TYPE{$type_constraint};
321 sub find_or_create_isa_type_constraint {
322 my $type_constraint = shift;
324 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)")
325 if $type_constraint =~ /\A ( [^\[]+ ) \[\.+\] \z/xms &&
333 $type_constraint =~ s/\s+//g;
335 $code = $TYPE{$type_constraint};
337 my @type_constraints = split /\|/, $type_constraint;
338 if (@type_constraints == 1) {
339 $code = $TYPE{$type_constraints[0]} ||
340 _build_type_constraint($type_constraints[0]);
342 my @code_list = map {
343 $TYPE{$_} || _build_type_constraint($_)
345 $code = Mouse::Meta::TypeConstraint->new(
346 _compiled_type_constraint => sub {
348 for my $code (@code_list) {
349 return 1 if $code->check($_[0]);
353 name => $type_constraint,
366 Mouse::Util::TypeConstraints - Type constraint system for Mouse
370 use Mouse::Util::TypeConstraints;
376 subtype 'NaturalLessThanTen'
379 => message { "This number ($_) is not less than ten!" };
385 enum 'RGBColors' => qw(red green blue);
387 no Mouse::Util::TypeConstraints;
391 This module provides Mouse with the ability to create custom type
392 constraints to be used in attribute definition.
394 =head2 Important Caveat
396 This is B<NOT> a type system for Perl 5. These are type constraints,
397 and they are not used by Mouse unless you tell it to. No type
398 inference is performed, expressions are not typed, etc. etc. etc.
400 A type constraint is at heart a small "check if a value is valid"
401 function. A constraint can be associated with an attribute. This
402 simplifies parameter validation, and makes your code clearer to read,
403 because you can refer to constraints by name.
405 =head2 Slightly Less Important Caveat
407 It is B<always> a good idea to quote your type names.
409 This prevents Perl from trying to execute the call as an indirect
410 object call. This can be an issue when you have a subtype with the
411 same name as a valid class.
415 subtype DateTime => as Object => where { $_->isa('DateTime') };
417 will I<just work>, while this:
420 subtype DateTime => as Object => where { $_->isa('DateTime') };
422 will fail silently and cause many headaches. The simple way to solve
423 this, as well as future proof your subtypes from classes which have
424 yet to have been created, is to quote the type name:
427 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
429 =head2 Default Type Constraints
431 This module also provides a simple hierarchy for Perl 5 types, here is
432 that hierarchy represented visually.
457 B<NOTE:> Any type followed by a type parameter C<[`a]> can be
458 parameterized, this means you can say:
460 ArrayRef[Int] # an array of integers
461 HashRef[CodeRef] # a hash of str to CODE ref mappings
462 Maybe[Str] # value may be a string, may be undefined
464 If Mouse finds a name in brackets that it does not recognize as an
465 existing type, it assumes that this is a class name, for example
466 C<ArrayRef[DateTime]>.
468 B<NOTE:> Unless you parameterize a type, then it is invalid to include
469 the square brackets. I.e. C<ArrayRef[]> will be treated as a new type
470 name, I<not> as a parameterization of C<ArrayRef>.
472 B<NOTE:> The C<Undef> type constraint for the most part works
473 correctly now, but edge cases may still exist, please use it
476 B<NOTE:> The C<ClassName> type constraint does a complex package
477 existence check. This means that your class B<must> be loaded for this
478 type constraint to pass.
480 B<NOTE:> The C<RoleName> constraint checks a string is a I<package
481 name> which is a role, like C<'MyApp::Role::Comparable'>. The C<Role>
482 constraint checks that an I<object does> the named role.
484 =head2 Type Constraint Naming
486 Type name declared via this module can only contain alphanumeric
487 characters, colons (:), and periods (.).
489 Since the types created by this module are global, it is suggested
490 that you namespace your types just as you would namespace your
491 modules. So instead of creating a I<Color> type for your
492 B<My::Graphics> module, you would call the type
493 I<My::Graphics::Types::Color> instead.
495 =head2 Use with Other Constraint Modules
497 This module can play nicely with other constraint modules with some
498 slight tweaking. The C<where> clause in types is expected to be a
499 C<CODE> reference which checks it's first argument and returns a
500 boolean. Since most constraint modules work in a similar way, it
501 should be simple to adapt them to work with Mouse.
503 For instance, this is how you could use it with
504 L<Declare::Constraints::Simple> to declare a completely new type.
506 type 'HashOfArrayOfObjects',
510 -values => IsArrayRef(IsObject)
514 Here is an example of using L<Test::Deep> and it's non-test
515 related C<eq_deeply> function.
517 type 'ArrayOfHashOfBarsAndRandomNumbers'
520 array_each(subhashof({
522 random_number => ignore()
528 =head2 optimized_constraints -> HashRef[CODE]
530 Returns the simple type constraints that Mouse understands.
536 =item B<subtype 'Name' => as 'Parent' => where { } ...>
538 =item B<subtype as 'Parent' => where { } ...>
540 =item B<class_type ($class, ?$options)>
542 =item B<role_type ($role, ?$options)>
544 =item B<enum (\@values)>
550 Much of this documentation was taken from L<Moose::Util::TypeConstraints>