Add assert_valid() to Meta::TypeConstraint
[gitmo/Mouse.git] / lib / Mouse / Meta / TypeConstraint.pm
CommitLineData
684db121 1package Mouse::Meta::TypeConstraint;
bc69ee88 2use Mouse::Util qw(:meta); # enables strict and warnings
9c85e9dc 3
f5ee065f 4use overload
c489a477 5 'bool' => sub { 1 }, # always true
6
93540011 7 '""' => sub { $_[0]->name }, # stringify to tc name
8adc0b23 8
9 '|' => sub { # or-combination
10 require Mouse::Util::TypeConstraints;
11 return Mouse::Util::TypeConstraints::find_or_parse_type_constraint(
12 "$_[0] | $_[1]",
13 );
14 },
15
f5ee065f 16 fallback => 1;
684db121 17
ca352580 18use Carp ();
6d28c5cf 19
684db121 20sub new {
f5ee065f 21 my($class, %args) = @_;
22
23 $args{name} = '__ANON__' if !defined $args{name};
684db121 24
3b89ea91 25 my $check = delete $args{optimized};
26
3b89ea91 27 if($check){
28 $args{hand_optimized_type_constraint} = $check;
29 $args{compiled_type_constraint} = $check;
30 }
31
32 $check = $args{constraint};
f5ee065f 33
f5ee065f 34 if(defined($check) && ref($check) ne 'CODE'){
ca352580 35 Carp::confess("Constraint for $args{name} is not a CODE reference");
f5ee065f 36 }
37
3b89ea91 38 $args{package_defined_in} ||= caller;
39
f5ee065f 40 my $self = bless \%args, $class;
3b89ea91 41 $self->compile_type_constraint() if !$self->{hand_optimized_type_constraint};
f5ee065f 42
536e3c33 43 $self->_compile_union_type_coercion() if $self->{type_constraints};
f5ee065f 44 return $self;
45}
46
47sub create_child_type{
48 my $self = shift;
e98220ab 49 # XXX: FIXME
50 return ref($self)->new(
3b89ea91 51 # a child inherits its parent's attributes
52 %{$self},
53
54 # but does not inherit 'compiled_type_constraint' and 'hand_optimized_type_constraint'
55 compiled_type_constraint => undef,
56 hand_optimized_type_constraint => undef,
57
58 # and is given child-specific args, of course.
59 @_,
60
61 # and its parent
62 parent => $self,
e98220ab 63 );
684db121 64}
65
4c99ea95 66sub name;
67sub parent;
68sub message;
69sub has_coercion;
70sub _compiled_type_constraint;
71sub _compiled_type_coercion;
72
73sub compile_type_constraint;
74
ffbbf459 75sub _add_type_coercions{
76 my $self = shift;
77
78 my $coercions = ($self->{_coercion_map} ||= []);
79 my %has = map{ $_->[0] => undef } @{$coercions};
80
81 for(my $i = 0; $i < @_; $i++){
82 my $from = $_[ $i];
83 my $action = $_[++$i];
84
85 if(exists $has{$from}){
ca352580 86 Carp::confess("A coercion action already exists for '$from'");
ffbbf459 87 }
88
89 my $type = Mouse::Util::TypeConstraints::find_or_parse_type_constraint($from)
ca352580 90 or Carp::confess("Could not find the type constraint ($from) to coerce from");
ffbbf459 91
92 push @{$coercions}, [ $type => $action ];
93 }
94
95 # compile
96 if(exists $self->{type_constraints}){ # union type
ca352580 97 Carp::confess("Cannot add additional type coercions to Union types");
ffbbf459 98 }
99 else{
536e3c33 100 $self->_compile_type_coercion();
101 }
102 return;
103}
104
105sub _compile_type_coercion {
106 my($self) = @_;
107
108 my @coercions = @{$self->{_coercion_map}};
109
110 $self->{_compiled_type_coercion} = sub {
111 my($thing) = @_;
112 foreach my $pair (@coercions) {
113 #my ($constraint, $converter) = @$pair;
114 if ($pair->[0]->check($thing)) {
115 local $_ = $thing;
116 return $pair->[1]->($thing);
117 }
118 }
119 return $thing;
120 };
121 return;
122}
123
124sub _compile_union_type_coercion {
125 my($self) = @_;
126
127 my @coercions;
128 foreach my $type(@{$self->{type_constraints}}){
129 if($type->has_coercion){
130 push @coercions, $type;
131 }
132 }
133 if(@coercions){
ffbbf459 134 $self->{_compiled_type_coercion} = sub {
536e3c33 135 my($thing) = @_;
136 foreach my $type(@coercions){
137 my $value = $type->coerce($thing);
138 return $value if $self->check($value);
139 }
140 return $thing;
ffbbf459 141 };
142 }
143 return;
144}
145
feb0e21b 146sub check {
147 my $self = shift;
ffbbf459 148 return $self->_compiled_type_constraint->(@_);
149}
150
151sub coerce {
152 my $self = shift;
ffbbf459 153
de0d4152 154 my $coercion = $self->_compiled_type_coercion;
155 if(!$coercion){
156 Carp::confess("Cannot coerce without a type coercion");
157 }
158
ffbbf459 159 return $_[0] if $self->_compiled_type_constraint->(@_);
160
de0d4152 161 return $coercion->(@_);
feb0e21b 162}
163
164sub get_message {
165 my ($self, $value) = @_;
166 if ( my $msg = $self->message ) {
167 local $_ = $value;
168 return $msg->($value);
169 }
170 else {
171 $value = ( defined $value ? overload::StrVal($value) : 'undef' );
172 return "Validation failed for '$self' failed with value $value";
173 }
174}
175
176sub is_a_type_of{
177 my($self, $other) = @_;
178
179 # ->is_a_type_of('__ANON__') is always false
ca352580 180 return 0 if !ref($other) && $other eq '__ANON__';
feb0e21b 181
182 (my $other_name = $other) =~ s/\s+//g;
183
184 return 1 if $self->name eq $other_name;
185
186 if(exists $self->{type_constraints}){ # union
187 foreach my $type(@{$self->{type_constraints}}){
188 return 1 if $type->name eq $other_name;
189 }
190 }
191
192 for(my $parent = $self->parent; defined $parent; $parent = $parent->parent){
193 return 1 if $parent->name eq $other_name;
194 }
195
196 return 0;
197}
198
b4d791ba 199# See also Moose::Meta::TypeConstraint::Parameterizable
200sub parameterize{
201 my($self, $param, $name) = @_;
202
203 if(!ref $param){
204 require Mouse::Util::TypeConstraints;
205 $param = Mouse::Util::TypeConstraints::find_or_create_isa_type_constraint($param);
206 }
207
208 $name ||= sprintf '%s[%s]', $self->name, $param->name;
209
210 my $generator = $self->{constraint_generator}
211 || Carp::confess("The $name constraint cannot be used, because $param doesn't subtype from a parameterizable type");
212
213 return Mouse::Meta::TypeConstraint->new(
fc83f4cf 214 name => $name,
215 parent => $self,
216 type_parameter => $param,
217 constraint => $generator->($param), # must be 'constraint', not 'optimized'
b4d791ba 218
fc83f4cf 219 type => 'Parameterized',
b4d791ba 220 );
221}
feb0e21b 222
78508064 223sub assert_valid {
224 my ($self, $value) = @_;
225
226 if(!$self->_compiled_type_constraint->($value)){
227 Carp::confess($self->get_message($value));
228 }
229 return 1;
230}
231
684db121 2321;
233__END__
234
235=head1 NAME
236
1820fffe 237Mouse::Meta::TypeConstraint - The Mouse Type Constraint metaclass
684db121 238
a25ca8d6 239=head1 VERSION
240
05a12aa4 241This document describes Mouse version 0.50_01
a25ca8d6 242
684db121 243=head1 DESCRIPTION
244
245For the most part, the only time you will ever encounter an
246instance of this class is if you are doing some serious deep
247introspection. This API should not be considered final, but
248it is B<highly unlikely> that this will matter to a regular
249Mouse user.
250
251Don't use this.
252
253=head1 METHODS
254
255=over 4
256
257=item B<new>
258
259=item B<name>
260
261=back
262
1820fffe 263=head1 SEE ALSO
264
265L<Moose::Meta::TypeConstraint>
266
684db121 267=cut
268