fixed up test cases
[gitmo/MooseX-Dependent.git] / lib / MooseX / Meta / TypeConstraint / Parameterizable.pm
CommitLineData
3cfd35fd 1package ## Hide from PAUSE
ca01e833 2 MooseX::Meta::TypeConstraint::Parameterizable;
3cfd35fd 3
4use Moose;
5use Moose::Util::TypeConstraints ();
ca01e833 6use MooseX::Meta::TypeCoercion::Parameterizable;
0a9f5b94 7use Scalar::Util qw(blessed);
9c319add 8use Data::Dump;
9use Digest::MD5;
10
3cfd35fd 11extends 'Moose::Meta::TypeConstraint';
12
13=head1 NAME
14
ca01e833 15MooseX::Meta::TypeConstraint::Parameterizable - Metaclass for Parameterizable type constraints.
3cfd35fd 16
17=head1 DESCRIPTION
18
ca01e833 19see L<MooseX::Parameterizable::Types> for how to use parameterizable
3cfd35fd 20types. This class is a subclass of L<Moose::Meta::TypeConstraint> which
88f7dcd2 21provides the gut functionality to enable parameterizable type constraints.
3cfd35fd 22
91623f94 23This class is not intended for public consumption. Please don't subclass it
24or rely on it. Chances are high stuff here is going to change a lot. For
25example, I will probably refactor this into several classes to get rid of all
26the ugly conditionals.
27
3cfd35fd 28=head1 ATTRIBUTES
29
30This class defines the following attributes.
31
a588ee00 32=head2 parent_type_constraint
3cfd35fd 33
88f7dcd2 34The type constraint whose validity is being made parameterizable.
3cfd35fd 35
36=cut
37
a588ee00 38has 'parent_type_constraint' => (
3cfd35fd 39 is=>'ro',
3a5dab74 40 isa=>'Object',
a588ee00 41 default=> sub {
42 Moose::Util::TypeConstraints::find_type_constraint("Any");
3a5dab74 43 },
a588ee00 44 required=>1,
3cfd35fd 45);
46
6c67366e 47
a588ee00 48=head2 constraining_value_type_constraint
3cfd35fd 49
50This is a type constraint which defines what kind of value is allowed to be the
88f7dcd2 51constraining value of the parameterizable type.
3cfd35fd 52
53=cut
54
a588ee00 55has 'constraining_value_type_constraint' => (
3cfd35fd 56 is=>'ro',
3a5dab74 57 isa=>'Object',
a588ee00 58 default=> sub {
59 Moose::Util::TypeConstraints::find_type_constraint("Any");
3a5dab74 60 },
a588ee00 61 required=>1,
3cfd35fd 62);
63
0a9f5b94 64=head2 constraining_value
3cfd35fd 65
a588ee00 66This is the actual value that constraints the L</parent_type_constraint>
01a12424 67
3cfd35fd 68=cut
69
a588ee00 70has 'constraining_value' => (
0a9f5b94 71 is=>'ro',
a588ee00 72 predicate=>'has_constraining_value',
3cfd35fd 73);
74
3cfd35fd 75=head1 METHODS
76
77This class defines the following methods.
78
1a6ad4bd 79=head2 new
21df4517 80
81Do some post build stuff
82
83=cut
84
88f7dcd2 85## Right now I add in the parameterizable type coercion until I can merge some Moose
1a6ad4bd 86## changes upstream.
87
26cf337e 88around 'new' => sub {
89 my ($new, $class, @args) = @_;
90 my $self = $class->$new(@args);
ca01e833 91 my $coercion = MooseX::Meta::TypeCoercion::Parameterizable->new(type_constraint => $self);
26cf337e 92 $self->coercion($coercion);
93 return $self;
94};
21df4517 95
0a9f5b94 96=head2 parameterize (@args)
3cfd35fd 97
6ff72600 98Given a ref of type constraints, create a parameterized constraint
0a9f5b94 99
3cfd35fd 100=cut
101
102sub parameterize {
0a9f5b94 103 my $self = shift @_;
3cfd35fd 104 my $class = ref $self;
6c67366e 105
106 Moose->throw_error("$self already has a constraining value.") if
107 $self->has_constraining_value;
108
0a9f5b94 109 if(blessed $_[0] && $_[0]->isa('Moose::Meta::TypeConstraint')) {
110 my $arg1 = shift @_;
0a9f5b94 111
6c67366e 112 if(blessed $_[0] && $_[0]->isa('Moose::Meta::TypeConstraint')) {
113 my $arg2 = shift @_ || $self->constraining_value_type_constraint;
114
115 ## TODO fix this crap!
116 Moose->throw_error("$arg2 is not a type constraint")
117 unless $arg2->isa('Moose::Meta::TypeConstraint');
118
119 Moose->throw_error("$arg1 is not a type of: ".$self->parent_type_constraint->name)
120 unless $arg1->is_a_type_of($self->parent_type_constraint);
121
122 Moose->throw_error("$arg2 is not a type of: ".$self->constraining_value_type_constraint->name)
123 unless $arg2->is_a_type_of($self->constraining_value_type_constraint);
124
125 Moose->throw_error('Too Many Args! Two are allowed.') if @_;
126
9c319add 127 my $name = $self->_generate_subtype_name($arg1, $arg2);
128 if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
129 return $exists;
130 } else {
131 my $type_constraint = $class->new(
132 name => $name,
133 parent => $self,
134 constraint => $self->constraint,
135 parent_type_constraint=>$arg1,
136 constraining_value_type_constraint => $arg2,
137 );
138 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
139 return $type_constraint;
140 }
6c67366e 141 } else {
142 Moose->throw_error("$arg1 is not a type of: ".$self->constraining_value_type_constraint->name)
143 unless $arg1->is_a_type_of($self->constraining_value_type_constraint);
144
9c319add 145 my $name = $self->_generate_subtype_name($self->parent_type_constraint, $arg1);
146 if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
147 return $exists;
148 } else {
149 my $type_constraint = $class->new(
150 name => $name,
151 parent => $self,
152 constraint => $self->constraint,
153 parent_type_constraint=>$self->parent_type_constraint,
154 constraining_value_type_constraint => $arg1,
155 );
156 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
157 return $type_constraint;
158 }
6c67366e 159 }
0a9f5b94 160 } else {
0a9f5b94 161 my $args;
162 ## Jump through some hoops to let them do tc[key=>10] and tc[{key=>10}]
163 if(@_) {
164 if($#_) {
165 if($self->constraining_value_type_constraint->is_a_type_of('HashRef')) {
166 $args = {@_};
167 } else {
168 $args = [@_];
169 }
170 } else {
171 $args = $_[0];
172 }
173
174 } else {
175 ## TODO: Is there a use case for parameterizing null or undef?
176 Moose->throw_error('Cannot Parameterize null values.');
177 }
178
179 if(my $err = $self->constraining_value_type_constraint->validate($args)) {
180 Moose->throw_error($err);
181 } else {
9c319add 182
183 my $sig = $args;
184 if(ref $sig) {
185 $sig = Digest::MD5::md5_hex(Data::Dump::dump($args));
186 }
187 my $name = $self->name."[$sig]";
188 if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
189 return $exists;
190 } else {
191 my $type_constraint = $class->new(
192 name => $name,
193 parent => $self,
194 constraint => $self->constraint,
195 constraining_value => $args,
196 parent_type_constraint=>$self->parent_type_constraint,
197 constraining_value_type_constraint => $self->constraining_value_type_constraint,
3ad84652 198 message => $self->message,
9c319add 199 );
21df4517 200
201 ## TODO This is probably going to have to go away (too many things added to the registry)
26cf337e 202 ##Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
9c319add 203 return $type_constraint;
204 }
0a9f5b94 205 }
206 }
3cfd35fd 207}
208
209=head2 _generate_subtype_name
210
88f7dcd2 211Returns a name for the parameterizable type that should be unique
3cfd35fd 212
213=cut
214
215sub _generate_subtype_name {
a588ee00 216 my ($self, $parent_tc, $constraining_tc) = @_;
3cfd35fd 217 return sprintf(
0a9f5b94 218 $self."[%s, %s]",
a588ee00 219 $parent_tc, $constraining_tc,
3cfd35fd 220 );
221}
222
0a9f5b94 223=head2 create_child_type
3cfd35fd 224
0a9f5b94 225modifier to make sure we get the constraint_generator
3cfd35fd 226
227=cut
228
0a9f5b94 229around 'create_child_type' => sub {
230 my ($create_child_type, $self, %opts) = @_;
66efbe23 231 if($self->has_constraining_value) {
232 $opts{constraining_value} = $self->constraining_value;
233 }
0a9f5b94 234 return $self->$create_child_type(
235 %opts,
236 parent=> $self,
237 parent_type_constraint=>$self->parent_type_constraint,
238 constraining_value_type_constraint => $self->constraining_value_type_constraint,
239 );
240};
3cfd35fd 241
0a9f5b94 242=head2 equals ($type_constraint)
3cfd35fd 243
88f7dcd2 244Override the base class behavior so that a parameterizable type equal both the parent
245type and the overall parameterizable container. This behavior may change if we can
246figure out what a parameterizable type is (multiply inheritance or a role...)
1e87d1a7 247
0a9f5b94 248=cut
3cfd35fd 249
0a9f5b94 250around 'equals' => sub {
251 my ( $equals, $self, $type_or_name ) = @_;
3cfd35fd 252
0a9f5b94 253 my $other = defined $type_or_name ?
254 Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
255 Moose->throw_error("Can't call $self ->equals without a parameter");
256
257 Moose->throw_error("$type_or_name is not a registered Type")
258 unless $other;
259
260 if(my $parent = $other->parent) {
261 return $self->$equals($other)
262 || $self->parent->equals($parent);
263 } else {
264 return $self->$equals($other);
3cfd35fd 265 }
3cfd35fd 266};
267
0af9bd45 268=head2 is_subtype_of
269
88f7dcd2 270Method modifier to make sure we match on subtype for both the parameterizable type
271as well as the type being made parameterizable
0af9bd45 272
273=cut
274
0a9f5b94 275around 'is_subtype_of' => sub {
276 my ( $is_subtype_of, $self, $type_or_name ) = @_;
3cfd35fd 277
0a9f5b94 278 my $other = defined $type_or_name ?
279 Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
280 Moose->throw_error("Can't call $self ->equals without a parameter");
281
282 Moose->throw_error("$type_or_name is not a registered Type")
283 unless $other;
284
285 return $self->$is_subtype_of($other)
286 || $self->parent_type_constraint->is_subtype_of($other);
9b6d2e22 287
3cfd35fd 288};
289
0af9bd45 290=head2 check
291
292As with 'is_subtype_of', we need to dual dispatch the method request
293
294=cut
3cfd35fd 295
0a9f5b94 296around 'check' => sub {
297 my ($check, $self, @args) = @_;
6c67366e 298 return (
299 $self->parent_type_constraint->check(@args) &&
300 $self->$check(@args)
301 );
0a9f5b94 302};
3cfd35fd 303
0af9bd45 304=head2 validate
305
306As with 'is_subtype_of', we need to dual dispatch the method request
307
308=cut
309
0a9f5b94 310around 'validate' => sub {
311 my ($validate, $self, @args) = @_;
6c67366e 312 return (
313 $self->parent_type_constraint->validate(@args) ||
314 $self->$validate(@args)
315 );
0a9f5b94 316};
3cfd35fd 317
0af9bd45 318=head2 _compiled_type_constraint
319
320modify this method so that we pass along the constraining value to the constraint
321coderef and also throw the correct error message if the constraining value does
322not match it's requirement.
323
324=cut
325
66efbe23 326around '_compiled_type_constraint' => sub {
327 my ($method, $self, @args) = @_;
328 my $coderef = $self->$method(@args);
6c67366e 329 my $constraining;
330 if($self->has_constraining_value) {
331 $constraining = $self->constraining_value;
332 }
333
66efbe23 334 return sub {
335 my @local_args = @_;
6c67366e 336 if(my $err = $self->constraining_value_type_constraint->validate($constraining)) {
337 Moose->throw_error($err);
338 }
339 $coderef->(@local_args, $constraining);
66efbe23 340 };
341};
342
0af9bd45 343=head2 coerce
344
345More method modification to support dispatch coerce to a parent.
346
347=cut
348
9c319add 349around 'coerce' => sub {
350 my ($coerce, $self, @args) = @_;
26cf337e 351
352 if($self->has_constraining_value) {
353 push @args, $self->constraining_value;
354 if(@{$self->coercion->type_coercion_map}) {
355 my $coercion = $self->coercion;
26cf337e 356 my $coerced = $self->$coerce(@args);
357 if(defined $coerced) {
26cf337e 358 return $coerced;
359 } else {
360 my $parent = $self->parent;
26cf337e 361 return $parent->coerce(@args);
362 }
363 } else {
364 my $parent = $self->parent;
26cf337e 365 return $parent->coerce(@args);
9c319add 366 }
367 }
26cf337e 368 else {
369 return $self->$coerce(@args);
370 }
371 return;
9c319add 372};
373
3cfd35fd 374=head2 get_message
375
ae1d0652 376Give you a better peek into what's causing the error.
3cfd35fd 377
3cfd35fd 378around 'get_message' => sub {
379 my ($get_message, $self, $value) = @_;
ae1d0652 380 return $self->$get_message($value);
3cfd35fd 381};
382
383=head1 SEE ALSO
384
385The following modules or resources may be of interest.
386
387L<Moose>, L<Moose::Meta::TypeConstraint>
388
389=head1 AUTHOR
390
391John Napiorkowski, C<< <jjnapiork@cpan.org> >>
392
393=head1 COPYRIGHT & LICENSE
394
395This program is free software; you can redistribute it and/or modify
396it under the same terms as Perl itself.
397
398=cut
399
1a6ad4bd 400__PACKAGE__->meta->make_immutable(inline_constructor => 0);
1e87d1a7 401