convert to my pluginbundle, with extra git automation
[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) ];
fe5e8860 13use Devel::PartialDump 0.13;
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
a4ae4800 722my $IsType = sub {
723 my ($obj, $type) = @_;
724
725 return $obj->can('equals')
726 ? $obj->equals($type)
727 : undef;
728};
729
730my $CompiledTC = sub {
731 my ($obj) = @_;
732
733 my $method = '_compiled_type_constraint';
734 return(
735 $obj->$IsType('Any') ? undef
736 : $obj->can($method) ? $obj->$method
737 : sub { $obj->check(shift) },
738 );
739};
740
b86402a0 741Moose::Util::TypeConstraints::register_type_constraint($Optional);
742Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
743
67a8bc04 744Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
8dbdca20 745 MooseX::Meta::TypeConstraint::Structured->new(
746 name => "MooseX::Types::Structured::Tuple" ,
747 parent => find_type_constraint('ArrayRef'),
748 constraint_generator=> sub {
749 ## Get the constraints and values to check
a4ae4800 750 my ($self, $type_constraints) = @_;
751 $type_constraints ||= $self->type_constraints;
8dbdca20 752 my @type_constraints = defined $type_constraints ?
ff801143 753 @$type_constraints : ();
46e0d91a 754
ff801143 755 my $overflow_handler;
aa4718fe 756 if($type_constraints[-1] && blessed $type_constraints[-1]
2f8e2a40 757 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
ff801143 758 $overflow_handler = pop @type_constraints;
759 }
46e0d91a 760
220f2fbb 761 my $length = $#type_constraints;
762 foreach my $idx (0..$length) {
763 unless(blessed $type_constraints[$idx]) {
764 ($type_constraints[$idx] = find_type_constraint($type_constraints[$idx]))
765 || die "$type_constraints[$idx] is not a registered type";
766 }
767 }
768
a4ae4800 769 my (@checks, @optional, $o_check, $is_compiled);
770 return sub {
771 my ($values, $err) = @_;
772 my @values = defined $values ? @$values : ();
773
774 ## initialise on first time run
775 unless ($is_compiled) {
776 @checks = map { $_->$CompiledTC } @type_constraints;
777 @optional = map { $_->is_subtype_of($Optional) } @type_constraints;
778 $o_check = $overflow_handler->$CompiledTC
779 if $overflow_handler;
780 $is_compiled++;
781 }
782
783 ## Perform the checking
784 VALUE:
785 for my $type_index (0 .. $#checks) {
786
787 my $type_constraint = $checks[ $type_index ];
788
789 if(@values) {
790 my $value = shift @values;
791
792 next VALUE
793 unless $type_constraint;
794
795 unless($type_constraint->($value)) {
796 if($err) {
797 my $message = $type_constraints[ $type_index ]->validate($value,$err);
798 $err->add_message({message=>$message,level=>$err->level});
799 }
800 return;
801 }
802 } else {
803 ## Test if the TC supports null values
804 unless ($optional[ $type_index ]) {
805 if($err) {
806 my $message = $type_constraints[ $type_index ]->get_message('NULL',$err);
807 $err->add_message({message=>$message,level=>$err->level});
808 }
809 return;
21d0e759 810 }
8dbdca20 811 }
a4ae4800 812 }
813
814 ## Make sure there are no leftovers.
815 if(@values) {
816 if($overflow_handler) {
817 return $o_check->([@values], $err);
818 } else {
819 if($err) {
820 my $message = "More values than Type Constraints!";
821 $err->add_message({message=>$message,level=>$err->level});
9448ea2c 822 }
8dbdca20 823 return;
824 }
ff801143 825 } else {
a4ae4800 826 return 1;
9448ea2c 827 }
a4ae4800 828 };
8dbdca20 829 }
830 )
67a8bc04 831);
46e0d91a 832
67a8bc04 833Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
8dbdca20 834 MooseX::Meta::TypeConstraint::Structured->new(
835 name => "MooseX::Types::Structured::Dict",
836 parent => find_type_constraint('HashRef'),
21d0e759 837 constraint_generator => sub {
8dbdca20 838 ## Get the constraints and values to check
a4ae4800 839 my ($self, $type_constraints) = @_;
840 $type_constraints = $self->type_constraints;
8dbdca20 841 my @type_constraints = defined $type_constraints ?
ff801143 842 @$type_constraints : ();
46e0d91a 843
ff801143 844 my $overflow_handler;
aa4718fe 845 if($type_constraints[-1] && blessed $type_constraints[-1]
2f8e2a40 846 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
ff801143 847 $overflow_handler = pop @type_constraints;
46e0d91a 848 }
220f2fbb 849 my %type_constraints = @type_constraints;
850 foreach my $key (keys %type_constraints) {
851 unless(blessed $type_constraints{$key}) {
852 ($type_constraints{$key} = find_type_constraint($type_constraints{$key}))
853 || die "$type_constraints{$key} is not a registered type";
854 }
855 }
a4ae4800 856
857 my (%check, %optional, $o_check, $is_compiled);
858 return sub {
859 my ($values, $err) = @_;
860 my %values = defined $values ? %$values: ();
861
862 unless ($is_compiled) {
863 %check = map { ($_ => $type_constraints{ $_ }->$CompiledTC) } keys %type_constraints;
864 %optional = map { ($_ => $type_constraints{ $_ }->is_subtype_of($Optional)) } keys %type_constraints;
865 $o_check = $overflow_handler->$CompiledTC
866 if $overflow_handler;
867 $is_compiled++;
868 }
869
870 ## Perform the checking
871 KEY:
872 for my $key (keys %check) {
873 my $type_constraint = $check{ $key };
874
875 if(exists $values{$key}) {
876 my $value = $values{$key};
877 delete $values{$key};
878
879 next KEY
880 unless $type_constraint;
881
882 unless($type_constraint->($value)) {
883 if($err) {
884 my $message = $type_constraints{ $key }->validate($value,$err);
885 $err->add_message({message=>$message,level=>$err->level});
886 }
887 return;
888 }
889 } else {
890 ## Test to see if the TC supports null values
891 unless ($optional{ $key }) {
892 if($err) {
893 my $message = $type_constraints{ $key }->get_message('NULL',$err);
894 $err->add_message({message=>$message,level=>$err->level});
895 }
896 return;
21d0e759 897 }
8dbdca20 898 }
a4ae4800 899 }
900
901 ## Make sure there are no leftovers.
902 if(%values) {
903 if($overflow_handler) {
904 return $o_check->(+{%values});
905 } else {
906 if($err) {
907 my $message = "More values than Type Constraints!";
908 $err->add_message({message=>$message,level=>$err->level});
9448ea2c 909 }
8dbdca20 910 return;
911 }
ff801143 912 } else {
a4ae4800 913 return 1;
9448ea2c 914 }
8dbdca20 915 }
916 },
917 )
67a8bc04 918);
d24da8ec 919
678b4064 920Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
921 MooseX::Meta::TypeConstraint::Structured->new(
922 name => "MooseX::Types::Structured::Map",
923 parent => find_type_constraint('HashRef'),
46e0d91a 924 constraint_generator=> sub {
678b4064 925 ## Get the constraints and values to check
a4ae4800 926 my ($self, $type_constraints) = @_;
927 $type_constraints = $self->type_constraints;
678b4064 928 my @constraints = defined $type_constraints ? @$type_constraints : ();
46e0d91a 929
678b4064 930 Carp::confess( "too many args for Map type" ) if @constraints > 2;
931
932 my ($key_type, $value_type) = @constraints == 2 ? @constraints
933 : @constraints == 1 ? (undef, @constraints)
934 : ();
935
a4ae4800 936 my ($key_check, $value_check, $is_compiled);
937 return sub {
938 my ($values, $err) = @_;
939 my %values = defined $values ? %$values: ();
940
941 unless ($is_compiled) {
942 ($key_check, $value_check)
943 = map { $_ ? $_->$CompiledTC : undef }
944 $key_type, $value_type;
945 $is_compiled++;
946 }
947
948 ## Perform the checking
949 if ($value_check) {
950 for my $value (values %$values) {
951 unless ($value_check->($value)) {
952 if($err) {
953 my $message = $value_type->validate($value,$err);
954 $err->add_message({message=>$message,level=>$err->level});
955 }
956 return;
957 }
9448ea2c 958 }
678b4064 959 }
a4ae4800 960 if ($key_check) {
961 for my $key (keys %$values) {
962 unless ($key_check->($key)) {
963 if($err) {
964 my $message = $key_type->validate($key,$err);
965 $err->add_message({message=>$message,level=>$err->level});
966 }
967 return;
968 }
9448ea2c 969 }
678b4064 970 }
678b4064 971
a4ae4800 972 return 1;
973 };
678b4064 974 },
975 )
976);
977
2f8e2a40 978sub slurpy ($) {
8dbdca20 979 my ($tc) = @_;
980 return MooseX::Types::Structured::OverflowHandler->new(
2f8e2a40 981 type_constraint => $tc,
982 );
c116e19a 983}
e327145a 984
d24da8ec 985=head1 SEE ALSO
986
987The following modules or resources may be of interest.
988
22727dd5 989L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
a30fa891 990L<MooseX::Meta::TypeConstraint::Structured>
d24da8ec 991
d24da8ec 992=cut
46e0d91a 993
67a8bc04 9941;