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