more pod and clarified license
[gitmo/MooseX-Dependent.git] / lib / MooseX / Dependent / Meta / TypeConstraint / Dependent.pm
CommitLineData
3cfd35fd 1package ## Hide from PAUSE
a588ee00 2 MooseX::Dependent::Meta::TypeConstraint::Dependent;
3cfd35fd 3
4use Moose;
5use Moose::Util::TypeConstraints ();
21df4517 6use MooseX::Dependent::Meta::TypeCoercion::Dependent;
0a9f5b94 7use Scalar::Util qw(blessed);
9c319add 8use Data::Dump;
9use Digest::MD5;
10
3cfd35fd 11extends 'Moose::Meta::TypeConstraint';
12
13=head1 NAME
14
a588ee00 15MooseX::Dependent::Meta::TypeConstraint::Dependent - Metaclass for Dependent type constraints.
3cfd35fd 16
17=head1 DESCRIPTION
18
a588ee00 19see L<MooseX::Dependent> for examples and details of how to use dependent
3cfd35fd 20types. This class is a subclass of L<Moose::Meta::TypeConstraint> which
21provides the gut functionality to enable dependent type constraints.
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
a588ee00 34The type constraint whose validity is being made dependent.
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
a588ee00 51constraining value of the dependent 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
21df4517 79=head2 BUILD
80
81Do some post build stuff
82
83=cut
84
0af9bd45 85## Right now I add in the dependent type coercion until I can merge some Moose
86## changes upstream
87
26cf337e 88around 'new' => sub {
89 my ($new, $class, @args) = @_;
90 my $self = $class->$new(@args);
91 my $coercion = MooseX::Dependent::Meta::TypeCoercion::Dependent->new(type_constraint => $self);
92 $self->coercion($coercion);
93 return $self;
94};
21df4517 95
0a9f5b94 96=head2 parameterize (@args)
3cfd35fd 97
98Given a ref of type constraints, create a structured type.
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,
198 );
21df4517 199
200 ## TODO This is probably going to have to go away (too many things added to the registry)
26cf337e 201 ##Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
9c319add 202 return $type_constraint;
203 }
0a9f5b94 204 }
205 }
3cfd35fd 206}
207
208=head2 _generate_subtype_name
209
210Returns a name for the dependent type that should be unique
211
212=cut
213
214sub _generate_subtype_name {
a588ee00 215 my ($self, $parent_tc, $constraining_tc) = @_;
3cfd35fd 216 return sprintf(
0a9f5b94 217 $self."[%s, %s]",
a588ee00 218 $parent_tc, $constraining_tc,
3cfd35fd 219 );
220}
221
0a9f5b94 222=head2 create_child_type
3cfd35fd 223
0a9f5b94 224modifier to make sure we get the constraint_generator
3cfd35fd 225
226=cut
227
0a9f5b94 228around 'create_child_type' => sub {
229 my ($create_child_type, $self, %opts) = @_;
66efbe23 230 if($self->has_constraining_value) {
231 $opts{constraining_value} = $self->constraining_value;
232 }
0a9f5b94 233 return $self->$create_child_type(
234 %opts,
235 parent=> $self,
236 parent_type_constraint=>$self->parent_type_constraint,
237 constraining_value_type_constraint => $self->constraining_value_type_constraint,
238 );
239};
3cfd35fd 240
0a9f5b94 241=head2 equals ($type_constraint)
3cfd35fd 242
0a9f5b94 243Override the base class behavior so that a dependent type equal both the parent
244type and the overall dependent container. This behavior may change if we can
245figure out what a dependent type is (multiply inheritance or a role...)
1e87d1a7 246
0a9f5b94 247=cut
3cfd35fd 248
0a9f5b94 249around 'equals' => sub {
250 my ( $equals, $self, $type_or_name ) = @_;
3cfd35fd 251
0a9f5b94 252 my $other = defined $type_or_name ?
253 Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
254 Moose->throw_error("Can't call $self ->equals without a parameter");
255
256 Moose->throw_error("$type_or_name is not a registered Type")
257 unless $other;
258
259 if(my $parent = $other->parent) {
260 return $self->$equals($other)
261 || $self->parent->equals($parent);
262 } else {
263 return $self->$equals($other);
3cfd35fd 264 }
3cfd35fd 265};
266
0af9bd45 267=head2 is_subtype_of
268
269Method modifier to make sure we match on subtype for both the dependent type
270as well as the type being made dependent
271
272=cut
273
0a9f5b94 274around 'is_subtype_of' => sub {
275 my ( $is_subtype_of, $self, $type_or_name ) = @_;
3cfd35fd 276
0a9f5b94 277 my $other = defined $type_or_name ?
278 Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
279 Moose->throw_error("Can't call $self ->equals without a parameter");
280
281 Moose->throw_error("$type_or_name is not a registered Type")
282 unless $other;
283
284 return $self->$is_subtype_of($other)
285 || $self->parent_type_constraint->is_subtype_of($other);
9b6d2e22 286
3cfd35fd 287};
288
0af9bd45 289=head2 check
290
291As with 'is_subtype_of', we need to dual dispatch the method request
292
293=cut
3cfd35fd 294
0a9f5b94 295around 'check' => sub {
296 my ($check, $self, @args) = @_;
6c67366e 297 return (
298 $self->parent_type_constraint->check(@args) &&
299 $self->$check(@args)
300 );
0a9f5b94 301};
3cfd35fd 302
0af9bd45 303=head2 validate
304
305As with 'is_subtype_of', we need to dual dispatch the method request
306
307=cut
308
0a9f5b94 309around 'validate' => sub {
310 my ($validate, $self, @args) = @_;
6c67366e 311 return (
312 $self->parent_type_constraint->validate(@args) ||
313 $self->$validate(@args)
314 );
0a9f5b94 315};
3cfd35fd 316
0af9bd45 317=head2 _compiled_type_constraint
318
319modify this method so that we pass along the constraining value to the constraint
320coderef and also throw the correct error message if the constraining value does
321not match it's requirement.
322
323=cut
324
66efbe23 325around '_compiled_type_constraint' => sub {
326 my ($method, $self, @args) = @_;
327 my $coderef = $self->$method(@args);
6c67366e 328 my $constraining;
329 if($self->has_constraining_value) {
330 $constraining = $self->constraining_value;
331 }
332
66efbe23 333 return sub {
334 my @local_args = @_;
6c67366e 335 if(my $err = $self->constraining_value_type_constraint->validate($constraining)) {
336 Moose->throw_error($err);
337 }
338 $coderef->(@local_args, $constraining);
66efbe23 339 };
340};
341
0af9bd45 342=head2 coerce
343
344More method modification to support dispatch coerce to a parent.
345
346=cut
347
9c319add 348around 'coerce' => sub {
349 my ($coerce, $self, @args) = @_;
26cf337e 350
351 if($self->has_constraining_value) {
352 push @args, $self->constraining_value;
353 if(@{$self->coercion->type_coercion_map}) {
354 my $coercion = $self->coercion;
26cf337e 355 my $coerced = $self->$coerce(@args);
356 if(defined $coerced) {
26cf337e 357 return $coerced;
358 } else {
359 my $parent = $self->parent;
26cf337e 360 return $parent->coerce(@args);
361 }
362 } else {
363 my $parent = $self->parent;
26cf337e 364 return $parent->coerce(@args);
9c319add 365 }
366 }
26cf337e 367 else {
368 return $self->$coerce(@args);
369 }
370 return;
9c319add 371};
372
3cfd35fd 373=head2 get_message
374
ae1d0652 375Give you a better peek into what's causing the error.
3cfd35fd 376
3cfd35fd 377around 'get_message' => sub {
378 my ($get_message, $self, $value) = @_;
ae1d0652 379 return $self->$get_message($value);
3cfd35fd 380};
381
382=head1 SEE ALSO
383
384The following modules or resources may be of interest.
385
386L<Moose>, L<Moose::Meta::TypeConstraint>
387
388=head1 AUTHOR
389
390John Napiorkowski, C<< <jjnapiork@cpan.org> >>
391
392=head1 COPYRIGHT & LICENSE
393
394This program is free software; you can redistribute it and/or modify
395it under the same terms as Perl itself.
396
397=cut
398
26cf337e 3991;
400##__PACKAGE__->meta->make_immutable(inline_constructor => 0);
1e87d1a7 401