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