performance enhancements
[gitmo/Moose.git] / lib / Moose / Meta / TypeConstraint.pm
CommitLineData
4e036ee4 1
2package Moose::Meta::TypeConstraint;
3
4use strict;
5use warnings;
6use metaclass;
7
c07af9d2 8use Sub::Name 'subname';
9use Carp 'confess';
10use Scalar::Util 'blessed';
66811d63 11
43123819 12our $VERSION = '0.06';
66811d63 13
66811d63 14__PACKAGE__->meta->add_attribute('name' => (reader => 'name' ));
15__PACKAGE__->meta->add_attribute('parent' => (reader => 'parent' ));
16__PACKAGE__->meta->add_attribute('constraint' => (reader => 'constraint'));
76d37e5a 17__PACKAGE__->meta->add_attribute('message' => (
18 accessor => 'message',
19 predicate => 'has_message'
20));
a27aa600 21__PACKAGE__->meta->add_attribute('coercion' => (
22 accessor => 'coercion',
23 predicate => 'has_coercion'
24));
66811d63 25
26# private accessor
27__PACKAGE__->meta->add_attribute('compiled_type_constraint' => (
28 accessor => '_compiled_type_constraint'
29));
30
66811d63 31sub new {
a27aa600 32 my $class = shift;
33 my $self = $class->meta->new_object(@_);
66811d63 34 $self->compile_type_constraint();
35 return $self;
36}
37
0a5bd159 38sub coerce {
39 ((shift)->coercion || confess "Cannot coerce without a type coercion")->coerce(@_)
40}
41
43123819 42sub _collect_all_parents {
43 my $self = shift;
44 my @parents;
45 my $current = $self->parent;
46 while (defined $current) {
47 unshift @parents => $current;
48 $current = $current->parent;
49 }
50 return @parents;
51}
52
451c8248 53sub compile_type_constraint {
a27aa600 54 my $self = shift;
55 my $check = $self->constraint;
66811d63 56 (defined $check)
57 || confess "Could not compile type constraint '" . $self->name . "' because no constraint check";
58 my $parent = $self->parent;
59 if (defined $parent) {
43123819 60 # we have a subtype ...
61 # so we gather all the parents in order
62 # and grab their constraints ...
63 my @parents = map { $_->constraint } $self->_collect_all_parents;
64 # then we compile them to run without
65 # having to recurse as we did before
66811d63 66 $self->_compiled_type_constraint(subname $self->name => sub {
67 local $_ = $_[0];
43123819 68 foreach my $parent (@parents) {
69 return undef unless $parent->($_[0]);
70 }
71 return undef unless $check->($_[0]);
5a4c5493 72 1;
66811d63 73 });
43123819 74
66811d63 75 }
76 else {
a27aa600 77 # we have a type ....
66811d63 78 $self->_compiled_type_constraint(subname $self->name => sub {
79 local $_ = $_[0];
80 return undef unless $check->($_[0]);
5a4c5493 81 1;
66811d63 82 });
83 }
84}
85
a27aa600 86sub check { $_[0]->_compiled_type_constraint->($_[1]) }
4e036ee4 87
76d37e5a 88sub validate {
89 my ($self, $value) = @_;
90 if ($self->_compiled_type_constraint->($value)) {
91 return undef;
92 }
93 else {
94 if ($self->has_message) {
95 local $_ = $value;
96 return $self->message->($value);
97 }
98 else {
451c8248 99 return "Validation failed for '" . $self->name . "' failed";
76d37e5a 100 }
101 }
102}
103
b26e162e 104sub is_a_type_of {
105 my ($self, $type_name) = @_;
106 ($self->name eq $type_name || $self->is_subtype_of($type_name));
107}
108
cce8198b 109sub is_subtype_of {
110 my ($self, $type_name) = @_;
111 my $current = $self;
112 while (my $parent = $current->parent) {
113 return 1 if $parent->name eq $type_name;
114 $current = $parent;
115 }
116 return 0;
117}
118
451c8248 119sub union {
120 my ($class, @type_constraints) = @_;
c07af9d2 121 (scalar @type_constraints >= 2)
122 || confess "You must pass in at least 2 Moose::Meta::TypeConstraint instances to make a union";
123 (blessed($_) && $_->isa('Moose::Meta::TypeConstraint'))
124 || confess "You must pass in only Moose::Meta::TypeConstraint instances to make unions"
125 foreach @type_constraints;
451c8248 126 return Moose::Meta::TypeConstraint::Union->new(
0a5bd159 127 type_constraints => \@type_constraints,
451c8248 128 );
129}
130
131package Moose::Meta::TypeConstraint::Union;
132
133use strict;
134use warnings;
135use metaclass;
136
43123819 137our $VERSION = '0.03';
451c8248 138
139__PACKAGE__->meta->add_attribute('type_constraints' => (
140 accessor => 'type_constraints',
141 default => sub { [] }
142));
143
144sub new {
145 my $class = shift;
146 my $self = $class->meta->new_object(@_);
147 return $self;
148}
149
150sub name { join ' | ' => map { $_->name } @{$_[0]->type_constraints} }
151
c07af9d2 152# NOTE:
153# this should probably never be used
154# but we include it here for completeness
155sub constraint {
156 my $self = shift;
157 sub { $self->check($_[0]) };
158}
159
160# conform to the TypeConstraint API
161sub parent { undef }
c07af9d2 162sub message { undef }
163sub has_message { 0 }
164
0a5bd159 165# FIXME:
166# not sure what this should actually do here
167sub coercion { undef }
168
169# this should probably be memoized
170sub has_coercion {
171 my $self = shift;
172 foreach my $type (@{$self->type_constraints}) {
173 return 1 if $type->has_coercion
174 }
175 return 0;
176}
177
178# NOTE:
179# this feels too simple, and may not always DWIM
180# correctly, especially in the presence of
181# close subtype relationships, however it should
182# work for a fair percentage of the use cases
183sub coerce {
184 my $self = shift;
185 my $value = shift;
186 foreach my $type (@{$self->type_constraints}) {
187 if ($type->has_coercion) {
188 my $temp = $type->coerce($value);
189 return $temp if $self->check($temp);
190 }
191 }
192 return undef;
193}
194
43123819 195sub _compiled_type_constraint {
196 my $self = shift;
197 return sub {
198 my $value = shift;
199 foreach my $type (@{$self->type_constraints}) {
200 return 1 if $type->check($value);
201 }
202 return undef;
203 }
204}
205
451c8248 206sub check {
207 my $self = shift;
208 my $value = shift;
43123819 209 $self->_compiled_type_constraint->($value);
451c8248 210}
211
212sub validate {
213 my $self = shift;
214 my $value = shift;
215 my $message;
216 foreach my $type (@{$self->type_constraints}) {
217 my $err = $type->validate($value);
218 return unless defined $err;
219 $message .= ($message ? ' and ' : '') . $err
220 if defined $err;
221 }
222 return ($message . ' in (' . $self->name . ')') ;
223}
224
94b8bbb8 225sub is_a_type_of {
226 my ($self, $type_name) = @_;
227 foreach my $type (@{$self->type_constraints}) {
228 return 1 if $type->is_a_type_of($type_name);
229 }
230 return 0;
231}
232
233sub is_subtype_of {
234 my ($self, $type_name) = @_;
235 foreach my $type (@{$self->type_constraints}) {
236 return 1 if $type->is_subtype_of($type_name);
237 }
238 return 0;
239}
240
4e036ee4 2411;
242
243__END__
244
245=pod
246
247=head1 NAME
248
6ba6d68c 249Moose::Meta::TypeConstraint - The Moose Type Constraint metaclass
4e036ee4 250
251=head1 DESCRIPTION
252
6ba6d68c 253For the most part, the only time you will ever encounter an
254instance of this class is if you are doing some serious deep
255introspection. This API should not be considered final, but
256it is B<highly unlikely> that this will matter to a regular
257Moose user.
258
259If you wish to use features at this depth, please come to the
260#moose IRC channel on irc.perl.org and we can talk :)
261
4e036ee4 262=head1 METHODS
263
264=over 4
265
266=item B<meta>
267
268=item B<new>
269
b26e162e 270=item B<is_a_type_of ($type_name)>
271
272This checks the current type name, and if it does not match,
273checks if it is a subtype of it.
274
275=item B<is_subtype_of ($type_name)>
cce8198b 276
6ba6d68c 277=item B<compile_type_constraint>
278
0a5bd159 279=item B<coerce ($value)>
280
281This will apply the type-coercion if applicable.
282
76d37e5a 283=item B<check ($value)>
284
285This method will return a true (C<1>) if the C<$value> passes the
286constraint, and false (C<0>) otherwise.
287
288=item B<validate ($value)>
289
290This method is similar to C<check>, but it deals with the error
291message. If the C<$value> passes the constraint, C<undef> will be
292returned. If the C<$value> does B<not> pass the constraint, then
293the C<message> will be used to construct a custom error message.
6ba6d68c 294
4e036ee4 295=item B<name>
296
66811d63 297=item B<parent>
298
66811d63 299=item B<constraint>
300
76d37e5a 301=item B<has_message>
302
303=item B<message>
304
4e036ee4 305=item B<has_coercion>
306
a27aa600 307=item B<coercion>
308
4e036ee4 309=back
310
451c8248 311=over 4
312
313=item B<union (@type_constraints)>
314
315=back
316
4e036ee4 317=head1 BUGS
318
319All complex software has bugs lurking in it, and this module is no
320exception. If you find a bug please either email me, or add the bug
321to cpan-RT.
322
323=head1 AUTHOR
324
325Stevan Little E<lt>stevan@iinteractive.comE<gt>
326
327=head1 COPYRIGHT AND LICENSE
328
329Copyright 2006 by Infinity Interactive, Inc.
330
331L<http://www.iinteractive.com>
332
333This library is free software; you can redistribute it and/or modify
334it under the same terms as Perl itself.
335
336=cut