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