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