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