started the custom coercion
[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
80sub BUILD {
81 my ($self) = @_;
82 $self->coercion(
83 MooseX::Dependent::Meta::TypeCoercion::Dependent->new(
84 type_constraint => $self,
85 ));
86}
87
0a9f5b94 88=head2 parameterize (@args)
3cfd35fd 89
90Given a ref of type constraints, create a structured type.
0a9f5b94 91
3cfd35fd 92=cut
93
94sub parameterize {
0a9f5b94 95 my $self = shift @_;
3cfd35fd 96 my $class = ref $self;
6c67366e 97
98 Moose->throw_error("$self already has a constraining value.") if
99 $self->has_constraining_value;
100
0a9f5b94 101 if(blessed $_[0] && $_[0]->isa('Moose::Meta::TypeConstraint')) {
102 my $arg1 = shift @_;
0a9f5b94 103
6c67366e 104 if(blessed $_[0] && $_[0]->isa('Moose::Meta::TypeConstraint')) {
105 my $arg2 = shift @_ || $self->constraining_value_type_constraint;
106
107 ## TODO fix this crap!
108 Moose->throw_error("$arg2 is not a type constraint")
109 unless $arg2->isa('Moose::Meta::TypeConstraint');
110
111 Moose->throw_error("$arg1 is not a type of: ".$self->parent_type_constraint->name)
112 unless $arg1->is_a_type_of($self->parent_type_constraint);
113
114 Moose->throw_error("$arg2 is not a type of: ".$self->constraining_value_type_constraint->name)
115 unless $arg2->is_a_type_of($self->constraining_value_type_constraint);
116
117 Moose->throw_error('Too Many Args! Two are allowed.') if @_;
118
9c319add 119 my $name = $self->_generate_subtype_name($arg1, $arg2);
120 if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
121 return $exists;
122 } else {
123 my $type_constraint = $class->new(
124 name => $name,
125 parent => $self,
126 constraint => $self->constraint,
127 parent_type_constraint=>$arg1,
128 constraining_value_type_constraint => $arg2,
129 );
130 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
131 return $type_constraint;
132 }
6c67366e 133 } else {
134 Moose->throw_error("$arg1 is not a type of: ".$self->constraining_value_type_constraint->name)
135 unless $arg1->is_a_type_of($self->constraining_value_type_constraint);
136
9c319add 137 my $name = $self->_generate_subtype_name($self->parent_type_constraint, $arg1);
138 if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
139 return $exists;
140 } else {
141 my $type_constraint = $class->new(
142 name => $name,
143 parent => $self,
144 constraint => $self->constraint,
145 parent_type_constraint=>$self->parent_type_constraint,
146 constraining_value_type_constraint => $arg1,
147 );
148 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
149 return $type_constraint;
150 }
6c67366e 151 }
0a9f5b94 152 } else {
0a9f5b94 153 my $args;
154 ## Jump through some hoops to let them do tc[key=>10] and tc[{key=>10}]
155 if(@_) {
156 if($#_) {
157 if($self->constraining_value_type_constraint->is_a_type_of('HashRef')) {
158 $args = {@_};
159 } else {
160 $args = [@_];
161 }
162 } else {
163 $args = $_[0];
164 }
165
166 } else {
167 ## TODO: Is there a use case for parameterizing null or undef?
168 Moose->throw_error('Cannot Parameterize null values.');
169 }
170
171 if(my $err = $self->constraining_value_type_constraint->validate($args)) {
172 Moose->throw_error($err);
173 } else {
9c319add 174
175 my $sig = $args;
176 if(ref $sig) {
177 $sig = Digest::MD5::md5_hex(Data::Dump::dump($args));
178 }
179 my $name = $self->name."[$sig]";
180 if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
181 return $exists;
182 } else {
183 my $type_constraint = $class->new(
184 name => $name,
185 parent => $self,
186 constraint => $self->constraint,
187 constraining_value => $args,
188 parent_type_constraint=>$self->parent_type_constraint,
189 constraining_value_type_constraint => $self->constraining_value_type_constraint,
190 );
21df4517 191
192 ## TODO This is probably going to have to go away (too many things added to the registry)
9c319add 193 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
194 return $type_constraint;
195 }
0a9f5b94 196 }
197 }
3cfd35fd 198}
199
200=head2 _generate_subtype_name
201
202Returns a name for the dependent type that should be unique
203
204=cut
205
206sub _generate_subtype_name {
a588ee00 207 my ($self, $parent_tc, $constraining_tc) = @_;
3cfd35fd 208 return sprintf(
0a9f5b94 209 $self."[%s, %s]",
a588ee00 210 $parent_tc, $constraining_tc,
3cfd35fd 211 );
212}
213
0a9f5b94 214=head2 create_child_type
3cfd35fd 215
0a9f5b94 216modifier to make sure we get the constraint_generator
3cfd35fd 217
218=cut
219
0a9f5b94 220around 'create_child_type' => sub {
221 my ($create_child_type, $self, %opts) = @_;
66efbe23 222 if($self->has_constraining_value) {
223 $opts{constraining_value} = $self->constraining_value;
224 }
0a9f5b94 225 return $self->$create_child_type(
226 %opts,
227 parent=> $self,
228 parent_type_constraint=>$self->parent_type_constraint,
229 constraining_value_type_constraint => $self->constraining_value_type_constraint,
230 );
231};
3cfd35fd 232
0a9f5b94 233=head2 equals ($type_constraint)
3cfd35fd 234
0a9f5b94 235Override the base class behavior so that a dependent type equal both the parent
236type and the overall dependent container. This behavior may change if we can
237figure out what a dependent type is (multiply inheritance or a role...)
1e87d1a7 238
0a9f5b94 239=cut
3cfd35fd 240
0a9f5b94 241around 'equals' => sub {
242 my ( $equals, $self, $type_or_name ) = @_;
3cfd35fd 243
0a9f5b94 244 my $other = defined $type_or_name ?
245 Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
246 Moose->throw_error("Can't call $self ->equals without a parameter");
247
248 Moose->throw_error("$type_or_name is not a registered Type")
249 unless $other;
250
251 if(my $parent = $other->parent) {
252 return $self->$equals($other)
253 || $self->parent->equals($parent);
254 } else {
255 return $self->$equals($other);
3cfd35fd 256 }
3cfd35fd 257};
258
0a9f5b94 259around 'is_subtype_of' => sub {
260 my ( $is_subtype_of, $self, $type_or_name ) = @_;
3cfd35fd 261
0a9f5b94 262 my $other = defined $type_or_name ?
263 Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
264 Moose->throw_error("Can't call $self ->equals without a parameter");
265
266 Moose->throw_error("$type_or_name is not a registered Type")
267 unless $other;
268
269 return $self->$is_subtype_of($other)
270 || $self->parent_type_constraint->is_subtype_of($other);
9b6d2e22 271
3cfd35fd 272};
273
0a9f5b94 274sub is_a_type_of {
275 my ($self, @args) = @_;
276 return ($self->equals(@args) ||
277 $self->is_subtype_of(@args));
278}
3cfd35fd 279
0a9f5b94 280around 'check' => sub {
281 my ($check, $self, @args) = @_;
6c67366e 282 return (
283 $self->parent_type_constraint->check(@args) &&
284 $self->$check(@args)
285 );
0a9f5b94 286};
3cfd35fd 287
0a9f5b94 288around 'validate' => sub {
289 my ($validate, $self, @args) = @_;
6c67366e 290 return (
291 $self->parent_type_constraint->validate(@args) ||
292 $self->$validate(@args)
293 );
0a9f5b94 294};
3cfd35fd 295
66efbe23 296around '_compiled_type_constraint' => sub {
297 my ($method, $self, @args) = @_;
298 my $coderef = $self->$method(@args);
6c67366e 299 my $constraining;
300 if($self->has_constraining_value) {
301 $constraining = $self->constraining_value;
302 }
303
66efbe23 304 return sub {
305 my @local_args = @_;
6c67366e 306 if(my $err = $self->constraining_value_type_constraint->validate($constraining)) {
307 Moose->throw_error($err);
308 }
309 $coderef->(@local_args, $constraining);
66efbe23 310 };
311};
312
9c319add 313around 'coerce' => sub {
314 my ($coerce, $self, @args) = @_;
315 if($self->coercion) {
316 if(my $value = $self->$coerce(@args)) {
5ae5d765 317 return $value if defined $value;
9c319add 318 }
319 }
320 return $self->parent->coerce(@args);
321};
322
3cfd35fd 323=head2 get_message
324
ae1d0652 325Give you a better peek into what's causing the error.
3cfd35fd 326
3cfd35fd 327around 'get_message' => sub {
328 my ($get_message, $self, $value) = @_;
ae1d0652 329 return $self->$get_message($value);
3cfd35fd 330};
331
332=head1 SEE ALSO
333
334The following modules or resources may be of interest.
335
336L<Moose>, L<Moose::Meta::TypeConstraint>
337
338=head1 AUTHOR
339
340John Napiorkowski, C<< <jjnapiork@cpan.org> >>
341
342=head1 COPYRIGHT & LICENSE
343
344This program is free software; you can redistribute it and/or modify
345it under the same terms as Perl itself.
346
347=cut
348
1e87d1a7 349__PACKAGE__->meta->make_immutable(inline_constructor => 0);
350