File Coverage

blib/lib/MooseX/Meta/TypeConstraint/Parameterizable.pm
Criterion Covered Total %
statement 82 90 91.1
branch 31 44 70.4
condition 7 12 58.3
subroutine 13 13 100.0
pod 3 4 75.0
total 136 163 83.4


line stmt bran cond sub pod time code
1             package ## Hide from PAUSE
2             MooseX::Meta::TypeConstraint::Parameterizable;
3              
4 10     10   3279 use Moose;
  10         1112002  
  10         70  
5 10     10   81650 use MooseX::NonMoose;
  10         11379  
  10         51  
6 10     10   746965 use Moose::Util::TypeConstraints ();
  10         29  
  10         247  
7 10     10   5772 use MooseX::Meta::TypeCoercion::Parameterizable;
  10         359848  
  10         652  
8 10     10   119 use Scalar::Util qw(blessed);
  10         28  
  10         749  
9 10     10   6503 use Data::Dump;
  10         56842  
  10         629  
10 10     10   87 use Digest::MD5;
  10         28  
  10         17034  
11              
12             extends 'Moose::Meta::TypeConstraint';
13              
14             =head1 NAME
15              
16             MooseX::Meta::TypeConstraint::Parameterizable - Parameterizable Meta Class.
17              
18             =head1 DESCRIPTION
19              
20             See L<MooseX::Types::Parameterizable> for how to use parameterizable
21             types. This class is a subclass of L<Moose::Meta::TypeConstraint> which
22             provides the gut functionality to enable parameterizable type constraints.
23              
24             You probably won't need to subclass or consume this class directly.
25              
26             =head1 ATTRIBUTES
27              
28             This class defines the following attributes.
29              
30             =head2 parent_type_constraint
31              
32             The type constraint whose validity is being made parameterizable.
33              
34             =cut
35              
36             has 'parent_type_constraint' => (
37             is=>'ro',
38             isa=>Moose::Util::TypeConstraints::class_type('Moose::Meta::TypeConstraint'),
39             default=> sub {
40             Moose::Util::TypeConstraints::find_type_constraint("Any");
41             },
42             required=>1,
43             );
44              
45             =head2 constraining_value_type_constraint
46              
47             This is a type constraint which defines what kind of value is allowed to be the
48             constraining value of the parameterizable type.
49              
50             =cut
51              
52             has 'constraining_value_type_constraint' => (
53             is=>'ro',
54             isa=>Moose::Util::TypeConstraints::class_type('Moose::Meta::TypeConstraint'),
55             default=> sub {
56             Moose::Util::TypeConstraints::find_type_constraint("Any");
57             },
58             required=>1,
59             );
60              
61             =head2 constraining_value
62              
63             This is the actual value that constraints the L</parent_type_constraint>
64              
65             =cut
66              
67             ## TODO, this is where we probably should break out Parameterized stuff from
68             ## parameterizable...
69              
70             has 'constraining_value' => (
71             is=>'ro',
72             predicate=>'has_constraining_value',
73             );
74              
75             =head1 METHODS
76              
77             This class defines the following methods.
78              
79             =head2 new
80              
81             Do some post build stuff, mostly make sure we set the correct coercion object.
82              
83             =cut
84              
85             my $self = shift;
86 118     118 0 202522 my $coercion = MooseX::Meta::TypeCoercion::Parameterizable->new(type_constraint => $self);
87 118         755 $self->coercion($coercion);
88 118         4989 }
89              
90             =head2 parameterize (@args)
91              
92             Given a ref of type constraints, create a parameterized constraint
93              
94             =cut
95              
96             my $self = shift @_;
97             my $class = ref $self;
98 96     96 1 241538  
99 96         262 Moose->throw_error("$self already has a constraining value.") if
100             $self->has_constraining_value;
101 96 50       4290  
102             if(blessed $_[0] && $_[0]->isa('Moose::Meta::TypeConstraint')) {
103             my $arg1 = shift @_;
104 96 100 100     612  
105 17         548 if(blessed $_[0] && $_[0]->isa('Moose::Meta::TypeConstraint')) {
106             my $arg2 = shift @_ || $self->constraining_value_type_constraint;
107 17 100 66     137  
108 14   33     272 ## TODO fix this crap!
109             Moose->throw_error("$arg2 is not a type constraint")
110             unless $arg2->isa('Moose::Meta::TypeConstraint');
111 14 50       180  
112             Moose->throw_error("$arg1 is not a type of: ".$self->parent_type_constraint->name)
113             unless $arg1->is_a_type_of($self->parent_type_constraint);
114 14 50       821  
115             Moose->throw_error("$arg2 is not a type of: ".$self->constraining_value_type_constraint->name)
116             unless $arg2->is_a_type_of($self->constraining_value_type_constraint);
117 14 50       16229  
118             Moose->throw_error('Too Many Args! Two are allowed.') if @_;
119              
120 14 50       16262 my $name = $self->_generate_subtype_name($arg1, $arg2);
121             if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
122 14         80 return $exists;
123 14 100       2421 } else {
124 1         103 my $type_constraint = $class->new(
125             name => $name,
126 13         1133 parent => $self,
127             constraint => $self->constraint,
128             parent_type_constraint=>$arg1,
129             constraining_value_type_constraint => $arg2,
130             );
131             Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint($type_constraint);
132              
133 13         1726  
134             return $type_constraint;
135             }
136 13         1223 } 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 3 50       128  
140             my $name = $self->_generate_subtype_name($self->parent_type_constraint, $arg1);
141             if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
142 3         20169 return $exists;
143 3 50       411 } else {
144 0         0 my $type_constraint = $class->new(
145             name => $name,
146 3         245 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 3         405 }
154 3         289 }
155             } else {
156             my $args;
157             ## Jump through some hoops to let them do tc[key=>10] and tc[{key=>10}]
158 79         173 if(@_) {
159             if($#_) {
160 79 50       225 if($self->constraining_value_type_constraint->is_a_type_of('HashRef')) {
161 79 100       210 $args = {@_};
162 28 50       1174 } else {
163 28         28980 $args = [@_];
164             }
165 0         0 } else {
166             $args = $_[0];
167             }
168 51         107  
169             } else {
170             ## TODO: Is there a use case for parameterizing null or undef?
171             Moose->throw_error('Cannot Parameterize null values.');
172             }
173 0         0  
174             if(my $err = $self->constraining_value_type_constraint->validate($args)) {
175             Moose->throw_error($err);
176 79 100       3293 } else {
177 10         12526  
178             my $sig = $args;
179             if(ref $sig) {
180 69         65804 $sig = Digest::MD5::md5_hex(Data::Dump::dump($args));
181 69 100       280 }
182 40         192 my $name = $self->name."[$sig]";
183             if(my $exists = Moose::Util::TypeConstraints::find_type_constraint($name)) {
184 69         20553 return $exists;
185 69 50       790 } else {
186 0         0 return $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             ($self->has_message ? (message => sub { $self->message->( @_, $args ) } ) : ()),
194             );
195 69 100   2   5437 }
  2         1944  
196             }
197             }
198             }
199              
200             =head2 _generate_subtype_name
201              
202             Returns a name for the parameterizable type that should be unique
203              
204             =cut
205              
206             my ($self, $parent_tc, $constraining_tc) = @_;
207             return sprintf(
208             $self."[%s, %s]",
209 17     17   65 $parent_tc, $constraining_tc,
210 17         72 );
211             }
212              
213             =head2 create_child_type
214              
215             modifier to make sure we get the constraint_generator
216              
217             =cut
218              
219             around 'create_child_type' => sub {
220             my ($create_child_type, $self, %opts) = @_;
221             if($self->has_constraining_value) {
222             $opts{constraining_value} = $self->constraining_value;
223             }
224             return $self->$create_child_type(
225             %opts,
226             parent=> $self,
227             parent_type_constraint=>$self->parent_type_constraint,
228             constraining_value_type_constraint => $self->constraining_value_type_constraint,
229             );
230             };
231              
232             =head2 equals ($type_constraint)
233              
234             Override the base class behavior so that a parameterizable type equal both the parent
235             type and the overall parameterizable container. This behavior may change if we can
236             figure out what a parameterizable type is (multiply inheritance or a role...)
237              
238             =cut
239              
240             around 'equals' => sub {
241             my ( $equals, $self, $type_or_name ) = @_;
242              
243             my $other = defined $type_or_name ?
244             Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
245             Moose->throw_error("Can't call $self ->equals without a parameter");
246              
247             Moose->throw_error("$type_or_name is not a registered Type")
248             unless $other;
249              
250             if(my $parent = $other->parent) {
251             return $self->$equals($other)
252             || $self->parent->equals($parent);
253             } else {
254             return $self->$equals($other);
255             }
256             };
257              
258             =head2 is_subtype_of
259              
260             Method modifier to make sure we match on subtype for both the parameterizable type
261             as well as the type being made parameterizable
262              
263             =cut
264              
265             around 'is_subtype_of' => sub {
266             my ( $is_subtype_of, $self, $type_or_name ) = @_;
267              
268             my $other = defined $type_or_name ?
269             Moose::Util::TypeConstraints::find_type_constraint($type_or_name) :
270             Moose->throw_error("Can't call $self ->equals without a parameter");
271              
272             Moose->throw_error("$type_or_name is not a registered Type")
273             unless $other;
274              
275             return $self->$is_subtype_of($other)
276             || $self->parent_type_constraint->is_subtype_of($other);
277              
278             };
279              
280             =head2 check
281              
282             As with 'is_subtype_of', we need to dual dispatch the method request
283              
284             =cut
285              
286             around 'check' => sub {
287             my ($check, $self, @args) = @_;
288             return (
289             $self->parent_type_constraint->check(@args) &&
290             $self->$check(@args)
291             );
292             };
293              
294             =head2 validate
295              
296             As with 'is_subtype_of', we need to dual dispatch the method request
297              
298             =cut
299              
300             around 'validate' => sub {
301             my ($validate, $self, @args) = @_;
302             return (
303             $self->parent_type_constraint->validate(@args) ||
304             $self->$validate(@args)
305             );
306             };
307              
308             =head2 _compiled_type_constraint
309              
310             modify this method so that we pass along the constraining value to the constraint
311             coderef and also throw the correct error message if the constraining value does
312             not match it's requirement.
313              
314             =cut
315              
316             around '_compiled_type_constraint' => sub {
317             my ($method, $self, @args) = @_;
318             my $coderef = $self->$method(@args);
319             my $constraining;
320             if($self->has_constraining_value) {
321             $constraining = $self->constraining_value;
322             }
323              
324             return sub {
325             my @local_args = @_;
326             if(my $err = $self->constraining_value_type_constraint->validate($constraining)) {
327             Moose->throw_error($err);
328             }
329             $coderef->(@local_args, $constraining);
330             };
331             };
332              
333             =head2 coerce
334              
335             More method modification to support dispatch coerce to a parent.
336              
337             =cut
338              
339             #around 'has_coercion', sub {
340             # my ($orig, $self) = @_;
341             # $self->$orig ? 1 : $self->parent->has_coercion;
342             #};
343              
344             my $self = shift;
345            
346             my $coercion = $self->coercion || $self->parent->coercion;
347            
348 1     1 1 899 unless ($coercion) {
349             require Moose;
350 1   33     33 Moose->throw_error("Cannot coerce without a type coercion");
351             }
352 1 50       13
353 0         0 return $_[0] if $self->check($_[0]);
354 0         0
355             my $result = $self->coerce(@_);
356              
357 1 50       6 $self->assert_valid($result);
358            
359 1         547 return $result;
360             }
361 1         9  
362             my ($self, @args) = @_;
363 1         33 if($self->has_constraining_value) {
364             push @args, $self->constraining_value;
365             }
366             if(@{$self->coercion->type_coercion_map}) {
367 32     32 1 14376 my $coercion = $self->coercion;
368 32 100       1401 my $coerced = $coercion->coerce(@args);
369 16         642 if(defined $coerced) {
370             return $coerced;
371 32 100       63 } else {
  32         903  
372 16         1045 my $parent = $self->parent;
373 16         200 return $parent->coerce(@args);
374 16 50       140 }
375 16         139 } else {
376             my $parent = $self->parent;
377 0         0 return $parent->coerce(@args);
378 0         0 }
379             }
380              
381 16         1100 =head1 SEE ALSO
382 16         170  
383             The following modules or resources may be of interest.
384              
385             L<Moose>, L<Moose::Meta::TypeConstraint>
386              
387             =head1 AUTHOR
388              
389             John Napiorkowski, C<< <jjnapiork@cpan.org> >>
390              
391             =head1 COPYRIGHT & LICENSE
392              
393             This program is free software; you can redistribute it and/or modify
394             it under the same terms as Perl itself.
395              
396             =cut
397              
398             __PACKAGE__->meta->make_immutable(inline_constructor => 0);
399