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   30058 use Moose;
  21         2109407  
  21         164  
6 21     21   159104 use Devel::PartialDump;
  21         1680372  
  21         145  
7 21     21   16601 use MooseX::Meta::TypeCoercion::Structured;
  21         317379  
  21         30922  
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   125509 my ($self) = @_;
58 233         1351 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   6147 my $message = shift @_;
71 18         57 $message =~s/MooseX::Types::Structured:://g;
72 18         825 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 229 my ($self, $type_constraints) = @_;
113 131         5784 return $self->constraint_generator->($self, $type_constraints);
114             }
115              
116             #pod =for :prelude
117             #pod =for stopwords parameterize
118             #pod
119             #pod =method parameterize (@type_constraints)
120             #pod
121             #pod Given a ref of type constraints, create a structured type.
122             #pod
123             #pod =cut
124              
125             sub parameterize {
126 130     130 1 271936 my ($self, @type_constraints) = @_;
127 130         267 my $class = ref $self;
128 130         4431 my $name = $self->name .'['. join(',', map {"$_"} @type_constraints) .']';
  325         7496  
129 130         6785 my $constraint_generator = $self->__infer_constraint_generator;
130              
131 130         687 return $class->new(
132             name => $name,
133             parent => $self,
134             type_constraints => \@type_constraints,
135             constraint_generator => $constraint_generator,
136             );
137             }
138              
139             #pod =method __infer_constraint_generator
140             #pod
141             #pod =for stopwords servicable
142             #pod
143             #pod This returns a CODEREF which generates a suitable constraint generator. Not
144             #pod user servicable, you'll never call this directly.
145             #pod
146             #pod =cut
147              
148             sub __infer_constraint_generator {
149 172     172   312 my ($self) = @_;
150 172 50       9234 if($self->has_constraint_generator) {
151 172         7700 return $self->constraint_generator;
152             } else {
153             return sub {
154             ## I'm not sure about this stuff but everything seems to work
155 0     0   0 my $tc = shift @_;
156 0         0 my $merged_tc = [@$tc, @{$self->parent->type_constraints}];
  0         0  
157 0         0 $self->constraint->($merged_tc, @_);
158 0         0 };
159             }
160             }
161              
162             #pod =method compile_type_constraint
163             #pod
164             #pod hook into compile_type_constraint so we can set the correct validation rules.
165             #pod
166             #pod =cut
167              
168             around 'compile_type_constraint' => sub {
169             my ($compile_type_constraint, $self, @args) = @_;
170              
171             if($self->has_type_constraints) {
172             my $type_constraints = $self->type_constraints;
173             my $constraint = $self->generate_constraint_for($type_constraints);
174             $self->_set_constraint($constraint);
175             }
176              
177             return $self->$compile_type_constraint(@args);
178             };
179              
180             #pod =method create_child_type
181             #pod
182             #pod modifier to make sure we get the constraint_generator
183             #pod
184             #pod =cut
185              
186             around 'create_child_type' => sub {
187             my ($create_child_type, $self, %opts) = @_;
188             return $self->$create_child_type(
189             %opts,
190             constraint_generator => $self->__infer_constraint_generator,
191             );
192             };
193              
194             #pod =method is_a_type_of
195             #pod
196             #pod =method is_subtype_of
197             #pod
198             #pod =method equals
199             #pod
200             #pod Override the base class behavior.
201             #pod
202             #pod =cut
203              
204             sub equals {
205 1516     1516 1 125828 my ( $self, $type_or_name ) = @_;
206 1516 100       3953 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
207             or return;
208              
209 1515 100       26225 return unless $other->isa(__PACKAGE__);
210              
211             return (
212 238   100     8897 $self->parent->equals($other->parent)
213             and
214             $self->type_constraints_equals($other)
215             );
216             }
217              
218             sub is_a_type_of {
219 173     173 1 73139 my ( $self, $type_or_name ) = @_;
220 173 100       449 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
221             or return;
222              
223 172 100 100     3053 if ( $other->isa(__PACKAGE__) and @{ $other->type_constraints || [] }) {
  107 100       3827  
224 17 50       577 if ( $self->parent->is_a_type_of($other->parent) ) {
    0          
225 17         89 return $self->_type_constraints_op_all($other, "is_a_type_of");
226             } elsif ( $self->parent->is_a_type_of($other) ) {
227 0         0 return 1;
228             # FIXME compare?
229             } else {
230 0         0 return 0;
231             }
232             } else {
233 155         1477 return $self->SUPER::is_a_type_of($other);
234             }
235             }
236              
237             sub is_subtype_of {
238 1503     1503 1 837791 my ( $self, $type_or_name ) = @_;
239 1503 100       4520 my $other = Moose::Util::TypeConstraints::find_type_constraint($type_or_name)
240             or return;
241 1500 100       139047 if ( $other->isa(__PACKAGE__) ) {
242 97 100 100     3343 if ( $other->type_constraints and $self->type_constraints ) {
243 24 50       862 if ( $self->parent->is_a_type_of($other->parent) ) {
    0          
244             return (
245 24   100     152 $self->_type_constraints_op_all($other, "is_a_type_of")
246             and
247             $self->_type_constraints_op_any($other, "is_subtype_of")
248             );
249             } elsif ( $self->parent->is_a_type_of($other) ) {
250 0         0 return 1;
251             # FIXME compare?
252             } else {
253 0         0 return 0;
254             }
255             } else {
256 73 100       3394 if ( $self->type_constraints ) {
257 11 50       41 if ( $self->SUPER::is_subtype_of($other) ) {
258 11         113 return 1;
259             } else {
260 0         0 return;
261             }
262             } else {
263 62         2199 return $self->parent->is_subtype_of($other->parent);
264             }
265             }
266             } else {
267 1403         6351 return $self->SUPER::is_subtype_of($other);
268             }
269             }
270              
271             #pod =method type_constraints_equals
272             #pod
273             #pod Checks to see if the internal type constraints are equal.
274             #pod
275             #pod =cut
276              
277             sub type_constraints_equals {
278 167     167 1 2569 my ( $self, $other ) = @_;
279 167         375 $self->_type_constraints_op_all($other, "equals");
280             }
281              
282             sub _type_constraints_op_all {
283 208     208   330 my ($self, $other, $op) = @_;
284              
285 208 50       678 return unless $other->isa(__PACKAGE__);
286              
287 208 100       701 my @self_type_constraints = @{$self->type_constraints||[]};
  208         9189  
288 208 100       293 my @other_type_constraints = @{$other->type_constraints||[]};
  208         7621  
289              
290 208 100       719 return unless @self_type_constraints == @other_type_constraints;
291              
292             ## Incoming ay be either arrayref or hashref, need top compare both
293 187         445 while(@self_type_constraints) {
294 168         255 my $self_type_constraint = shift @self_type_constraints;
295 168         250 my $other_type_constraint = shift @other_type_constraints;
296              
297             $_ = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_)
298 168         636 for $self_type_constraint, $other_type_constraint;
299              
300 168         44834 my $result = $self_type_constraint->$op($other_type_constraint);
301 168 100       85391 return unless $result;
302             }
303              
304 159         898 return 1; ##If we get this far, everything is good.
305             }
306              
307             sub _type_constraints_op_any {
308 14     14   27 my ($self, $other, $op) = @_;
309              
310 14 50       63 return unless $other->isa(__PACKAGE__);
311              
312 14 50       71 my @self_type_constraints = @{$self->type_constraints||[]};
  14         645  
313 14 50       26 my @other_type_constraints = @{$other->type_constraints||[]};
  14         499  
314              
315 14 50       47 return unless @self_type_constraints == @other_type_constraints;
316              
317             ## Incoming ay be either arrayref or hashref, need top compare both
318 14         39 while(@self_type_constraints) {
319 42         60907 my $self_type_constraint = shift @self_type_constraints;
320 42         75 my $other_type_constraint = shift @other_type_constraints;
321              
322             $_ = Moose::Util::TypeConstraints::find_or_create_isa_type_constraint($_)
323 42         152 for $self_type_constraint, $other_type_constraint;
324              
325 42 100       7927 return 1 if $self_type_constraint->$op($other_type_constraint);
326             }
327              
328 6         26326 return 0;
329             }
330              
331             #pod =method get_message
332             #pod
333             #pod Give you a better peek into what's causing the error. For now we stringify the
334             #pod incoming deep value with L<Devel::PartialDump> and pass that on to either your
335             #pod custom error message or the default one. In the future we'll try to provide a
336             #pod more complete stack trace of the actual offending elements
337             #pod
338             #pod =cut
339              
340             around 'get_message' => sub {
341             my ($get_message, $self, $value) = @_;
342             $value = Devel::PartialDump::dump($value)
343             if ref $value;
344             return $self->$get_message($value);
345             };
346              
347             #pod =head1 SEE ALSO
348             #pod
349             #pod The following modules or resources may be of interest.
350             #pod
351             #pod L<Moose>, L<Moose::Meta::TypeConstraint>
352             #pod
353             #pod =cut
354              
355 21     21   167 no Moose;
  21         43  
  21         132  
356             __PACKAGE__->meta->make_immutable(inline_constructor => 0);
357              
358             __END__
359              
360             =pod
361              
362             =encoding UTF-8
363              
364             =head1 NAME
365              
366             MooseX::Meta::TypeConstraint::Structured - Structured type constraints
367              
368             =head1 VERSION
369              
370             version 0.35
371              
372             =for stopwords parameterize
373              
374             =head1 DESCRIPTION
375              
376             A structure is a set of L<Moose::Meta::TypeConstraint> that are 'aggregated' in
377             such a way as that they are all applied to an incoming list of arguments. The
378             idea here is that a Type Constraint could be something like, "An C<Int> followed by
379             an C<Int> and then a C<Str>" and that this could be done so with a declaration like:
380              
381             Tuple[Int,Int,Str]; ## Example syntax
382              
383             So a structure is a list of type constraints (the C<Int,Int,Str> in the above
384             example) which are intended to function together.
385              
386             =head1 ATTRIBUTES
387              
388             =head2 type_constraints
389              
390             A list of L<Moose::Meta::TypeConstraint> objects.
391              
392             =head2 constraint_generator
393              
394             =head1 METHODS
395              
396             =head2 validate
397              
398             Messing with validate so that we can support nicer error messages.
399              
400             =head2 generate_constraint_for ($type_constraints)
401              
402             Given some type constraints, use them to generate validation rules for an ref
403             of values (to be passed at check time)
404              
405             =head2 parameterize (@type_constraints)
406              
407             Given a ref of type constraints, create a structured type.
408              
409             =head2 __infer_constraint_generator
410              
411             =head2 compile_type_constraint
412              
413             hook into compile_type_constraint so we can set the correct validation rules.
414              
415             =head2 create_child_type
416              
417             modifier to make sure we get the constraint_generator
418              
419             =head2 is_a_type_of
420              
421             =head2 is_subtype_of
422              
423             =head2 equals
424              
425             Override the base class behavior.
426              
427             =head2 type_constraints_equals
428              
429             Checks to see if the internal type constraints are equal.
430              
431             =head2 get_message
432              
433             Give you a better peek into what's causing the error. For now we stringify the
434             incoming deep value with L<Devel::PartialDump> and pass that on to either your
435             custom error message or the default one. In the future we'll try to provide a
436             more complete stack trace of the actual offending elements
437              
438             =for stopwords subref
439              
440             A subref or closure that contains the way we validate incoming values against
441             a set of type constraints.
442              
443             =for stopwords servicable
444              
445             This returns a CODEREF which generates a suitable constraint generator. Not
446             user servicable, you'll never call this directly.
447              
448             =head1 SEE ALSO
449              
450             The following modules or resources may be of interest.
451              
452             L<Moose>, L<Moose::Meta::TypeConstraint>
453              
454             =head1 SUPPORT
455              
456             Bugs may be submitted through L<the RT bug tracker|https://rt.cpan.org/Public/Dist/Display.html?Name=MooseX-Types-Structured>
457             (or L<bug-MooseX-Types-Structured@rt.cpan.org|mailto:bug-MooseX-Types-Structured@rt.cpan.org>).
458              
459             There is also a mailing list available for users of this distribution, at
460             L<http://lists.perl.org/list/moose.html>.
461              
462             There is also an irc channel available for users of this distribution, at
463             L<irc://irc.perl.org/#moose>.
464              
465             =head1 AUTHORS
466              
467             =over 4
468              
469             =item *
470              
471             John Napiorkowski <jjnapiork@cpan.org>
472              
473             =item *
474              
475             Florian Ragwitz <rafl@debian.org>
476              
477             =item *
478              
479             יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
480              
481             =item *
482              
483             Tomas (t0m) Doran <bobtfish@bobtfish.net>
484              
485             =item *
486              
487             Robert Sedlacek <rs@474.at>
488              
489             =back
490              
491             =head1 COPYRIGHT AND LICENSE
492              
493             This software is copyright (c) 2008 by John Napiorkowski.
494              
495             This is free software; you can redistribute it and/or modify it under
496             the same terms as the Perl 5 programming language system itself.
497              
498             =cut