ddf09ef80fb5d5dbd6c776acad52b78670a56290
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Types / Structured.pm
1 package MooseX::Types::Structured;
2
3 use 5.008;
4 use Moose::Util::TypeConstraints;
5 use MooseX::Meta::TypeConstraint::Structured;
6 use MooseX::Types -declare => [qw(Dict Tuple Optional)];
7
8 our $VERSION = '0.07';
9 our $AUTHORITY = 'cpan:JJNAPIORK';
10
11 =head1 NAME
12
13 MooseX::Types::Structured - Structured Type Constraints for Moose
14
15 =head1 SYNOPSIS
16
17 The following is example usage for this module.
18
19     package Person;
20         
21     use Moose;
22     use MooseX::Types::Moose qw(Str Int HashRef);
23     use MooseX::Types::Structured qw(Dict Tuple Optional);
24
25     ## A name has a first and last part, but middle names are not required
26     has name => (
27         isa=>Dict[
28             first => Str,
29             last => Str,
30             middle => Optional[Str],
31         ],
32     );
33     
34     ## description is a string field followed by a HashRef of tagged data.
35     has description => (
36       isa=>Tuple[
37         Str,
38         Optional[HashRef],
39      ],
40     );
41
42 Then you can instantiate this class with something like:
43
44     my $john = Person->new(
45         name => {
46             first => 'John',
47             middle => 'James'
48             last => 'Napiorkowski',
49         },
50         description => [
51             'A cool guy who loves Perl and Moose.', {
52                 married_to => 'Vanessa Li',
53                 born_in => 'USA',
54             };
55         ]
56     );
57
58 Or with:
59
60     my $vanessa = Person->new(
61         name => {
62             first => 'Vanessa',
63             last => 'Li'
64         },
65         description => ['A great student!'],
66     );
67
68 But all of these would cause a constraint error for the 'name' attribute:
69
70     ## Value for 'name' not a HashRef
71     Person->new( name => 'John' );
72     
73     ## Value for 'name' has incorrect hash key and missing required keys
74     Person->new( name => {
75         first_name => 'John'
76     });
77     
78     ## Also incorrect keys
79     Person->new( name => {
80         first_name => 'John',
81         age => 39,
82     });
83     
84     ## key 'middle' incorrect type, should be a Str not a ArrayRef
85     Person->new( name => {
86         first => 'Vanessa',
87         middle => [1,2],
88         last => 'Li',
89     }); 
90
91 And these would cause a constraint error for the 'description' attribute:
92
93     ## Should be an ArrayRef
94     Person->new( description => 'Hello I am a String' );
95     
96     ## First element must be a string not a HashRef.
97     Person->new (description => [{
98         tag1 => 'value1',
99         tag2 => 'value2'
100     }]);
101
102 Please see the test cases for more examples.
103
104 =head1 DESCRIPTION
105
106 A structured type constraint is a standard container L<Moose> type constraint,
107 such as an ArrayRef or HashRef, which has been enhanced to allow you to
108 explicitly name all the allowed type constraints inside the structure.  The
109 generalized form is:
110
111     TypeConstraint[@TypeParameters or %TypeParameters]
112
113 Where 'TypeParameters' is an array reference or hash references of 
114 L<Moose::Meta::TypeConstraint> objects.
115
116 This type library enables structured type constraints. It is built on top of the
117 L<MooseX::Types> library system, so you should review the documentation for that
118 if you are not familiar with it.
119
120 =head2 Comparing Parameterized types to Structured types
121
122 Parameterized constraints are built into core Moose and you are probably already
123 familar with the type constraints 'HashRef' and 'ArrayRef'.  Structured types
124 have similar functionality, so their syntax is likewise similar. For example,
125 you could define a parameterized constraint like:
126
127     subtype ArrayOfInts,
128      as Arrayref[Int];
129
130 which would constrain a value to something like [1,2,3,...] and so on.  On the
131 other hand, a structured type constraint explicitly names all it's allowed
132 'internal' type parameter constraints.  For the example:
133
134     subtype StringFollowedByInt,
135      as Tuple[Str,Int];
136         
137 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
138 would fail, as well as ['hello', 111, 'world'] and so on.  Here's another
139 example:
140
141     subtype StringIntOptionalHashRef,
142      as Tuple[
143         Str, Int,
144         Optional[HashRef]
145      ];
146      
147 This defines a type constraint that validates values like:
148
149     ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
150     ['World', 200];
151     
152 Notice that the last type constraint in the structure is optional.  This is
153 enabled via the helper Optional type constraint, which is a variation of the
154 core Moose type constraint 'Maybe'.  The main difference is that Optional type
155 constraints are required to validate if they exist, while 'Maybe' permits 
156 undefined values.  So the following example would not validate:
157
158     StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
159     
160 Please note the subtle difference between undefined and null.  If you wish to
161 allow both null and undefined, you should use the core Moose 'Maybe' type
162 constraint instead:
163
164     use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
165     use MooseX::Types::Moose qw(Maybe);
166     use MooseX::Types::Structured qw(Tuple);
167
168     subtype StringIntMaybeHashRef,
169      as Tuple[
170         Str, Int, Maybe[HashRef]
171      ];
172
173 This would validate the following:
174
175     ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
176     ['World', 200, undef];    
177     ['World', 200];
178
179 Structured constraints are not limited to arrays.  You can define a structure
180 against a HashRef with 'Dict' as in this example:
181
182     subtype FirstNameLastName,
183      as Dict[
184         firstname => Str,
185         lastname => Str,
186      ];
187
188 This would constrain a HashRef to something like:
189
190     {firstname => 'Christopher', lastname= > 'Parsons'};
191     
192 but all the following would fail validation:
193
194     ## Incorrect keys
195     {first => 'Christopher', last => 'Parsons'};
196     
197     ## Too many keys
198     {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
199     
200     ## Not a HashRef
201     ['Christopher', 'Christopher']; 
202
203 These structures can be as simple or elaborate as you wish.  You can even
204 combine various structured, parameterized and simple constraints all together:
205
206     subtype Crazy,
207      as Tuple[
208         Int,
209         Dict[name=>Str, age=>Int],
210         ArrayRef[Int]
211      ];
212         
213 Which would match "[1, {name=>'John', age=>25},[10,11,12]]".  Please notice how
214 the type parameters can be visually arranged to your liking and to improve the
215 clarity of your meaning.  You don't need to run then altogether onto a single
216 line.
217
218 =head2 Alternatives
219
220 You should exercise some care as to whether or not your complex structured
221 constraints would be better off contained by a real object as in the following
222 example:
223
224     package MyApp::MyStruct;
225     use Moose;
226     
227     ## lazy way to make a bunch of attributes
228     has $_ for qw(full_name age_in_years);
229     
230     package MyApp::MyClass;
231     use Moose;
232     
233     has person => (isa => 'MyApp::MyStruct');           
234     
235     my $instance = MyApp::MyClass->new(
236         person=>MyApp::MyStruct->new(
237             full_name => 'John',
238             age_in_years => 39,
239         ),
240     );
241         
242 This method may take some additional time to setup but will give you more
243 flexibility.  However, structured constraints are highly compatible with this
244 method, granting some interesting possibilities for coercion.  Try:
245
246     package MyApp::MyClass;
247     
248     use Moose;
249     use MyApp::MyStruct;
250     
251     ## It's recommended your type declarations live in a separate class in order
252     ## to promote reusability and clarity.  Inlined here for brevity.
253     
254     use MooseX::Types::DateTime qw(DateTime);
255     use MooseX::Types -declare [qw(MyStruct)];
256     use MooseX::Types::Moose qw(Str Int);
257     use MooseX::Types::Structured qw(Dict);
258
259     ## Use class_type to create an ISA type constraint if your object doesn't
260     ## inherit from Moose::Object.
261     class_type 'MyApp::MyStruct';
262
263     ## Just a shorter version really.
264     subtype MyStruct,
265      as 'MyApp::MyStruct';
266     
267     ## Add the coercions.
268     coerce MyStruct,
269      from Dict[
270         full_name=>Str,
271         age_in_years=>Int
272      ], via {
273         MyApp::MyStruct->new(%$_);
274      },
275      from Dict[
276         lastname=>Str,
277         firstname=>Str,
278         dob=>DateTime
279      ], via {
280         my $name = $_->{firstname} .' '. $_->{lastname};
281         my $age = DateTime->now - $_->{dob};
282         
283         MyApp::MyStruct->new(
284             full_name=>$name,
285             age_in_years=>$age->years,
286         );
287      };
288      
289     has person => (isa=>MyStruct);      
290      
291 This would allow you to instantiate with something like:
292
293     my $obj = MyApp::MyClass->new( person => {
294         full_name=>'John Napiorkowski',
295         age_in_years=>39,
296     });
297     
298 Or even:
299
300     my $obj = MyApp::MyClass->new( person => {
301         lastname=>'John',
302         firstname=>'Napiorkowski',
303         dob=>DateTime->new(year=>1969),
304     });
305
306 If you are not familiar with how coercions work, check out the L<Moose> cookbook
307 entry L<Moose::Cookbook::Recipe5> for an explanation.  The section L</Coercions>
308 has additional examples and discussion.
309
310 =head2 Subtyping a Structured type constraint
311
312 You need to exercise some care when you try to subtype a structured type as in
313 this example:
314
315     subtype Person,
316      as Dict[name => Str];
317          
318     subtype FriendlyPerson,
319      as Person[
320         name => Str,
321         total_friends => Int,
322      ];
323          
324 This will actually work BUT you have to take care that the subtype has a
325 structure that does not contradict the structure of it's parent.  For now the
326 above works, but I will clarify the syntax for this at a future point, so
327 it's recommended to avoid (should not really be needed so much anyway).  For
328 now this is supported in an EXPERIMENTAL way.  Your thoughts, test cases and
329 patches are welcomed for discussion.  If you find a good use for this, please
330 let me know.
331
332 =head2 Coercions
333
334 Coercions currently work for 'one level' deep.  That is you can do:
335
336     subtype Person,
337      as Dict[
338         name => Str,
339         age => Int
340     ];
341     
342     subtype Fullname,
343      as Dict[
344         first => Str,
345         last => Str
346      ];
347     
348     coerce Person,
349      ## Coerce an object of a particular class
350      from BlessedPersonObject, via {
351         +{
352             name=>$_->name,
353             age=>$_->age,
354         };
355      },
356      
357      ## Coerce from [$name, $age]
358      from ArrayRef, via {
359         +{
360             name=>$_->[0],
361             age=>$_->[1],
362         },
363      },
364      ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
365      from Dict[fullname=>Fullname, dob=>DateTime], via {
366         my $age = $_->dob - DateTime->now;
367         my $firstn = $_->{fullname}->{first};
368         my $lastn = $_->{fullname}->{last}
369         +{
370             name => $_->{fullname}->{first} .' '. ,
371             age =>$age->years
372         }
373      };
374          
375 And that should just work as expected.  However, if there are any 'inner'
376 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
377 won't currently get activated.
378
379 Please see the test '07-coerce.t' for a more detailed example.  Discussion on
380 extending coercions to support this welcome on the Moose development channel or
381 mailing list.
382
383 =head2 Recursion
384
385 Newer versions of L<MooseX::Types> support recursive type constraints.  That is
386 you can include a type constraint as a contained type constraint of itself.  For
387 example:
388
389         subtype Person,
390          as Dict[
391                 name=>Str,
392                 friends=>Optional[
393                         ArrayRef[Person]
394                 ],
395          ];
396          
397 This would declare a Person subtype that contains a name and an optional
398 ArrayRef of Persons who are friends as in:
399
400         {
401                 name => 'Mike',
402                 friends => [
403                         { name => 'John' },
404                         { name => 'Vincent' },
405                         {
406                                 name => 'Tracey',
407                                 friends => [
408                                         { name => 'Stephenie' },
409                                         { name => 'Ilya' },
410                                 ],
411                         },
412                 ],
413         };
414
415 Please take care to make sure the recursion node is either Optional, or declare
416 a Union with an non recursive option such as:
417
418         subtype Value
419          as Tuple[
420                 Str,
421                 Str|Tuple,
422          ];
423          
424 Which validates:
425
426         [
427                 'Hello', [
428                         'World', [
429                                 'Is', [
430                                         'Getting',
431                                         'Old',
432                                 ],
433                         ],
434                 ],
435         ];
436
437 Otherwise you will define a subtype thatis impossible to validate since it is 
438 infinitely recursive.  For more information about defining recursive types,
439 please see the documentation in L<MooseX::Types> and the test cases.
440
441 =head1 TYPE CONSTRAINTS
442
443 This type library defines the following constraints.
444
445 =head2 Tuple[@constraints]
446
447 This defines an ArrayRef based constraint which allows you to validate a specific
448 list of contained constraints.  For example:
449
450     Tuple[Int,Str]; ## Validates [1,'hello']
451     Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
452
453 =head2 Dict[%constraints]
454
455 This defines a HashRef based constraint which allowed you to validate a specific
456 hashref.  For example:
457
458     Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
459
460 =head2 Optional[$constraint]
461
462 This is primarily a helper constraint for Dict and Tuple type constraints.  What
463 this allows if for you to assert that a given type constraint is allowed to be
464 null (but NOT undefined).  If the value is null, then the type constraint passes
465 but if the value is defined it must validate against the type constraint.  This
466 makes it easy to make a Dict where one or more of the keys doesn't have to exist
467 or a tuple where some of the values are not required.  For example:
468
469     subtype Name() => as Dict[
470         first=>Str,
471         last=>Str,
472         middle=>Optional[Str],
473     ];
474         
475 Creates a constraint that validates against a hashref with the keys 'first' and
476 'last' being strings and required while an optional key 'middle' is must be a
477 string if it appears but doesn't have to appear.  So in this case both the
478 following are valid:
479
480     {first=>'John', middle=>'James', last=>'Napiorkowski'}
481     {first=>'Vanessa', last=>'Li'}
482     
483 =head1 EXAMPLES
484
485 Here are some additional example usage for structured types.  All examples can
486 be found also in the 't/examples.t' test.  Your contributions are also welcomed.
487
488 =head2 Normalize a HashRef
489
490 You need a hashref to conform to a canonical structure but are required accept a
491 bunch of different incoming structures.  You can normalize using the Dict type
492 constraint and coercions.  This example also shows structured types mixed which
493 other MooseX::Types libraries.
494
495     package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
496     
497     use Moose;
498     use DateTime;
499     
500     use MooseX::Types::Structured qw(Dict Tuple);
501     use MooseX::Types::DateTime qw(DateTime);
502     use MooseX::Types::Moose qw(Int Str Object);
503     use MooseX::Types -declare => [qw(Name Age Person)];
504      
505     subtype Person,
506      as Dict[
507         name=>Str,
508         age=>Int,
509      ];
510     
511     coerce Person,
512      from Dict[
513         first=>Str, 
514         last=>Str, 
515         years=>Int,
516      ], via { +{
517         name => "$_->{first} $_->{last}",
518         age => $_->{years},
519      }},
520      from Dict[
521         fullname=>Dict[
522                 last=>Str, 
523                 first=>Str,
524         ], 
525         dob=>DateTime,
526      ],
527      ## DateTime needs to be inside of single quotes here to disambiguate the
528      ## class package from the DataTime type constraint imported via the
529      ## line "use MooseX::Types::DateTime qw(DateTime);"
530      via { +{
531         name => "$_->{fullname}{first} $_->{fullname}{last}",
532         age => ($_->{dob} - 'DateTime'->now)->years,
533      }};
534      
535     has person => (is=>'rw', isa=>Person, coerce=>1);
536     
537 And now you can instantiate with all the following:
538
539     __PACKAGE__->new(
540         name=>'John Napiorkowski',
541         age=>39,
542     );
543         
544     __PACKAGE__->new(
545         first=>'John',
546         last=>'Napiorkowski',
547         years=>39,
548     );
549     
550     __PACKAGE__->new(
551         fullname => {
552             first=>'John',
553             last=>'Napiorkowski'
554         },
555         dob => 'DateTime'->new(
556             year=>1969,
557             month=>2,
558             day=>13
559         ),
560     );
561     
562 This technique is a way to support various ways to instantiate your class in a
563 clean and declarative way.
564
565 =cut
566
567 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
568         MooseX::Meta::TypeConstraint::Structured->new(
569                 name => "MooseX::Types::Structured::Tuple" ,
570                 parent => find_type_constraint('ArrayRef'),
571                 constraint_generator=> sub { 
572                         ## Get the constraints and values to check
573             my ($type_constraints, $values) = @_;
574                         my @type_constraints = defined $type_constraints ?
575              @$type_constraints : ();
576             
577             my $overflow_handler;
578             if(ref $type_constraints[-1] eq 'CODE') {
579                 $overflow_handler = pop @type_constraints;
580             }
581             
582                         my @values = defined $values ? @$values: ();
583                         ## Perform the checking
584                         while(@type_constraints) {
585                                 my $type_constraint = shift @type_constraints;
586                                 if(@values) {
587                                         my $value = shift @values;
588                                         unless($type_constraint->check($value)) {
589                                                 return;
590                                         }                               
591                                 } else {
592                     ## Test if the TC supports null values
593                                         unless($type_constraint->check()) {
594                                                 return;
595                                         }
596                                 }
597                         }
598                         ## Make sure there are no leftovers.
599                         if(@values) {
600                 if($overflow_handler) {
601                     return $overflow_handler->(@values);
602                 } else {
603                     return;
604                 }
605                         } elsif(@type_constraints) {
606                 warn "I failed due to left over TC";
607                                 return;
608                         } else {
609                                 return 1;
610                         }
611                 }
612         )
613 );
614         
615 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
616         MooseX::Meta::TypeConstraint::Structured->new(
617                 name => "MooseX::Types::Structured::Dict",
618                 parent => find_type_constraint('HashRef'),
619                 constraint_generator=> sub { 
620                         ## Get the constraints and values to check
621             my ($type_constraints, $values) = @_;
622                         my @type_constraints = defined $type_constraints ?
623              @$type_constraints : ();
624             
625             my $overflow_handler;
626             if(ref $type_constraints[-1] eq 'CODE') {
627                 $overflow_handler = pop @type_constraints;
628             } 
629             my (%type_constraints) = @type_constraints;
630                         my %values = defined $values ? %$values: ();
631                         ## Perform the checking
632                         while(%type_constraints) {
633                                 my($key, $type_constraint) = each %type_constraints;
634                                 delete $type_constraints{$key};
635                                 if(exists $values{$key}) {
636                                         my $value = $values{$key};
637                                         delete $values{$key};
638                                         unless($type_constraint->check($value)) {
639                                                 return;
640                                         }
641                                 } else {
642                     ## Test to see if the TC supports null values
643                                         unless($type_constraint->check()) {
644                                                 return;
645                                         }
646                                 }
647                         }
648                         ## Make sure there are no leftovers.
649                         if(%values) { 
650                 if($overflow_handler) {
651                     return $overflow_handler->(%values);
652                 } else {
653                     return;
654                 }
655                         } elsif(%type_constraints) {
656                                 return;
657                         } else {
658                                 return 1;
659                         }
660                 },
661         )
662 );
663
664 OPTIONAL: {
665     my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
666         name => 'MooseX::Types::Structured::Optional',
667         package_defined_in => __PACKAGE__,
668         parent => find_type_constraint('Item'),
669         constraint => sub { 1 },
670         constraint_generator => sub {
671             my ($type_parameter, @args) = @_;
672             my $check = $type_parameter->_compiled_type_constraint();
673             return sub {
674                 my (@args) = @_;
675                 ## Does the arg exist?  Something exists if it's a 'real' value
676                 ## or if it is set to undef.
677                 if(exists($args[0])) {
678                     ## If it exists, we need to validate it
679                     $check->($args[0]);
680                 } else {
681                     ## But it's is okay if the value doesn't exists
682                     return 1;
683                 }
684             }
685         }
686     );
687
688     Moose::Util::TypeConstraints::register_type_constraint($Optional);
689     Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
690 }
691
692
693 =head1 SEE ALSO
694
695 The following modules or resources may be of interest.
696
697 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
698 L<MooseX::Meta::TypeConstraint::Structured>
699
700 =head1 TODO
701
702 Here's a list of stuff I would be happy to get volunteers helping with:
703
704 All POD examples need test cases in t/documentation/*.t
705 Want to break out the examples section to a separate cookbook style POD.
706 Want more examples and best practice / usage guidance for authors
707 Need to clarify deep coercions, 
708 Need to clarify subtypes of subtypes.
709
710 =head1 AUTHOR
711
712 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
713
714 =head1 COPYRIGHT & LICENSE
715
716 This program is free software; you can redistribute it and/or modify
717 it under the same terms as Perl itself.
718
719 =cut
720         
721 1;