for mst and castaway
[gitmo/Moose.git] / lib / Moose / Util / TypeConstraints.pm
CommitLineData
a15dff8d 1
2package Moose::Util::TypeConstraints;
3
4use strict;
5use warnings;
6
e90c03d0 7use Carp 'confess';
86629f93 8use Scalar::Util 'blessed', 'reftype';
571dd39f 9use B 'svref_2object';
10use Sub::Exporter;
a15dff8d 11
c899258b 12our $VERSION = '0.12';
d44714be 13our $AUTHORITY = 'cpan:STEVAN';
a15dff8d 14
8c4acc60 15# Prototyped subs must be predeclared because we have a circular dependency
16# with Moose::Meta::Attribute et. al. so in case of us being use'd first the
17# predeclaration ensures the prototypes are in scope when consumers are
18# compiled
19
20sub find_type_constraint ($);
21sub _create_type_constraint ($$$;$$);
22sub _install_type_coercions ($$);
23sub create_type_constraint_union (@);
24sub type ($$;$$);
25sub subtype ($$;$$$);
26sub coerce ($@);
27sub as ($);
28sub from ($);
29sub where (&);
30sub via (&);
31sub message (&);
32sub optimize_as (&);
33sub enum ($;@);
34
4e036ee4 35use Moose::Meta::TypeConstraint;
2ca63f5d 36use Moose::Meta::TypeCoercion;
4e036ee4 37
571dd39f 38my @exports = qw/
8ecb1fa0 39 type subtype as where message optimize_as
571dd39f 40 coerce from via
41 enum
42 find_type_constraint
43/;
44
45Sub::Exporter::setup_exporter({
46 exports => \@exports,
47 groups => { default => [':all'] }
48});
49
50sub unimport {
51 no strict 'refs';
52 my $class = caller();
53 # loop through the exports ...
54 foreach my $name (@exports) {
55 # if we find one ...
56 if (defined &{$class . '::' . $name}) {
57 my $keyword = \&{$class . '::' . $name};
58
59 # make sure it is from Moose
60 my $pkg_name = eval { svref_2object($keyword)->GV->STASH->NAME };
61 next if $@;
62 next if $pkg_name ne 'Moose::Util::TypeConstraints';
63
64 # and if it is from Moose then undef the slot
65 delete ${$class . '::'}{$name};
66 }
2c0cbef7 67 }
571dd39f 68}
a15dff8d 69
182134e8 70{
71 my %TYPES;
2c0cbef7 72 sub find_type_constraint ($) {
446e850f 73 return $TYPES{$_[0]}->[1]
74 if exists $TYPES{$_[0]};
75 return;
76 }
77
78 sub _dump_type_constraints {
79 require Data::Dumper;
256903b6 80 Data::Dumper::Dumper(\%TYPES);
446e850f 81 }
82
8ecb1fa0 83 sub _create_type_constraint ($$$;$$) {
84 my $name = shift;
85 my $parent = shift;
86629f93 86 my $check = shift || sub { 1 };
8ecb1fa0 87
88 my ($message, $optimized);
89 for (@_) {
815ec671 90 $message = $_->{message} if exists $_->{message};
8ecb1fa0 91 $optimized = $_->{optimized} if exists $_->{optimized};
92 }
587ae0d2 93
a05c504e 94 my $pkg_defined_in = scalar(caller(0));
95
0e6614c3 96 ($TYPES{$name}->[0] eq $pkg_defined_in)
a05c504e 97 || confess ("The type constraint '$name' has already been created in "
98 . $TYPES{$name}->[0] . " and cannot be created again in "
99 . $pkg_defined_in)
100 if defined $name && exists $TYPES{$name};
101
0e6614c3 102 $parent = find_type_constraint($parent) if defined $parent;
a27aa600 103 my $constraint = Moose::Meta::TypeConstraint->new(
104 name => $name || '__ANON__',
66811d63 105 parent => $parent,
76d37e5a 106 constraint => $check,
107 message => $message,
8ecb1fa0 108 optimized => $optimized,
4e036ee4 109 );
0e6614c3 110 $TYPES{$name} = [ $pkg_defined_in, $constraint ] if defined $name;
a27aa600 111 return $constraint;
182134e8 112 }
182134e8 113
2c0cbef7 114 sub _install_type_coercions ($$) {
a27aa600 115 my ($type_name, $coercion_map) = @_;
0e6614c3 116 my $type = find_type_constraint($type_name);
4e036ee4 117 (!$type->has_coercion)
d46a48f3 118 || confess "The type coercion for '$type_name' has already been registered";
a27aa600 119 my $type_coercion = Moose::Meta::TypeCoercion->new(
120 type_coercion_map => $coercion_map,
121 type_constraint => $type
122 );
123 $type->coercion($type_coercion);
182134e8 124 }
66811d63 125
2c0cbef7 126 sub create_type_constraint_union (@) {
c07af9d2 127 my (@type_constraint_names) = @_;
128 return Moose::Meta::TypeConstraint->union(
129 map {
130 find_type_constraint($_)
131 } @type_constraint_names
132 );
133 }
134
429ccc11 135 sub export_type_constraints_as_functions {
66811d63 136 my $pkg = caller();
137 no strict 'refs';
138 foreach my $constraint (keys %TYPES) {
0e6614c3 139 *{"${pkg}::${constraint}"} = find_type_constraint($constraint)->_compiled_type_constraint;
66811d63 140 }
429ccc11 141 }
142
143 *Moose::Util::TypeConstraints::export_type_contstraints_as_functions = \&export_type_constraints_as_functions;
b1e01e3c 144
145 sub list_all_type_constraints { keys %TYPES }
182134e8 146}
a15dff8d 147
7c13858b 148# type constructors
a15dff8d 149
815ec671 150sub type ($$;$$) {
1b7df21f 151 splice(@_, 1, 0, undef);
152 goto &_create_type_constraint;
a15dff8d 153}
154
8ecb1fa0 155sub subtype ($$;$$$) {
86629f93 156 # NOTE:
157 # this adds an undef for the name
158 # if this is an anon-subtype:
159 # subtype(Num => where { $_ % 2 == 0 }) # anon 'even' subtype
160 # but if the last arg is not a code
161 # ref then it is a subtype alias:
162 # subtype(MyNumbers => as Num); # now MyNumbers is the same as Num
163 # ... yeah I know it's ugly code
164 # - SL
165 unshift @_ => undef if scalar @_ <= 2 && (reftype($_[1]) || '') eq 'CODE';
2c0cbef7 166 goto &_create_type_constraint;
a15dff8d 167}
168
4b598ea3 169sub coerce ($@) {
66811d63 170 my ($type_name, @coercion_map) = @_;
7c13858b 171 _install_type_coercions($type_name, \@coercion_map);
182134e8 172}
173
76d37e5a 174sub as ($) { $_[0] }
175sub from ($) { $_[0] }
176sub where (&) { $_[0] }
177sub via (&) { $_[0] }
8ecb1fa0 178
179sub message (&) { +{ message => $_[0] } }
180sub optimize_as (&) { +{ optimized => $_[0] } }
a15dff8d 181
2c0cbef7 182sub enum ($;@) {
fcec2383 183 my ($type_name, @values) = @_;
2c0cbef7 184 (scalar @values >= 2)
185 || confess "You must have at least two values to enumerate through";
fcec2383 186 my $regexp = join '|' => @values;
187 _create_type_constraint(
188 $type_name,
189 'Str',
190 sub { qr/^$regexp$/i }
191 );
192}
193
a15dff8d 194# define some basic types
195
f65cb534 196type 'Any' => where { 1 }; # meta-type including all
197type 'Item' => where { 1 }; # base-type
a15dff8d 198
f65cb534 199subtype 'Undef' => as 'Item' => where { !defined($_) };
200subtype 'Defined' => as 'Item' => where { defined($_) };
a15dff8d 201
8ecb1fa0 202subtype 'Bool'
203 => as 'Item'
204 => where { !defined($_) || $_ eq "" || "$_" eq '1' || "$_" eq '0' };
5a4c5493 205
8ecb1fa0 206subtype 'Value'
207 => as 'Defined'
208 => where { !ref($_) }
209 => optimize_as { defined($_[0]) && !ref($_[0]) };
210
211subtype 'Ref'
212 => as 'Defined'
213 => where { ref($_) }
214 => optimize_as { ref($_[0]) };
215
216subtype 'Str'
217 => as 'Value'
218 => where { 1 }
219 => optimize_as { defined($_[0]) && !ref($_[0]) };
220
221subtype 'Num'
222 => as 'Value'
223 => where { Scalar::Util::looks_like_number($_) }
224 => optimize_as { !ref($_[0]) && Scalar::Util::looks_like_number($_[0]) };
225
226subtype 'Int'
227 => as 'Num'
228 => where { "$_" =~ /^-?[0-9]+$/ }
229 => optimize_as { defined($_[0]) && !ref($_[0]) && $_[0] =~ /^-?[0-9]+$/ };
230
231subtype 'ScalarRef' => as 'Ref' => where { ref($_) eq 'SCALAR' } => optimize_as { ref($_[0]) eq 'SCALAR' };
232subtype 'ArrayRef' => as 'Ref' => where { ref($_) eq 'ARRAY' } => optimize_as { ref($_[0]) eq 'ARRAY' };
233subtype 'HashRef' => as 'Ref' => where { ref($_) eq 'HASH' } => optimize_as { ref($_[0]) eq 'HASH' };
234subtype 'CodeRef' => as 'Ref' => where { ref($_) eq 'CODE' } => optimize_as { ref($_[0]) eq 'CODE' };
235subtype 'RegexpRef' => as 'Ref' => where { ref($_) eq 'Regexp' } => optimize_as { ref($_[0]) eq 'Regexp' };
236subtype 'GlobRef' => as 'Ref' => where { ref($_) eq 'GLOB' } => optimize_as { ref($_[0]) eq 'GLOB' };
a15dff8d 237
0a5bd159 238# NOTE:
239# scalar filehandles are GLOB refs,
240# but a GLOB ref is not always a filehandle
8ecb1fa0 241subtype 'FileHandle'
242 => as 'GlobRef'
243 => where { Scalar::Util::openhandle($_) }
244 => optimize_as { ref($_[0]) eq 'GLOB' && Scalar::Util::openhandle($_[0]) };
0a5bd159 245
a15dff8d 246# NOTE:
247# blessed(qr/.../) returns true,.. how odd
8ecb1fa0 248subtype 'Object'
249 => as 'Ref'
250 => where { blessed($_) && blessed($_) ne 'Regexp' }
251 => optimize_as { blessed($_[0]) && blessed($_[0]) ne 'Regexp' };
a15dff8d 252
8ecb1fa0 253subtype 'Role'
254 => as 'Object'
255 => where { $_->can('does') }
256 => optimize_as { blessed($_[0]) && $_[0]->can('does') };
9af1d28b 257
258subtype 'ClassName'
259 => as 'Str'
260 => where { eval { $_->isa('UNIVERSAL') } }
261 => optimize_as { !ref($_[0]) && eval { $_[0]->isa('UNIVERSAL') } };
02a0fb52 262
943596a6 263{
264 my @BUILTINS = list_all_type_constraints();
265 sub list_all_builtin_type_constraints { @BUILTINS }
266}
267
a15dff8d 2681;
269
270__END__
271
272=pod
273
274=head1 NAME
275
e522431d 276Moose::Util::TypeConstraints - Type constraint system for Moose
a15dff8d 277
278=head1 SYNOPSIS
279
280 use Moose::Util::TypeConstraints;
281
2c0cbef7 282 type 'Num' => where { Scalar::Util::looks_like_number($_) };
a15dff8d 283
2c0cbef7 284 subtype 'Natural'
285 => as 'Num'
a15dff8d 286 => where { $_ > 0 };
287
2c0cbef7 288 subtype 'NaturalLessThanTen'
289 => as 'Natural'
79592a54 290 => where { $_ < 10 }
291 => message { "This number ($_) is not less than ten!" };
6b8bd8d3 292
2c0cbef7 293 coerce 'Num'
294 => from 'Str'
d6e2d9a1 295 => via { 0+$_ };
98aae381 296
2c0cbef7 297 enum 'RGBColors' => qw(red green blue);
a15dff8d 298
299=head1 DESCRIPTION
300
d44714be 301This module provides Moose with the ability to create custom type
302contraints to be used in attribute definition.
e522431d 303
6ba6d68c 304=head2 Important Caveat
305
306This is B<NOT> a type system for Perl 5. These are type constraints,
307and they are not used by Moose unless you tell it to. No type
308inference is performed, expression are not typed, etc. etc. etc.
309
310This is simply a means of creating small constraint functions which
a7d0cd00 311can be used to simplify your own type-checking code.
6ba6d68c 312
2c0cbef7 313=head2 Slightly Less Important Caveat
314
315It is almost always a good idea to quote your type and subtype names.
43d599e5 316This is to prevent perl from trying to execute the call as an indirect
2c0cbef7 317object call. This issue only seems to come up when you have a subtype
318the same name as a valid class, but when the issue does arise it tends
319to be quite annoying to debug.
320
321So for instance, this:
322
323 subtype DateTime => as Object => where { $_->isa('DateTime') };
324
325will I<Just Work>, while this:
326
327 use DateTime;
328 subtype DateTime => as Object => where { $_->isa('DateTime') };
329
330will fail silently and cause many headaches. The simple way to solve
331this, as well as future proof your subtypes from classes which have
332yet to have been created yet, is to simply do this:
333
334 use DateTime;
d44714be 335 subtype 'DateTime' => as 'Object' => where { $_->isa('DateTime') };
2c0cbef7 336
6ba6d68c 337=head2 Default Type Constraints
e522431d 338
e522431d 339This module also provides a simple hierarchy for Perl 5 types, this
340could probably use some work, but it works for me at the moment.
341
342 Any
f65cb534 343 Item
5a4c5493 344 Bool
f65cb534 345 Undef
346 Defined
5a4c5493 347 Value
348 Num
349 Int
350 Str
9af1d28b 351 ClassName
5a4c5493 352 Ref
353 ScalarRef
451c8248 354 ArrayRef
355 HashRef
5a4c5493 356 CodeRef
357 RegexpRef
3f7376b0 358 GlobRef
0a5bd159 359 FileHandle
5a4c5493 360 Object
361 Role
e522431d 362
6ba6d68c 363Suggestions for improvement are welcome.
2c0cbef7 364
365B<NOTE:> The C<Undef> type constraint does not work correctly
366in every occasion, please use it sparringly.
703e92fb 367
9af1d28b 368B<NOTE:> The C<ClassName> type constraint is simply a subtype
369of string which responds true to C<isa('UNIVERSAL')>. This means
370that your class B<must> be loaded for this type constraint to
371pass. I know this is not ideal for all, but it is a saner
372restriction then most others.
373
703e92fb 374=head2 Use with Other Constraint Modules
375
376This module should play fairly nicely with other constraint
377modules with only some slight tweaking. The C<where> clause
378in types is expected to be a C<CODE> reference which checks
379it's first argument and returns a bool. Since most constraint
380modules work in a similar way, it should be simple to adapt
381them to work with Moose.
382
383For instance, this is how you could use it with
66c57662 384L<Declare::Constraints::Simple> to declare a completely new type.
703e92fb 385
386 type 'HashOfArrayOfObjects'
387 => IsHashRef(
388 -keys => HasLength,
389 -values => IsArrayRef( IsObject ));
390
391For more examples see the F<t/204_example_w_DCS.t> test file.
392
393Here is an example of using L<Test::Deep> and it's non-test
394related C<eq_deeply> function.
395
396 type 'ArrayOfHashOfBarsAndRandomNumbers'
397 => where {
398 eq_deeply($_,
399 array_each(subhashof({
400 bar => isa('Bar'),
401 random_number => ignore()
402 })))
403 };
404
405For a complete example see the F<t/205_example_w_TestDeep.t>
406test file.
e522431d 407
a15dff8d 408=head1 FUNCTIONS
409
182134e8 410=head2 Type Constraint Registry
411
412=over 4
413
414=item B<find_type_constraint ($type_name)>
415
6ba6d68c 416This function can be used to locate a specific type constraint
417meta-object. What you do with it from there is up to you :)
182134e8 418
c07af9d2 419=item B<create_type_constraint_union (@type_constraint_names)>
420
421Given a list of C<@type_constraint_names>, this will return a
422B<Moose::Meta::TypeConstraint::Union> instance.
423
429ccc11 424=item B<export_type_constraints_as_functions>
182134e8 425
6ba6d68c 426This will export all the current type constraints as functions
427into the caller's namespace. Right now, this is mostly used for
428testing, but it might prove useful to others.
429
429ccc11 430=item B<export_type_contstraints_as_functions>
431
432Alias for the above function.
433
b1e01e3c 434=item B<list_all_type_constraints>
435
436This will return a list of type constraint names, you can then
437fetch them using C<find_type_constraint ($type_name)> if you
438want to.
439
943596a6 440=item B<list_all_builtin_type_constraints>
441
442This will return a list of builtin type constraints, meaning,
443those which are defined in this module. See the section
444labeled L<Default Type Constraints> for a complete list.
445
182134e8 446=back
447
a15dff8d 448=head2 Type Constraint Constructors
449
6ba6d68c 450The following functions are used to create type constraints.
451They will then register the type constraints in a global store
452where Moose can get to them if it needs to.
a15dff8d 453
25f2c3fc 454See the L<SYNOPSIS> for an example of how to use these.
a15dff8d 455
6ba6d68c 456=over 4
a15dff8d 457
6ba6d68c 458=item B<type ($name, $where_clause)>
a15dff8d 459
6ba6d68c 460This creates a base type, which has no parent.
a15dff8d 461
79592a54 462=item B<subtype ($name, $parent, $where_clause, ?$message)>
182134e8 463
6ba6d68c 464This creates a named subtype.
d6e2d9a1 465
79592a54 466=item B<subtype ($parent, $where_clause, ?$message)>
182134e8 467
6ba6d68c 468This creates an unnamed subtype and will return the type
469constraint meta-object, which will be an instance of
470L<Moose::Meta::TypeConstraint>.
a15dff8d 471
fcec2383 472=item B<enum ($name, @values)>
473
2c0cbef7 474This will create a basic subtype for a given set of strings.
475The resulting constraint will be a subtype of C<Str> and
476will match any of the items in C<@values>. See the L<SYNOPSIS>
477for a simple example.
478
479B<NOTE:> This is not a true proper enum type, it is simple
480a convient constraint builder.
481
6ba6d68c 482=item B<as>
a15dff8d 483
6ba6d68c 484This is just sugar for the type constraint construction syntax.
a15dff8d 485
6ba6d68c 486=item B<where>
a15dff8d 487
6ba6d68c 488This is just sugar for the type constraint construction syntax.
76d37e5a 489
490=item B<message>
491
492This is just sugar for the type constraint construction syntax.
a15dff8d 493
8ecb1fa0 494=item B<optimize_as>
495
d44714be 496This can be used to define a "hand optimized" version of your
497type constraint which can be used to avoid traversing a subtype
498constraint heirarchy.
499
500B<NOTE:> You should only use this if you know what you are doing,
501all the built in types use this, so your subtypes (assuming they
502are shallow) will not likely need to use this.
503
6ba6d68c 504=back
a15dff8d 505
6ba6d68c 506=head2 Type Coercion Constructors
a15dff8d 507
587ae0d2 508Type constraints can also contain type coercions as well. If you
509ask your accessor too coerce, the Moose will run the type-coercion
510code first, followed by the type constraint check. This feature
511should be used carefully as it is very powerful and could easily
512take off a limb if you are not careful.
a15dff8d 513
25f2c3fc 514See the L<SYNOPSIS> for an example of how to use these.
a15dff8d 515
6ba6d68c 516=over 4
a15dff8d 517
6ba6d68c 518=item B<coerce>
a15dff8d 519
6ba6d68c 520=item B<from>
a15dff8d 521
6ba6d68c 522This is just sugar for the type coercion construction syntax.
523
524=item B<via>
a15dff8d 525
6ba6d68c 526This is just sugar for the type coercion construction syntax.
a15dff8d 527
528=back
529
571dd39f 530=head2 Namespace Management
531
532=over 4
533
534=item B<unimport>
535
536This will remove all the type constraint keywords from the
537calling class namespace.
538
539=back
540
a15dff8d 541=head1 BUGS
542
543All complex software has bugs lurking in it, and this module is no
544exception. If you find a bug please either email me, or add the bug
545to cpan-RT.
546
a15dff8d 547=head1 AUTHOR
548
549Stevan Little E<lt>stevan@iinteractive.comE<gt>
550
551=head1 COPYRIGHT AND LICENSE
552
b77fdbed 553Copyright 2006, 2007 by Infinity Interactive, Inc.
a15dff8d 554
555L<http://www.iinteractive.com>
556
557This library is free software; you can redistribute it and/or modify
558it under the same terms as Perl itself.
559
81dc201f 560=cut