Less trailing whitespace.
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Types / Structured.pm
CommitLineData
d24da8ec 1package MooseX::Types::Structured;
2
98336987 3use 5.008;
c116e19a 4
6c2f284c 5use Moose::Util::TypeConstraints;
a30fa891 6use MooseX::Meta::TypeConstraint::Structured;
2f8e2a40 7use MooseX::Types::Structured::OverflowHandler;
e327145a 8use MooseX::Types -declare => [qw(Dict Tuple Optional)];
c116e19a 9use Sub::Exporter -setup => { exports => [ qw(Dict Tuple Optional slurpy) ] };
7559b71f 10use Devel::PartialDump;
2f8e2a40 11use Scalar::Util qw(blessed);
011bacc6 12
f1288bfd 13our $VERSION = '0.19';
d24da8ec 14our $AUTHORITY = 'cpan:JJNAPIORK';
15
16=head1 NAME
17
af1d00c9 18MooseX::Types::Structured - Structured Type Constraints for Moose
d24da8ec 19
20=head1 SYNOPSIS
21
af1d00c9 22The following is example usage for this module.
6c2f284c 23
07a8693b 24 package Person;
91416e91 25
af1d00c9 26 use Moose;
07a8693b 27 use MooseX::Types::Moose qw(Str Int HashRef);
28 use MooseX::Types::Structured qw(Dict Tuple Optional);
190a34eb 29
30 ## A name has a first and last part, but middle names are not required
31 has name => (
32 isa=>Dict[
07a8693b 33 first => Str,
34 last => Str,
35 middle => Optional[Str],
190a34eb 36 ],
37 );
91416e91 38
07a8693b 39 ## description is a string field followed by a HashRef of tagged data.
40 has description => (
41 isa=>Tuple[
42 Str,
43 Optional[HashRef],
44 ],
45 );
af1d00c9 46
7caf630f 47 ## Remainder of your class attributes and methods
48
6c2f284c 49Then you can instantiate this class with something like:
50
07a8693b 51 my $john = Person->new(
190a34eb 52 name => {
07a8693b 53 first => 'John',
54 middle => 'James'
55 last => 'Napiorkowski',
190a34eb 56 },
07a8693b 57 description => [
58 'A cool guy who loves Perl and Moose.', {
59 married_to => 'Vanessa Li',
60 born_in => 'USA',
61 };
62 ]
190a34eb 63 );
22727dd5 64
65Or with:
66
07a8693b 67 my $vanessa = Person->new(
d87e8b74 68 name => {
07a8693b 69 first => 'Vanessa',
70 last => 'Li'
d87e8b74 71 },
07a8693b 72 description => ['A great student!'],
d87e8b74 73 );
d24da8ec 74
d87e8b74 75But all of these would cause a constraint error for the 'name' attribute:
6c2f284c 76
07a8693b 77 ## Value for 'name' not a HashRef
78 Person->new( name => 'John' );
91416e91 79
07a8693b 80 ## Value for 'name' has incorrect hash key and missing required keys
81 Person->new( name => {
82 first_name => 'John'
83 });
91416e91 84
07a8693b 85 ## Also incorrect keys
86 Person->new( name => {
87 first_name => 'John',
88 age => 39,
89 });
91416e91 90
07a8693b 91 ## key 'middle' incorrect type, should be a Str not a ArrayRef
92 Person->new( name => {
93 first => 'Vanessa',
94 middle => [1,2],
95 last => 'Li',
91416e91 96 });
07a8693b 97
98And these would cause a constraint error for the 'description' attribute:
99
100 ## Should be an ArrayRef
101 Person->new( description => 'Hello I am a String' );
91416e91 102
07a8693b 103 ## First element must be a string not a HashRef.
104 Person->new (description => [{
105 tag1 => 'value1',
106 tag2 => 'value2'
107 }]);
108
6c2f284c 109Please see the test cases for more examples.
d24da8ec 110
111=head1 DESCRIPTION
112
22727dd5 113A structured type constraint is a standard container L<Moose> type constraint,
07a8693b 114such as an ArrayRef or HashRef, which has been enhanced to allow you to
115explicitly name all the allowed type constraints inside the structure. The
af1d00c9 116generalized form is:
117
07a8693b 118 TypeConstraint[@TypeParameters or %TypeParameters]
af1d00c9 119
91416e91 120Where 'TypeParameters' is an array reference or hash references of
c6fece89 121L<Moose::Meta::TypeConstraint> objects.
af1d00c9 122
22727dd5 123This type library enables structured type constraints. It is built on top of the
59deb858 124L<MooseX::Types> library system, so you should review the documentation for that
125if you are not familiar with it.
126
5632ada1 127=head2 Comparing Parameterized types to Structured types
59deb858 128
22727dd5 129Parameterized constraints are built into core Moose and you are probably already
07a8693b 130familar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
131have similar functionality, so their syntax is likewise similar. For example,
22727dd5 132you could define a parameterized constraint like:
6c2f284c 133
d87e8b74 134 subtype ArrayOfInts,
0e5e997c 135 as ArrayRef[Int];
6c2f284c 136
c6fece89 137which would constrain a value to something like [1,2,3,...] and so on. On the
22727dd5 138other hand, a structured type constraint explicitly names all it's allowed
139'internal' type parameter constraints. For the example:
6c2f284c 140
af1d00c9 141 subtype StringFollowedByInt,
142 as Tuple[Str,Int];
91416e91 143
c6fece89 144would constrain it's value to things like ['hello', 111] but ['hello', 'world']
22727dd5 145would fail, as well as ['hello', 111, 'world'] and so on. Here's another
146example:
147
7caf630f 148 package MyApp::Types;
149
150 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
151 use MooseX::Types::Moose qw(Str Int);
152 use MooseX::Types::Structured qw(Tuple Optional);
153
22727dd5 154 subtype StringIntOptionalHashRef,
155 as Tuple[
156 Str, Int,
157 Optional[HashRef]
158 ];
91416e91 159
22727dd5 160This defines a type constraint that validates values like:
161
07a8693b 162 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
22727dd5 163 ['World', 200];
91416e91 164
22727dd5 165Notice that the last type constraint in the structure is optional. This is
166enabled via the helper Optional type constraint, which is a variation of the
07a8693b 167core Moose type constraint 'Maybe'. The main difference is that Optional type
91416e91 168constraints are required to validate if they exist, while 'Maybe' permits
c6fece89 169undefined values. So the following example would not validate:
22727dd5 170
171 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
91416e91 172
22727dd5 173Please note the subtle difference between undefined and null. If you wish to
07a8693b 174allow both null and undefined, you should use the core Moose 'Maybe' type
175constraint instead:
22727dd5 176
7caf630f 177 package MyApp::Types;
178
c6fece89 179 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
7caf630f 180 use MooseX::Types::Moose qw(Str Int Maybe);
22727dd5 181 use MooseX::Types::Structured qw(Tuple);
182
c6fece89 183 subtype StringIntMaybeHashRef,
22727dd5 184 as Tuple[
185 Str, Int, Maybe[HashRef]
186 ];
187
188This would validate the following:
189
07a8693b 190 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
91416e91 191 ['World', 200, undef];
22727dd5 192 ['World', 200];
d87e8b74 193
c6fece89 194Structured constraints are not limited to arrays. You can define a structure
7caf630f 195against a HashRef with the 'Dict' type constaint as in this example:
d87e8b74 196
197 subtype FirstNameLastName,
07a8693b 198 as Dict[
199 firstname => Str,
200 lastname => Str,
201 ];
d87e8b74 202
7caf630f 203This would constrain a HashRef that validates something like:
d87e8b74 204
7caf630f 205 {firstname => 'Christopher', lastname => 'Parsons'};
91416e91 206
d87e8b74 207but all the following would fail validation:
208
07a8693b 209 ## Incorrect keys
210 {first => 'Christopher', last => 'Parsons'};
91416e91 211
07a8693b 212 ## Too many keys
213 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
91416e91 214
07a8693b 215 ## Not a HashRef
91416e91 216 ['Christopher', 'Parsons'];
6c2f284c 217
218These structures can be as simple or elaborate as you wish. You can even
219combine various structured, parameterized and simple constraints all together:
220
c6fece89 221 subtype Crazy,
af1d00c9 222 as Tuple[
223 Int,
224 Dict[name=>Str, age=>Int],
225 ArrayRef[Int]
226 ];
91416e91 227
7caf630f 228Which would match:
229
230 [1, {name=>'John', age=>25},[10,11,12]];
231
232Please notice how the type parameters can be visually arranged to your liking
91416e91 233and to improve the clarity of your meaning. You don't need to run then
7caf630f 234altogether onto a single line. Additionally, since the 'Dict' type constraint
235defines a hash constraint, the key order is not meaningful. For example:
236
237 subtype AnyKeyOrder,
238 as Dict[
239 key1=>Int,
240 key2=>Str,
241 key3=>Int,
242 ];
243
244Would validate both:
245
246 {key1 => 1, key2 => "Hi!", key3 => 2};
247 {key2 => "Hi!", key1 => 100, key3 => 300};
248
249As you would expect, since underneath its just a plain old Perl hash at work.
59deb858 250
251=head2 Alternatives
6c2f284c 252
253You should exercise some care as to whether or not your complex structured
254constraints would be better off contained by a real object as in the following
255example:
256
af1d00c9 257 package MyApp::MyStruct;
258 use Moose;
91416e91 259
07a8693b 260 ## lazy way to make a bunch of attributes
22727dd5 261 has $_ for qw(full_name age_in_years);
91416e91 262
af1d00c9 263 package MyApp::MyClass;
264 use Moose;
91416e91 265
266 has person => (isa => 'MyApp::MyStruct');
267
af1d00c9 268 my $instance = MyApp::MyClass->new(
07a8693b 269 person=>MyApp::MyStruct->new(
270 full_name => 'John',
c6fece89 271 age_in_years => 39,
07a8693b 272 ),
af1d00c9 273 );
91416e91 274
6c2f284c 275This method may take some additional time to setup but will give you more
276flexibility. However, structured constraints are highly compatible with this
277method, granting some interesting possibilities for coercion. Try:
278
07a8693b 279 package MyApp::MyClass;
91416e91 280
07a8693b 281 use Moose;
22727dd5 282 use MyApp::MyStruct;
91416e91 283
07a8693b 284 ## It's recommended your type declarations live in a separate class in order
285 ## to promote reusability and clarity. Inlined here for brevity.
91416e91 286
22727dd5 287 use MooseX::Types::DateTime qw(DateTime);
288 use MooseX::Types -declare [qw(MyStruct)];
289 use MooseX::Types::Moose qw(Str Int);
290 use MooseX::Types::Structured qw(Dict);
291
292 ## Use class_type to create an ISA type constraint if your object doesn't
293 ## inherit from Moose::Object.
294 class_type 'MyApp::MyStruct';
295
296 ## Just a shorter version really.
297 subtype MyStruct,
af1d00c9 298 as 'MyApp::MyStruct';
91416e91 299
22727dd5 300 ## Add the coercions.
301 coerce MyStruct,
302 from Dict[
303 full_name=>Str,
304 age_in_years=>Int
305 ], via {
306 MyApp::MyStruct->new(%$_);
307 },
308 from Dict[
309 lastname=>Str,
310 firstname=>Str,
311 dob=>DateTime
312 ], via {
313 my $name = $_->{firstname} .' '. $_->{lastname};
af1d00c9 314 my $age = DateTime->now - $_->{dob};
91416e91 315
07a8693b 316 MyApp::MyStruct->new(
317 full_name=>$name,
318 age_in_years=>$age->years,
319 );
af1d00c9 320 };
91416e91 321
322 has person => (isa=>MyStruct);
323
07a8693b 324This would allow you to instantiate with something like:
325
326 my $obj = MyApp::MyClass->new( person => {
327 full_name=>'John Napiorkowski',
328 age_in_years=>39,
329 });
91416e91 330
07a8693b 331Or even:
332
333 my $obj = MyApp::MyClass->new( person => {
334 lastname=>'John',
335 firstname=>'Napiorkowski',
336 dob=>DateTime->new(year=>1969),
337 });
22727dd5 338
339If you are not familiar with how coercions work, check out the L<Moose> cookbook
340entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
341has additional examples and discussion.
342
343=head2 Subtyping a Structured type constraint
16aea7bf 344
07a8693b 345You need to exercise some care when you try to subtype a structured type as in
346this example:
d24da8ec 347
af1d00c9 348 subtype Person,
07a8693b 349 as Dict[name => Str];
91416e91 350
af1d00c9 351 subtype FriendlyPerson,
07a8693b 352 as Person[
353 name => Str,
354 total_friends => Int,
355 ];
91416e91 356
16aea7bf 357This will actually work BUT you have to take care that the subtype has a
a4a88fef 358structure that does not contradict the structure of it's parent. For now the
59deb858 359above works, but I will clarify the syntax for this at a future point, so
22727dd5 360it's recommended to avoid (should not really be needed so much anyway). For
59deb858 361now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
07a8693b 362patches are welcomed for discussion. If you find a good use for this, please
363let me know.
16aea7bf 364
365=head2 Coercions
366
367Coercions currently work for 'one level' deep. That is you can do:
368
af1d00c9 369 subtype Person,
07a8693b 370 as Dict[
371 name => Str,
372 age => Int
373 ];
91416e91 374
16aea7bf 375 subtype Fullname,
07a8693b 376 as Dict[
377 first => Str,
378 last => Str
379 ];
91416e91 380
af1d00c9 381 coerce Person,
d87e8b74 382 ## Coerce an object of a particular class
07a8693b 383 from BlessedPersonObject, via {
384 +{
385 name=>$_->name,
386 age=>$_->age,
387 };
388 },
91416e91 389
d87e8b74 390 ## Coerce from [$name, $age]
07a8693b 391 from ArrayRef, via {
392 +{
393 name=>$_->[0],
394 age=>$_->[1],
395 },
396 },
d87e8b74 397 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
07a8693b 398 from Dict[fullname=>Fullname, dob=>DateTime], via {
af1d00c9 399 my $age = $_->dob - DateTime->now;
07a8693b 400 my $firstn = $_->{fullname}->{first};
401 my $lastn = $_->{fullname}->{last}
af1d00c9 402 +{
07a8693b 403 name => $_->{fullname}->{first} .' '. ,
404 age =>$age->years
af1d00c9 405 }
16aea7bf 406 };
91416e91 407
16aea7bf 408And that should just work as expected. However, if there are any 'inner'
409coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
410won't currently get activated.
411
22727dd5 412Please see the test '07-coerce.t' for a more detailed example. Discussion on
413extending coercions to support this welcome on the Moose development channel or
414mailing list.
16aea7bf 415
c6fece89 416=head2 Recursion
417
418Newer versions of L<MooseX::Types> support recursive type constraints. That is
419you can include a type constraint as a contained type constraint of itself. For
420example:
421
422 subtype Person,
423 as Dict[
424 name=>Str,
425 friends=>Optional[
426 ArrayRef[Person]
427 ],
428 ];
91416e91 429
c6fece89 430This would declare a Person subtype that contains a name and an optional
431ArrayRef of Persons who are friends as in:
432
433 {
434 name => 'Mike',
435 friends => [
436 { name => 'John' },
437 { name => 'Vincent' },
438 {
439 name => 'Tracey',
440 friends => [
441 { name => 'Stephenie' },
442 { name => 'Ilya' },
443 ],
444 },
445 ],
446 };
447
448Please take care to make sure the recursion node is either Optional, or declare
449a Union with an non recursive option such as:
450
451 subtype Value
452 as Tuple[
453 Str,
454 Str|Tuple,
455 ];
91416e91 456
c6fece89 457Which validates:
458
459 [
460 'Hello', [
461 'World', [
462 'Is', [
463 'Getting',
464 'Old',
465 ],
466 ],
467 ],
468 ];
469
91416e91 470Otherwise you will define a subtype thatis impossible to validate since it is
c6fece89 471infinitely recursive. For more information about defining recursive types,
472please see the documentation in L<MooseX::Types> and the test cases.
473
16aea7bf 474=head1 TYPE CONSTRAINTS
475
476This type library defines the following constraints.
477
478=head2 Tuple[@constraints]
479
07a8693b 480This defines an ArrayRef based constraint which allows you to validate a specific
481list of contained constraints. For example:
16aea7bf 482
af1d00c9 483 Tuple[Int,Str]; ## Validates [1,'hello']
c6fece89 484 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
16aea7bf 485
7caf630f 486The Values of @constraints should ideally be L<MooseX::Types> declared type
487constraints. We do support 'old style' L<Moose> string based constraints to a
488limited degree but these string type constraints are considered deprecated.
91416e91 489There will be limited support for bugs resulting from mixing string and
7caf630f 490L<MooseX::Types> in your structures. If you encounter such a bug and really
491need it fixed, we will required a detailed test case at the minimum.
492
22727dd5 493=head2 Dict[%constraints]
16aea7bf 494
07a8693b 495This defines a HashRef based constraint which allowed you to validate a specific
16aea7bf 496hashref. For example:
497
af1d00c9 498 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
d24da8ec 499
91416e91 500The keys in %constraints follow the same rules as @constraints in the above
7caf630f 501section.
502
22727dd5 503=head2 Optional[$constraint]
190a34eb 504
505This is primarily a helper constraint for Dict and Tuple type constraints. What
7caf630f 506this allows is for you to assert that a given type constraint is allowed to be
190a34eb 507null (but NOT undefined). If the value is null, then the type constraint passes
508but if the value is defined it must validate against the type constraint. This
509makes it easy to make a Dict where one or more of the keys doesn't have to exist
510or a tuple where some of the values are not required. For example:
511
512 subtype Name() => as Dict[
513 first=>Str,
514 last=>Str,
515 middle=>Optional[Str],
516 ];
91416e91 517
190a34eb 518Creates a constraint that validates against a hashref with the keys 'first' and
519'last' being strings and required while an optional key 'middle' is must be a
520string if it appears but doesn't have to appear. So in this case both the
521following are valid:
522
523 {first=>'John', middle=>'James', last=>'Napiorkowski'}
524 {first=>'Vanessa', last=>'Li'}
52ffe972 525
7caf630f 526If you use the 'Maybe' type constraint instead, your values will also validate
527against 'undef', which may be incorrect for you.
528
52ffe972 529=head1 EXPORTABLE SUBROUTINES
530
531This type library makes available for export the following subroutines
532
533=head2 slurpy
534
535Structured type constraints by their nature are closed; that is validation will
7559b71f 536depend on an exact match between your structure definition and the arguments to
52ffe972 537be checked. Sometimes you might wish for a slightly looser amount of validation.
538For example, you may wish to validate the first 3 elements of an array reference
539and allow for an arbitrary number of additional elements. At first thought you
540might think you could do it this way:
541
542 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
543 subtype AllowTailingArgs,
544 as Tuple[
545 Int,
546 Str,
547 Object,
548 ArrayRef[Int],
549 ];
550
551However what this will actually validate are structures like this:
552
553 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
554
555In order to allow structured validation of, "and then some", arguments, you can
a59fe2a6 556use the L</slurpy> method against a type constraint. For example:
52ffe972 557
558 use MooseX::Types::Structured qw(Tuple slurpy);
91416e91 559
52ffe972 560 subtype AllowTailingArgs,
561 as Tuple[
562 Int,
563 Str,
564 Object,
565 slurpy ArrayRef[Int],
566 ];
567
568This will now work as expected, validating ArrayRef structures such as:
569
570 [1,"hello", $obj, 2,3,4,5,6,...]
91416e91 571
52ffe972 572A few caveats apply. First, the slurpy type constraint must be the last one in
573the list of type constraint parameters. Second, the parent type of the slurpy
574type constraint must match that of the containing type constraint. That means
575that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
576another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
577HashRef, also including other Dict constraints).
578
579Please note the the technical way this works 'under the hood' is that the
a59fe2a6 580slurpy keyword transforms the target type constraint into a coderef. Please do
52ffe972 581not try to create your own custom coderefs; always use the slurpy method. The
582underlying technology may change in the future but the slurpy keyword will be
583supported.
584
7559b71f 585=head1 ERROR MESSAGES
586
587Error reporting has been improved to return more useful debugging messages. Now
588I will stringify the incoming check value with L<Devel::PartialDump> so that you
589can see the actual structure that is tripping up validation. Also, I report the
590'internal' validation error, so that if a particular element inside the
591Structured Type is failing validation, you will see that. There's a limit to
592how deep this internal reporting goes, but you shouldn't see any of the "failed
593with ARRAY(XXXXXX)" that we got with earlier versions of this module.
594
595This support is continuing to expand, so it's best to use these messages for
596debugging purposes and not for creating messages that 'escape into the wild'
597such as error messages sent to the user.
598
599Please see the test '12-error.t' for a more lengthy example. Your thoughts and
600preferable tests or code patches very welcome!
601
59deb858 602=head1 EXAMPLES
603
604Here are some additional example usage for structured types. All examples can
605be found also in the 't/examples.t' test. Your contributions are also welcomed.
606
607=head2 Normalize a HashRef
608
609You need a hashref to conform to a canonical structure but are required accept a
610bunch of different incoming structures. You can normalize using the Dict type
611constraint and coercions. This example also shows structured types mixed which
612other MooseX::Types libraries.
613
614 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
91416e91 615
59deb858 616 use Moose;
617 use DateTime;
91416e91 618
59deb858 619 use MooseX::Types::Structured qw(Dict Tuple);
620 use MooseX::Types::DateTime qw(DateTime);
621 use MooseX::Types::Moose qw(Int Str Object);
622 use MooseX::Types -declare => [qw(Name Age Person)];
91416e91 623
59deb858 624 subtype Person,
c6fece89 625 as Dict[
626 name=>Str,
627 age=>Int,
628 ];
91416e91 629
59deb858 630 coerce Person,
c6fece89 631 from Dict[
91416e91 632 first=>Str,
633 last=>Str,
c6fece89 634 years=>Int,
635 ], via { +{
59deb858 636 name => "$_->{first} $_->{last}",
c6fece89 637 age => $_->{years},
59deb858 638 }},
c6fece89 639 from Dict[
640 fullname=>Dict[
91416e91 641 last=>Str,
c6fece89 642 first=>Str,
91416e91 643 ],
c6fece89 644 dob=>DateTime,
645 ],
07a8693b 646 ## DateTime needs to be inside of single quotes here to disambiguate the
647 ## class package from the DataTime type constraint imported via the
648 ## line "use MooseX::Types::DateTime qw(DateTime);"
59deb858 649 via { +{
650 name => "$_->{fullname}{first} $_->{fullname}{last}",
651 age => ($_->{dob} - 'DateTime'->now)->years,
652 }};
91416e91 653
59deb858 654 has person => (is=>'rw', isa=>Person, coerce=>1);
91416e91 655
07a8693b 656And now you can instantiate with all the following:
657
658 __PACKAGE__->new(
7559b71f 659 person=>{
660 name=>'John Napiorkowski',
91416e91 661 age=>39,
7559b71f 662 },
07a8693b 663 );
91416e91 664
07a8693b 665 __PACKAGE__->new(
7559b71f 666 person=>{
667 first=>'John',
668 last=>'Napiorkowski',
669 years=>39,
670 },
07a8693b 671 );
91416e91 672
07a8693b 673 __PACKAGE__->new(
7559b71f 674 person=>{
675 fullname => {
676 first=>'John',
677 last=>'Napiorkowski'
678 },
679 dob => 'DateTime'->new(
680 year=>1969,
681 month=>2,
682 day=>13
91416e91 683 ),
07a8693b 684 },
07a8693b 685 );
91416e91 686
07a8693b 687This technique is a way to support various ways to instantiate your class in a
688clean and declarative way.
59deb858 689
a30fa891 690=cut
691
67a8bc04 692Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
693 MooseX::Meta::TypeConstraint::Structured->new(
694 name => "MooseX::Types::Structured::Tuple" ,
695 parent => find_type_constraint('ArrayRef'),
91416e91 696 constraint_generator=> sub {
67a8bc04 697 ## Get the constraints and values to check
e327145a 698 my ($type_constraints, $values) = @_;
07a8693b 699 my @type_constraints = defined $type_constraints ?
ff801143 700 @$type_constraints : ();
91416e91 701
ff801143 702 my $overflow_handler;
aa4718fe 703 if($type_constraints[-1] && blessed $type_constraints[-1]
2f8e2a40 704 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
ff801143 705 $overflow_handler = pop @type_constraints;
706 }
91416e91 707
e327145a 708 my @values = defined $values ? @$values: ();
67a8bc04 709 ## Perform the checking
710 while(@type_constraints) {
711 my $type_constraint = shift @type_constraints;
a30fa891 712 if(@values) {
67a8bc04 713 my $value = shift @values;
714 unless($type_constraint->check($value)) {
7559b71f 715 $_[2]->{message} = $type_constraint->get_message($value)
716 if ref $_[2];
67a8bc04 717 return;
91416e91 718 }
67a8bc04 719 } else {
07a8693b 720 ## Test if the TC supports null values
190a34eb 721 unless($type_constraint->check()) {
7559b71f 722 $_[2]->{message} = $type_constraint->get_message('NULL')
723 if ref $_[2];
190a34eb 724 return;
725 }
a30fa891 726 }
727 }
67a8bc04 728 ## Make sure there are no leftovers.
729 if(@values) {
ff801143 730 if($overflow_handler) {
2f8e2a40 731 return $overflow_handler->check([@values], $_[2]);
ff801143 732 } else {
7559b71f 733 $_[2]->{message} = "More values than Type Constraints!"
734 if ref $_[2];
ff801143 735 return;
736 }
67a8bc04 737 } elsif(@type_constraints) {
7559b71f 738 $_[2]->{message} =
739 "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints)
740 if ref $_[2];
67a8bc04 741 return;
07a8693b 742 } else {
67a8bc04 743 return 1;
744 }
745 }
746 )
747);
91416e91 748
67a8bc04 749Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
750 MooseX::Meta::TypeConstraint::Structured->new(
751 name => "MooseX::Types::Structured::Dict",
752 parent => find_type_constraint('HashRef'),
91416e91 753 constraint_generator=> sub {
67a8bc04 754 ## Get the constraints and values to check
e327145a 755 my ($type_constraints, $values) = @_;
ff801143 756 my @type_constraints = defined $type_constraints ?
757 @$type_constraints : ();
91416e91 758
ff801143 759 my $overflow_handler;
aa4718fe 760 if($type_constraints[-1] && blessed $type_constraints[-1]
2f8e2a40 761 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
ff801143 762 $overflow_handler = pop @type_constraints;
91416e91 763 }
ff801143 764 my (%type_constraints) = @type_constraints;
e327145a 765 my %values = defined $values ? %$values: ();
67a8bc04 766 ## Perform the checking
767 while(%type_constraints) {
768 my($key, $type_constraint) = each %type_constraints;
769 delete $type_constraints{$key};
770 if(exists $values{$key}) {
771 my $value = $values{$key};
772 delete $values{$key};
773 unless($type_constraint->check($value)) {
7559b71f 774 $_[2]->{message} = $type_constraint->get_message($value)
775 if ref $_[2];
a30fa891 776 return;
777 }
07a8693b 778 } else {
779 ## Test to see if the TC supports null values
190a34eb 780 unless($type_constraint->check()) {
7559b71f 781 $_[2]->{message} = $type_constraint->get_message('NULL')
782 if ref $_[2];
190a34eb 783 return;
784 }
a30fa891 785 }
67a8bc04 786 }
787 ## Make sure there are no leftovers.
91416e91 788 if(%values) {
ff801143 789 if($overflow_handler) {
2f8e2a40 790 return $overflow_handler->check(+{%values});
ff801143 791 } else {
7559b71f 792 $_[2]->{message} = "More values than Type Constraints!"
793 if ref $_[2];
ff801143 794 return;
795 }
67a8bc04 796 } elsif(%type_constraints) {
7559b71f 797 $_[2]->{message} =
798 "Not enough values for all defined type constraints. Remaining: ". join(', ',values %values)
799 if ref $_[2];
67a8bc04 800 return;
07a8693b 801 } else {
67a8bc04 802 return 1;
803 }
804 },
805 )
806);
d24da8ec 807
e327145a 808OPTIONAL: {
809 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
810 name => 'MooseX::Types::Structured::Optional',
811 package_defined_in => __PACKAGE__,
812 parent => find_type_constraint('Item'),
813 constraint => sub { 1 },
814 constraint_generator => sub {
815 my ($type_parameter, @args) = @_;
816 my $check = $type_parameter->_compiled_type_constraint();
817 return sub {
07a8693b 818 my (@args) = @_;
819 ## Does the arg exist? Something exists if it's a 'real' value
820 ## or if it is set to undef.
e327145a 821 if(exists($args[0])) {
822 ## If it exists, we need to validate it
823 $check->($args[0]);
824 } else {
825 ## But it's is okay if the value doesn't exists
826 return 1;
827 }
828 }
829 }
830 );
831
832 Moose::Util::TypeConstraints::register_type_constraint($Optional);
833 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
834}
835
2f8e2a40 836sub slurpy ($) {
837 my ($tc) = @_;
838 return MooseX::Types::Structured::OverflowHandler->new(
839 type_constraint => $tc,
840 );
c116e19a 841}
e327145a 842
d24da8ec 843=head1 SEE ALSO
844
845The following modules or resources may be of interest.
846
22727dd5 847L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
a30fa891 848L<MooseX::Meta::TypeConstraint::Structured>
d24da8ec 849
16aea7bf 850=head1 TODO
851
c6fece89 852Here's a list of stuff I would be happy to get volunteers helping with:
853
7caf630f 854 * All POD examples need test cases in t/documentation/*.t
855 * Want to break out the examples section to a separate cookbook style POD.
856 * Want more examples and best practice / usage guidance for authors
91416e91 857 * Need to clarify deep coercions,
16aea7bf 858
d24da8ec 859=head1 AUTHOR
860
a59fe2a6 861John Napiorkowski <jjnapiork@cpan.org>
d24da8ec 862
ffa6bd15 863=head1 CONTRIBUTORS
864
7caf630f 865The following people have contributed to this module and agree with the listed
866Copyright & license information included below:
ffa6bd15 867
a59fe2a6 868 Florian Ragwitz, <rafl@debian.org>
869 Yuval Kogman, <nothingmuch@woobling.org>
68d0710b 870 Tomas Doran, <bobtfish@bobtfish.net>
ffa6bd15 871
d24da8ec 872=head1 COPYRIGHT & LICENSE
873
ffa6bd15 874Copyright 2008-2009, John Napiorkowski <jjnapiork@cpan.org>
875
7caf630f 876This program is free software; you can redistribute it and/or modify it under
877the same terms as Perl itself.
d24da8ec 878
879=cut
91416e91 880
67a8bc04 8811;