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