File Coverage

blib/lib/MooseX/Meta/TypeConstraint/Structured.pm
Criterion Covered Total %
statement 79 89 88.7
branch 37 50 74.0
condition 12 12 100.0
subroutine 15 16 93.7
pod 6 6 100.0
total 149 173 86.1


line stmt bran cond sub pod time code
1             package ## Hide from PAUSE
2             MooseX::Meta::TypeConstraint::Structured;
3             # ABSTRACT: Structured type constraints
4              
5 21     21   26926 use Moose;
  21         1975321  
  21         144  
6 21     21   150852 use Devel::PartialDump;
  21         757065  
  21         159  
7 21     21   16196 use MooseX::Meta::TypeCoercion::Structured;
  21         304193  
  21         31061  
8             extends 'Moose::Meta::TypeConstraint';
9              
10              
11             #pod =head1 DESCRIPTION
12             #pod
13             #pod A structure is a set of L<Moose::Meta::TypeConstraint> that are 'aggregated' in
14             #pod such a way as that they are all applied to an incoming list of arguments. The
15             #pod idea here is that a Type Constraint could be something like, "An C<Int> followed by
16             #pod an C<Int> and then a C<Str>" and that this could be done so with a declaration like:
17             #pod
18             #pod Tuple[Int,Int,Str]; ## Example syntax
19             #pod
20             #pod So a structure is a list of type constraints (the C<Int,Int,Str> in the above
21             #pod example) which are intended to function together.
22             #pod
23             #pod =attr type_constraints
24             #pod
25             #pod A list of L<Moose::Meta::TypeConstraint> objects.
26             #pod
27             #pod =cut
28              
29             has 'type_constraints' => (
30             is=>'ro',
31             isa=>'Ref',
32             predicate=>'has_type_constraints',
33             );
34              
35             #pod =attr constraint_generator
36             #pod
37             #pod =for stopwords subref
38             #pod
39             #pod A subref or closure that contains the way we validate incoming values against
40             #pod a set of type constraints.
41             #pod
42             #pod =cut
43              
44             has 'constraint_generator' => (
45             is=>'ro',
46             isa=>'CodeRef',
47             predicate=>'has_constraint_generator',
48             );
49              
50             has coercion => (
51             is => 'ro',
52             isa => 'Object',
53             builder => '_build_coercion',
54             );
55              
56             sub _build_coercion {
57 233     233   122281 my ($self) = @_;
58 233         1500 return MooseX::Meta::TypeCoercion::Structured->new(
59             type_constraint => $self,
60             );
61             }
62              
63             #pod =method validate
64             #pod
65             #pod Messing with validate so that we can support nicer error messages.
66             #pod
67             #pod =cut
68              
69             sub _clean_message {
70 18     18   5954 my $message = shift @_;
71 18         55 $message =~s/MooseX::Types::Structured:://g;
72 18         783 return $message;
73             }
74              
75             override 'validate' => sub {
76             my ($self, $value, $message_stack) = @_;
77             unless ($message_stack) {
78             $message_stack = MooseX::Types::Structured::MessageStack->new();
79             }
80              
81             $message_stack->inc_level;
82              
83             if ($self->_compiled_type_constraint->($value, $message_stack)) {
84             ## Everything is good, no error message to return
85             return undef;
86             } else {
87             ## Whoops, need to figure out the right error message
88             my $args = Devel::PartialDump::dump($value);
89             $message_stack->dec_level;
90             if($message_stack->has_messages) {
91             if($message_stack->level) {
92             ## we are inside a deeply structured constraint
93             return $self->get_message($args);
94             } else {
95             my $message_str = $message_stack->as_string;
96             return _clean_message($self->get_message("$args, Internal Validation Error is: $message_str"));
97             }
98             } else {
99             return $self->get_message($args);
100             }
101             }
102             };
103              
104             #pod =method generate_constraint_for ($type_constraints)
105             #pod
106             #pod Given some type constraints, use them to generate validation rules for an ref
107             #pod of values (to be passed at check time)
108             #pod
109             #pod =cut
110              
111             sub generate_constraint_for {
112 131     131 1 254 my ($self, $type_constraints) = @_;
113 131         5705 return $self->constraint_generator->($self, $type_constraints);
114             }
115              
116             #pod =begin :prelude
117             #pod
118             #pod =for stopwords parameterize
119             #pod
120             #pod =end :prelude
121             #pod
122             #pod =method parameterize (@type_constraints)
123             #pod
124             #pod Given a ref of type constraints, create a structured type.
125             #pod
126             #pod =cut
127              
128             sub parameterize {
129 130     130 1 268021 my ($self, @type_constraints) = @_;
130 130         283 my $class = ref $self;
131 130         4358 my $name = $self->name .'['. join(',', map {"$_"} @type_constraints) .']';
  325         8175  
132 130         6646 my $constraint_generator = $self->__infer_constraint_generator;
133              
134 130         720 return $class->new(
135             name => $name,
136             parent => $self,
137             type_constraints => \@type_constraints,
138             constraint_generator => $constraint_generator,
139             );
140             }
141              
142             #pod =method __infer_constraint_generator
143             #pod
144             #pod =for stopwords servicable
145             #pod
146             #pod This returns a CODEREF which generates a suitable constraint generator. Not
147             #pod user servicable, you'll never call this directly.
148             #pod
149             #pod =cut
150              
151             sub __infer_constraint_generator {
152 172     172   309 my ($self) = @_;
153 172 50       8791 if($self->has_constraint_generator) {
154 172         7638 return $self->constraint_generator;
155             } else {
156             return sub {
157             ## I'm not sure about this stuff but everything seems to work
158 0     0   0 my $tc = shift @_;
159 0         0 my $merged_tc = [@$tc, @{$self->parent->type_constraints}];
  0         0  
160 0         0 $self->constraint->($merged_tc, @_);
161 0         0 };
162             }
163             }
164              
165             #pod =method compile_type_constraint
166             #pod
167             #pod hook into compile_type_constraint so we can set the correct validation rules.
168             #pod
169             #pod =cut
170              
171             around 'compile_type_constraint' => sub {
172             my ($compile_type_constraint, $self, @args) = @_;
173              
174             if($self->has_type_constraints) {
175             my $type_constraints = $self->type_constraints;
176             my $constraint = $self->generate_constraint_for($type_constraints);
177             $self->_set_constraint($constraint);
178             }
179              
180             return $self->$compile_type_constraint(@args);
181             };
182              
183             #pod =method create_child_type
184             #pod
185             #pod modifier to make sure we get the constraint_generator
186             #pod
187             #pod =cut
188              
189             around 'create_child_type' => sub {
190             my ($create_child_type, $self, %opts) = @_;
191             return $self->$create_child_type(
192             %opts,
193             constraint_generator => $self->__infer_constraint_generator,
194             );
195             };
196              
197             #pod =method is_a_type_of
198             #pod
199             #pod =method is_subtype_of
200             #pod
201             #pod =method equals
202             #pod
203             #pod Override the base class behavior.
204             #pod
205             #pod =cut
206              
207             sub equals {
208 1510     1510 1 116030 my ( $self, $type_or_name ) = @_;
209 1510 100       3530 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
210             or return;
211              
212 1509 100       24863 return unless $other->isa(__PACKAGE__);
213              
214             return (
215 238   100     8236 $self->parent->equals($other->parent)
216             and
217             $self->type_constraints_equals($other)
218             );
219             }
220              
221             sub is_a_type_of {
222 173     173 1 67466 my ( $self, $type_or_name ) = @_;
223 173 100       438 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
224             or return;
225              
226 172 100 100     2867 if ( $other->isa(__PACKAGE__) and @{ $other->type_constraints || [] }) {
  107 100       3432  
227 17 50       558 if ( $self->parent->is_a_type_of($other->parent) ) {
    0          
228 17         92 return $self->_type_constraints_op_all($other, "is_a_type_of");
229             } elsif ( $self->parent->is_a_type_of($other) ) {
230 0         0 return 1;
231             # FIXME compare?
232             } else {
233 0         0 return 0;
234             }
235             } else {
236 155         1366 return $self->SUPER::is_a_type_of($other);
237             }
238             }
239              
240             sub is_subtype_of {
241 1490     1490 1 789732 my ( $self, $type_or_name ) = @_;
242 1490 100       3840 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
243             or return;
244 1487 100       130140 if ( $other->isa(__PACKAGE__) ) {
245 97 100 100     2902 if ( $other->type_constraints and $self->type_constraints ) {
246 24 50       911 if ( $self->parent->is_a_type_of($other->parent) ) {
    0          
247             return (
248 24   100     144 $self->_type_constraints_op_all($other, "is_a_type_of")
249             and
250             $self->_type_constraints_op_any($other, "is_subtype_of")
251             );
252             } elsif ( $self->parent->is_a_type_of($other) ) {
253 0         0 return 1;
254             # FIXME compare?
255             } else {
256 0         0 return 0;
257             }
258             } else {
259 73 100       2948 if ( $self->type_constraints ) {
260 11 50       38 if ( $self->SUPER::is_subtype_of($other) ) {
261 11         90 return 1;
262             } else {
263 0         0 return;
264             }
265             } else {
266 62         1900 return $self->parent->is_subtype_of($other->parent);
267             }
268             }
269             } else {
270 1390         5570 return $self->SUPER::is_subtype_of($other);
271             }
272             }
273              
274             #pod =method type_constraints_equals
275             #pod
276             #pod Checks to see if the internal type constraints are equal.
277             #pod
278             #pod =cut
279              
280             sub type_constraints_equals {
281 167     167 1 2410 my ( $self, $other ) = @_;
282 167         344 $self->_type_constraints_op_all($other, "equals");
283             }
284              
285             sub _type_constraints_op_all {
286 208     208   325 my ($self, $other, $op) = @_;
287              
288 208 50       616 return unless $other->isa(__PACKAGE__);
289              
290 208 100       654 my @self_type_constraints = @{$self->type_constraints||[]};
  208         8421  
291 208 100       291 my @other_type_constraints = @{$other->type_constraints||[]};
  208         6869  
292              
293 208 100       631 return unless @self_type_constraints == @other_type_constraints;
294              
295             ## Incoming ay be either arrayref or hashref, need top compare both
296 187         430 while(@self_type_constraints) {
297 168         244 my $self_type_constraint = shift @self_type_constraints;
298 168         232 my $other_type_constraint = shift @other_type_constraints;
299              
300             $_ = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_)
301 168         536 for $self_type_constraint, $other_type_constraint;
302              
303 168         42023 my $result = $self_type_constraint->$op($other_type_constraint);
304 168 100       80327 return unless $result;
305             }
306              
307 159         910 return 1; ##If we get this far, everything is good.
308             }
309              
310             sub _type_constraints_op_any {
311 14     14   29 my ($self, $other, $op) = @_;
312              
313 14 50       68 return unless $other->isa(__PACKAGE__);
314              
315 14 50       73 my @self_type_constraints = @{$self->type_constraints||[]};
  14         572  
316 14 50       27 my @other_type_constraints = @{$other->type_constraints||[]};
  14         436  
317              
318 14 50       45 return unless @self_type_constraints == @other_type_constraints;
319              
320             ## Incoming ay be either arrayref or hashref, need top compare both
321 14         36 while(@self_type_constraints) {
322 42         58120 my $self_type_constraint = shift @self_type_constraints;
323 42         77 my $other_type_constraint = shift @other_type_constraints;
324              
325             $_ = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_)
326 42         213 for $self_type_constraint, $other_type_constraint;
327              
328 42 100       7287 return 1 if $self_type_constraint->$op($other_type_constraint);
329             }
330              
331 6         24440 return 0;
332             }
333              
334             #pod =method get_message
335             #pod
336             #pod Give you a better peek into what's causing the error. For now we stringify the
337             #pod incoming deep value with L<Devel::PartialDump> and pass that on to either your
338             #pod custom error message or the default one. In the future we'll try to provide a
339             #pod more complete stack trace of the actual offending elements
340             #pod
341             #pod =cut
342              
343             around 'get_message' => sub {
344             my ($get_message, $self, $value) = @_;
345             $value = Devel::PartialDump::dump($value)
346             if ref $value;
347             return $self->$get_message($value);
348             };
349              
350             #pod =head1 SEE ALSO
351             #pod
352             #pod The following modules or resources may be of interest.
353             #pod
354             #pod L<Moose>, L<Moose::Meta::TypeConstraint>
355             #pod
356             #pod =cut
357              
358 21     21   202 no Moose;
  21         55  
  21         174  
359             __PACKAGE__->meta->make_immutable(inline_constructor => 0);
360              
361             __END__
362              
363             =pod
364              
365             =encoding UTF-8
366              
367             =head1 NAME
368              
369             MooseX::Meta::TypeConstraint::Structured - Structured type constraints
370              
371             =head1 VERSION
372              
373             version 0.34
374              
375             =for stopwords parameterize
376              
377             =head1 DESCRIPTION
378              
379             A structure is a set of L<Moose::Meta::TypeConstraint> that are 'aggregated' in
380             such a way as that they are all applied to an incoming list of arguments. The
381             idea here is that a Type Constraint could be something like, "An C<Int> followed by
382             an C<Int> and then a C<Str>" and that this could be done so with a declaration like:
383              
384             Tuple[Int,Int,Str]; ## Example syntax
385              
386             So a structure is a list of type constraints (the C<Int,Int,Str> in the above
387             example) which are intended to function together.
388              
389             =head1 ATTRIBUTES
390              
391             =head2 type_constraints
392              
393             A list of L<Moose::Meta::TypeConstraint> objects.
394              
395             =head2 constraint_generator
396              
397             =head1 METHODS
398              
399             =head2 validate
400              
401             Messing with validate so that we can support nicer error messages.
402              
403             =head2 generate_constraint_for ($type_constraints)
404              
405             Given some type constraints, use them to generate validation rules for an ref
406             of values (to be passed at check time)
407              
408             =head2 parameterize (@type_constraints)
409              
410             Given a ref of type constraints, create a structured type.
411              
412             =head2 __infer_constraint_generator
413              
414             =head2 compile_type_constraint
415              
416             hook into compile_type_constraint so we can set the correct validation rules.
417              
418             =head2 create_child_type
419              
420             modifier to make sure we get the constraint_generator
421              
422             =head2 is_a_type_of
423              
424             =head2 is_subtype_of
425              
426             =head2 equals
427              
428             Override the base class behavior.
429              
430             =head2 type_constraints_equals
431              
432             Checks to see if the internal type constraints are equal.
433              
434             =head2 get_message
435              
436             Give you a better peek into what's causing the error. For now we stringify the
437             incoming deep value with L<Devel::PartialDump> and pass that on to either your
438             custom error message or the default one. In the future we'll try to provide a
439             more complete stack trace of the actual offending elements
440              
441             =for stopwords subref
442              
443             A subref or closure that contains the way we validate incoming values against
444             a set of type constraints.
445              
446             =for stopwords servicable
447              
448             This returns a CODEREF which generates a suitable constraint generator. Not
449             user servicable, you'll never call this directly.
450              
451             =head1 SEE ALSO
452              
453             The following modules or resources may be of interest.
454              
455             L<Moose>, L<Moose::Meta::TypeConstraint>
456              
457             =head1 AUTHORS
458              
459             =over 4
460              
461             =item *
462              
463             John Napiorkowski <jjnapiork@cpan.org>
464              
465             =item *
466              
467             Florian Ragwitz <rafl@debian.org>
468              
469             =item *
470              
471             יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
472              
473             =item *
474              
475             Tomas (t0m) Doran <bobtfish@bobtfish.net>
476              
477             =item *
478              
479             Robert Sedlacek <rs@474.at>
480              
481             =back
482              
483             =head1 COPYRIGHT AND LICENSE
484              
485             This software is copyright (c) 2008 by John Napiorkowski.
486              
487             This is free software; you can redistribute it and/or modify it under
488             the same terms as the Perl 5 programming language system itself.
489              
490             =cut