doc formatting and spelling fixes
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Types / Structured.pm
CommitLineData
d24da8ec 1package MooseX::Types::Structured;
8b810e83 2# ABSTRACT: 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
8b810e83 71But all of these would cause a constraint error for the C<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
8b810e83 94And these would cause a constraint error for the C<description> attribute:
07a8693b 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,
8b810e83 110such as an C<ArrayRef> or C<HashRef>, which has been enhanced to allow you to
07a8693b 111explicitly name all the allowed type constraints inside the structure. The
af1d00c9 112generalized form is:
113
07a8693b 114 TypeConstraint[@TypeParameters or %TypeParameters]
af1d00c9 115
8b810e83 116Where C<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
8b810e83 126familiar with the type constraints C<HashRef> and C<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
8b810e83 140would constrain its value to things like C<< ['hello', 111] >> but C<< ['hello', 'world'] >>
141would fail, as well as C<< ['hello', 111, 'world'] >> and so on. Here's another
22727dd5 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
8b810e83 162enabled via the helper C<Optional> type constraint, which is a variation of the
163core Moose type constraint C<Maybe>. The main difference is that C<Optional> type
164constraints are required to validate if they exist, while C<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
8b810e83 170allow both null and undefined, you should use the core Moose C<Maybe> type
07a8693b 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
8b810e83 191against a C<HashRef> with the C<Dict> type constraint as in this example:
d87e8b74 192
193 subtype FirstNameLastName,
07a8693b 194 as Dict[
195 firstname => Str,
196 lastname => Str,
197 ];
d87e8b74 198
8b810e83 199This would constrain a C<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
8b810e83 230altogether onto a single line. Additionally, since the C<Dict> type constraint
7caf630f 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
8b810e83 245As you would expect, since underneath it's 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
8b810e83 271This method may take some additional time to set up but will give you more
6c2f284c 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
8b810e83 339=for stopwords Subtyping
340
22727dd5 341=head2 Subtyping a Structured type constraint
16aea7bf 342
07a8693b 343You need to exercise some care when you try to subtype a structured type as in
344this example:
d24da8ec 345
af1d00c9 346 subtype Person,
07a8693b 347 as Dict[name => Str];
46e0d91a 348
af1d00c9 349 subtype FriendlyPerson,
07a8693b 350 as Person[
351 name => Str,
352 total_friends => Int,
353 ];
46e0d91a 354
16aea7bf 355This will actually work BUT you have to take care that the subtype has a
a4a88fef 356structure that does not contradict the structure of it's parent. For now the
59deb858 357above works, but I will clarify the syntax for this at a future point, so
22727dd5 358it's recommended to avoid (should not really be needed so much anyway). For
59deb858 359now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
07a8693b 360patches are welcomed for discussion. If you find a good use for this, please
361let me know.
16aea7bf 362
363=head2 Coercions
364
365Coercions currently work for 'one level' deep. That is you can do:
366
af1d00c9 367 subtype Person,
07a8693b 368 as Dict[
369 name => Str,
370 age => Int
371 ];
46e0d91a 372
16aea7bf 373 subtype Fullname,
07a8693b 374 as Dict[
375 first => Str,
376 last => Str
377 ];
46e0d91a 378
af1d00c9 379 coerce Person,
d87e8b74 380 ## Coerce an object of a particular class
07a8693b 381 from BlessedPersonObject, via {
382 +{
383 name=>$_->name,
384 age=>$_->age,
385 };
386 },
46e0d91a 387
d87e8b74 388 ## Coerce from [$name, $age]
07a8693b 389 from ArrayRef, via {
390 +{
391 name=>$_->[0],
392 age=>$_->[1],
393 },
394 },
d87e8b74 395 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
07a8693b 396 from Dict[fullname=>Fullname, dob=>DateTime], via {
af1d00c9 397 my $age = $_->dob - DateTime->now;
07a8693b 398 my $firstn = $_->{fullname}->{first};
399 my $lastn = $_->{fullname}->{last}
af1d00c9 400 +{
07a8693b 401 name => $_->{fullname}->{first} .' '. ,
402 age =>$age->years
af1d00c9 403 }
16aea7bf 404 };
46e0d91a 405
16aea7bf 406And that should just work as expected. However, if there are any 'inner'
8b810e83 407coercions, such as a coercion on C<Fullname> or on C<DateTime>, that coercion
16aea7bf 408won't currently get activated.
409
8b810e83 410Please see the test F<07-coerce.t> for a more detailed example. Discussion on
22727dd5 411extending coercions to support this welcome on the Moose development channel or
412mailing list.
16aea7bf 413
c6fece89 414=head2 Recursion
415
416Newer versions of L<MooseX::Types> support recursive type constraints. That is
417you can include a type constraint as a contained type constraint of itself. For
418example:
419
8dbdca20 420 subtype Person,
421 as Dict[
422 name=>Str,
423 friends=>Optional[
424 ArrayRef[Person]
425 ],
426 ];
46e0d91a 427
8b810e83 428This would declare a C<Person> subtype that contains a name and an optional
429C<ArrayRef> of C<Person>s who are friends as in:
c6fece89 430
8dbdca20 431 {
432 name => 'Mike',
433 friends => [
434 { name => 'John' },
435 { name => 'Vincent' },
436 {
437 name => 'Tracey',
438 friends => [
439 { name => 'Stephenie' },
440 { name => 'Ilya' },
441 ],
442 },
443 ],
444 };
c6fece89 445
8b810e83 446Please take care to make sure the recursion node is either C<Optional>, or declare
447a union with an non-recursive option such as:
c6fece89 448
8dbdca20 449 subtype Value
450 as Tuple[
451 Str,
452 Str|Tuple,
453 ];
46e0d91a 454
c6fece89 455Which validates:
456
8dbdca20 457 [
458 'Hello', [
459 'World', [
460 'Is', [
461 'Getting',
462 'Old',
463 ],
464 ],
465 ],
466 ];
c6fece89 467
8b810e83 468Otherwise you will define a subtype that is impossible to validate since it is
c6fece89 469infinitely recursive. For more information about defining recursive types,
470please see the documentation in L<MooseX::Types> and the test cases.
471
16aea7bf 472=head1 TYPE CONSTRAINTS
473
474This type library defines the following constraints.
475
476=head2 Tuple[@constraints]
477
07a8693b 478This defines an ArrayRef based constraint which allows you to validate a specific
479list of contained constraints. For example:
16aea7bf 480
af1d00c9 481 Tuple[Int,Str]; ## Validates [1,'hello']
c6fece89 482 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
16aea7bf 483
7caf630f 484The Values of @constraints should ideally be L<MooseX::Types> declared type
485constraints. We do support 'old style' L<Moose> string based constraints to a
486limited degree but these string type constraints are considered deprecated.
46e0d91a 487There will be limited support for bugs resulting from mixing string and
7caf630f 488L<MooseX::Types> in your structures. If you encounter such a bug and really
489need it fixed, we will required a detailed test case at the minimum.
490
22727dd5 491=head2 Dict[%constraints]
16aea7bf 492
07a8693b 493This defines a HashRef based constraint which allowed you to validate a specific
16aea7bf 494hashref. For example:
495
af1d00c9 496 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
d24da8ec 497
8b810e83 498The keys in C<%constraints> follow the same rules as C<@constraints> in the above
7caf630f 499section.
500
249d5425 501=head2 Map[ $key_constraint, $value_constraint ]
502
8b810e83 503This defines a C<HashRef>-based constraint in which both the keys and values are
249d5425 504required to meet certain constraints. For example, to map hostnames to IP
505addresses, you might say:
506
507 Map[ HostName, IPAddress ]
508
8b810e83 509The type constraint would only be met if every key was a valid C<HostName> and
510every value was a valid C<IPAddress>.
249d5425 511
22727dd5 512=head2 Optional[$constraint]
190a34eb 513
8b810e83 514This is primarily a helper constraint for C<Dict> and C<Tuple> type constraints. What
7caf630f 515this allows is for you to assert that a given type constraint is allowed to be
190a34eb 516null (but NOT undefined). If the value is null, then the type constraint passes
517but if the value is defined it must validate against the type constraint. This
518makes it easy to make a Dict where one or more of the keys doesn't have to exist
519or a tuple where some of the values are not required. For example:
520
521 subtype Name() => as Dict[
522 first=>Str,
523 last=>Str,
524 middle=>Optional[Str],
525 ];
46e0d91a 526
8b810e83 527...creates a constraint that validates against a hashref with the keys 'first' and
190a34eb 528'last' being strings and required while an optional key 'middle' is must be a
529string if it appears but doesn't have to appear. So in this case both the
530following are valid:
531
532 {first=>'John', middle=>'James', last=>'Napiorkowski'}
533 {first=>'Vanessa', last=>'Li'}
52ffe972 534
8b810e83 535If you use the C<Maybe> type constraint instead, your values will also validate
536against C<undef>, which may be incorrect for you.
7caf630f 537
52ffe972 538=head1 EXPORTABLE SUBROUTINES
539
540This type library makes available for export the following subroutines
541
8b810e83 542=for stopwords slurpy
543
52ffe972 544=head2 slurpy
545
546Structured type constraints by their nature are closed; that is validation will
7559b71f 547depend on an exact match between your structure definition and the arguments to
52ffe972 548be checked. Sometimes you might wish for a slightly looser amount of validation.
549For example, you may wish to validate the first 3 elements of an array reference
550and allow for an arbitrary number of additional elements. At first thought you
551might think you could do it this way:
552
553 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
554 subtype AllowTailingArgs,
555 as Tuple[
556 Int,
557 Str,
558 Object,
559 ArrayRef[Int],
560 ];
561
562However what this will actually validate are structures like this:
563
564 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
565
566In order to allow structured validation of, "and then some", arguments, you can
a59fe2a6 567use the L</slurpy> method against a type constraint. For example:
52ffe972 568
569 use MooseX::Types::Structured qw(Tuple slurpy);
46e0d91a 570
52ffe972 571 subtype AllowTailingArgs,
572 as Tuple[
573 Int,
574 Str,
575 Object,
576 slurpy ArrayRef[Int],
577 ];
578
579This will now work as expected, validating ArrayRef structures such as:
580
581 [1,"hello", $obj, 2,3,4,5,6,...]
46e0d91a 582
52ffe972 583A few caveats apply. First, the slurpy type constraint must be the last one in
584the list of type constraint parameters. Second, the parent type of the slurpy
585type constraint must match that of the containing type constraint. That means
8b810e83 586that a C<Tuple> can allow a slurpy C<ArrayRef> (or children of C<ArrayRef>s, including
587another C<Tuple>) and a C<Dict> can allow a slurpy C<HashRef> (or children/subtypes of
588HashRef, also including other C<Dict> constraints).
52ffe972 589
8b810e83 590Please note the technical way this works 'under the hood' is that the
a59fe2a6 591slurpy keyword transforms the target type constraint into a coderef. Please do
52ffe972 592not try to create your own custom coderefs; always use the slurpy method. The
593underlying technology may change in the future but the slurpy keyword will be
594supported.
595
7559b71f 596=head1 ERROR MESSAGES
597
598Error reporting has been improved to return more useful debugging messages. Now
599I will stringify the incoming check value with L<Devel::PartialDump> so that you
600can see the actual structure that is tripping up validation. Also, I report the
601'internal' validation error, so that if a particular element inside the
602Structured Type is failing validation, you will see that. There's a limit to
603how deep this internal reporting goes, but you shouldn't see any of the "failed
604with ARRAY(XXXXXX)" that we got with earlier versions of this module.
605
606This support is continuing to expand, so it's best to use these messages for
607debugging purposes and not for creating messages that 'escape into the wild'
608such as error messages sent to the user.
609
610Please see the test '12-error.t' for a more lengthy example. Your thoughts and
611preferable tests or code patches very welcome!
612
59deb858 613=head1 EXAMPLES
614
615Here are some additional example usage for structured types. All examples can
616be found also in the 't/examples.t' test. Your contributions are also welcomed.
617
618=head2 Normalize a HashRef
619
620You need a hashref to conform to a canonical structure but are required accept a
8b810e83 621bunch of different incoming structures. You can normalize using the C<Dict> type
59deb858 622constraint and coercions. This example also shows structured types mixed which
8b810e83 623other L<MooseX::Types> libraries.
59deb858 624
625 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
46e0d91a 626
59deb858 627 use Moose;
628 use DateTime;
46e0d91a 629
59deb858 630 use MooseX::Types::Structured qw(Dict Tuple);
631 use MooseX::Types::DateTime qw(DateTime);
632 use MooseX::Types::Moose qw(Int Str Object);
633 use MooseX::Types -declare => [qw(Name Age Person)];
46e0d91a 634
59deb858 635 subtype Person,
c6fece89 636 as Dict[
8dbdca20 637 name=>Str,
638 age=>Int,
c6fece89 639 ];
46e0d91a 640
59deb858 641 coerce Person,
c6fece89 642 from Dict[
8dbdca20 643 first=>Str,
644 last=>Str,
645 years=>Int,
c6fece89 646 ], via { +{
59deb858 647 name => "$_->{first} $_->{last}",
c6fece89 648 age => $_->{years},
59deb858 649 }},
c6fece89 650 from Dict[
8dbdca20 651 fullname=>Dict[
652 last=>Str,
653 first=>Str,
654 ],
655 dob=>DateTime,
c6fece89 656 ],
07a8693b 657 ## DateTime needs to be inside of single quotes here to disambiguate the
658 ## class package from the DataTime type constraint imported via the
659 ## line "use MooseX::Types::DateTime qw(DateTime);"
59deb858 660 via { +{
661 name => "$_->{fullname}{first} $_->{fullname}{last}",
662 age => ($_->{dob} - 'DateTime'->now)->years,
663 }};
46e0d91a 664
59deb858 665 has person => (is=>'rw', isa=>Person, coerce=>1);
46e0d91a 666
07a8693b 667And now you can instantiate with all the following:
668
669 __PACKAGE__->new(
7559b71f 670 person=>{
671 name=>'John Napiorkowski',
46e0d91a 672 age=>39,
7559b71f 673 },
07a8693b 674 );
46e0d91a 675
07a8693b 676 __PACKAGE__->new(
7559b71f 677 person=>{
678 first=>'John',
679 last=>'Napiorkowski',
680 years=>39,
681 },
07a8693b 682 );
46e0d91a 683
07a8693b 684 __PACKAGE__->new(
7559b71f 685 person=>{
686 fullname => {
687 first=>'John',
688 last=>'Napiorkowski'
689 },
690 dob => 'DateTime'->new(
691 year=>1969,
692 month=>2,
693 day=>13
46e0d91a 694 ),
07a8693b 695 },
07a8693b 696 );
46e0d91a 697
07a8693b 698This technique is a way to support various ways to instantiate your class in a
699clean and declarative way.
59deb858 700
a30fa891 701=cut
702
abd193e2 703my $Optional = MooseX::Meta::TypeConstraint::Structured::Optional->new(
b86402a0 704 name => 'MooseX::Types::Structured::Optional',
705 package_defined_in => __PACKAGE__,
706 parent => find_type_constraint('Item'),
707 constraint => sub { 1 },
708 constraint_generator => sub {
709 my ($type_parameter, @args) = @_;
710 my $check = $type_parameter->_compiled_type_constraint();
711 return sub {
712 my (@args) = @_;
713 ## Does the arg exist? Something exists if it's a 'real' value
714 ## or if it is set to undef.
715 if(exists($args[0])) {
716 ## If it exists, we need to validate it
717 $check->($args[0]);
718 } else {
719 ## But it's is okay if the value doesn't exists
720 return 1;
721 }
722 }
723 }
724);
725
a4ae4800 726my $IsType = sub {
727 my ($obj, $type) = @_;
728
729 return $obj->can('equals')
730 ? $obj->equals($type)
731 : undef;
732};
733
734my $CompiledTC = sub {
735 my ($obj) = @_;
736
737 my $method = '_compiled_type_constraint';
738 return(
739 $obj->$IsType('Any') ? undef
740 : $obj->can($method) ? $obj->$method
741 : sub { $obj->check(shift) },
742 );
743};
744
b86402a0 745Moose::Util::TypeConstraints::register_type_constraint($Optional);
746Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
747
67a8bc04 748Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
8dbdca20 749 MooseX::Meta::TypeConstraint::Structured->new(
750 name => "MooseX::Types::Structured::Tuple" ,
751 parent => find_type_constraint('ArrayRef'),
752 constraint_generator=> sub {
753 ## Get the constraints and values to check
a4ae4800 754 my ($self, $type_constraints) = @_;
755 $type_constraints ||= $self->type_constraints;
8dbdca20 756 my @type_constraints = defined $type_constraints ?
ff801143 757 @$type_constraints : ();
46e0d91a 758
ff801143 759 my $overflow_handler;
aa4718fe 760 if($type_constraints[-1] && blessed $type_constraints[-1]
2f8e2a40 761 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
ff801143 762 $overflow_handler = pop @type_constraints;
763 }
46e0d91a 764
220f2fbb 765 my $length = $#type_constraints;
766 foreach my $idx (0..$length) {
767 unless(blessed $type_constraints[$idx]) {
768 ($type_constraints[$idx] = find_type_constraint($type_constraints[$idx]))
769 || die "$type_constraints[$idx] is not a registered type";
770 }
771 }
772
a4ae4800 773 my (@checks, @optional, $o_check, $is_compiled);
774 return sub {
775 my ($values, $err) = @_;
776 my @values = defined $values ? @$values : ();
777
778 ## initialise on first time run
779 unless ($is_compiled) {
780 @checks = map { $_->$CompiledTC } @type_constraints;
781 @optional = map { $_->is_subtype_of($Optional) } @type_constraints;
782 $o_check = $overflow_handler->$CompiledTC
783 if $overflow_handler;
784 $is_compiled++;
785 }
786
787 ## Perform the checking
788 VALUE:
789 for my $type_index (0 .. $#checks) {
790
791 my $type_constraint = $checks[ $type_index ];
792
793 if(@values) {
794 my $value = shift @values;
795
796 next VALUE
797 unless $type_constraint;
798
799 unless($type_constraint->($value)) {
800 if($err) {
801 my $message = $type_constraints[ $type_index ]->validate($value,$err);
802 $err->add_message({message=>$message,level=>$err->level});
803 }
804 return;
805 }
806 } else {
807 ## Test if the TC supports null values
808 unless ($optional[ $type_index ]) {
809 if($err) {
810 my $message = $type_constraints[ $type_index ]->get_message('NULL',$err);
811 $err->add_message({message=>$message,level=>$err->level});
812 }
813 return;
21d0e759 814 }
8dbdca20 815 }
a4ae4800 816 }
817
818 ## Make sure there are no leftovers.
819 if(@values) {
820 if($overflow_handler) {
821 return $o_check->([@values], $err);
822 } else {
823 if($err) {
824 my $message = "More values than Type Constraints!";
825 $err->add_message({message=>$message,level=>$err->level});
9448ea2c 826 }
8dbdca20 827 return;
828 }
ff801143 829 } else {
a4ae4800 830 return 1;
9448ea2c 831 }
a4ae4800 832 };
8dbdca20 833 }
834 )
67a8bc04 835);
46e0d91a 836
67a8bc04 837Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
8dbdca20 838 MooseX::Meta::TypeConstraint::Structured->new(
839 name => "MooseX::Types::Structured::Dict",
840 parent => find_type_constraint('HashRef'),
21d0e759 841 constraint_generator => sub {
8dbdca20 842 ## Get the constraints and values to check
a4ae4800 843 my ($self, $type_constraints) = @_;
844 $type_constraints = $self->type_constraints;
8dbdca20 845 my @type_constraints = defined $type_constraints ?
ff801143 846 @$type_constraints : ();
46e0d91a 847
ff801143 848 my $overflow_handler;
aa4718fe 849 if($type_constraints[-1] && blessed $type_constraints[-1]
2f8e2a40 850 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
ff801143 851 $overflow_handler = pop @type_constraints;
46e0d91a 852 }
220f2fbb 853 my %type_constraints = @type_constraints;
854 foreach my $key (keys %type_constraints) {
855 unless(blessed $type_constraints{$key}) {
856 ($type_constraints{$key} = find_type_constraint($type_constraints{$key}))
857 || die "$type_constraints{$key} is not a registered type";
858 }
859 }
a4ae4800 860
861 my (%check, %optional, $o_check, $is_compiled);
862 return sub {
863 my ($values, $err) = @_;
864 my %values = defined $values ? %$values: ();
865
866 unless ($is_compiled) {
867 %check = map { ($_ => $type_constraints{ $_ }->$CompiledTC) } keys %type_constraints;
868 %optional = map { ($_ => $type_constraints{ $_ }->is_subtype_of($Optional)) } keys %type_constraints;
869 $o_check = $overflow_handler->$CompiledTC
870 if $overflow_handler;
871 $is_compiled++;
872 }
873
874 ## Perform the checking
875 KEY:
876 for my $key (keys %check) {
877 my $type_constraint = $check{ $key };
878
879 if(exists $values{$key}) {
880 my $value = $values{$key};
881 delete $values{$key};
882
883 next KEY
884 unless $type_constraint;
885
886 unless($type_constraint->($value)) {
887 if($err) {
888 my $message = $type_constraints{ $key }->validate($value,$err);
889 $err->add_message({message=>$message,level=>$err->level});
890 }
891 return;
892 }
893 } else {
894 ## Test to see if the TC supports null values
895 unless ($optional{ $key }) {
896 if($err) {
897 my $message = $type_constraints{ $key }->get_message('NULL',$err);
898 $err->add_message({message=>$message,level=>$err->level});
899 }
900 return;
21d0e759 901 }
8dbdca20 902 }
a4ae4800 903 }
904
905 ## Make sure there are no leftovers.
906 if(%values) {
907 if($overflow_handler) {
908 return $o_check->(+{%values});
909 } else {
910 if($err) {
911 my $message = "More values than Type Constraints!";
912 $err->add_message({message=>$message,level=>$err->level});
9448ea2c 913 }
8dbdca20 914 return;
915 }
ff801143 916 } else {
a4ae4800 917 return 1;
9448ea2c 918 }
8dbdca20 919 }
920 },
921 )
67a8bc04 922);
d24da8ec 923
678b4064 924Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
925 MooseX::Meta::TypeConstraint::Structured->new(
926 name => "MooseX::Types::Structured::Map",
927 parent => find_type_constraint('HashRef'),
46e0d91a 928 constraint_generator=> sub {
678b4064 929 ## Get the constraints and values to check
a4ae4800 930 my ($self, $type_constraints) = @_;
931 $type_constraints = $self->type_constraints;
678b4064 932 my @constraints = defined $type_constraints ? @$type_constraints : ();
46e0d91a 933
678b4064 934 Carp::confess( "too many args for Map type" ) if @constraints > 2;
935
936 my ($key_type, $value_type) = @constraints == 2 ? @constraints
937 : @constraints == 1 ? (undef, @constraints)
938 : ();
939
a4ae4800 940 my ($key_check, $value_check, $is_compiled);
941 return sub {
942 my ($values, $err) = @_;
943 my %values = defined $values ? %$values: ();
944
945 unless ($is_compiled) {
946 ($key_check, $value_check)
947 = map { $_ ? $_->$CompiledTC : undef }
948 $key_type, $value_type;
949 $is_compiled++;
950 }
951
952 ## Perform the checking
953 if ($value_check) {
954 for my $value (values %$values) {
955 unless ($value_check->($value)) {
956 if($err) {
957 my $message = $value_type->validate($value,$err);
958 $err->add_message({message=>$message,level=>$err->level});
959 }
960 return;
961 }
9448ea2c 962 }
678b4064 963 }
a4ae4800 964 if ($key_check) {
965 for my $key (keys %$values) {
966 unless ($key_check->($key)) {
967 if($err) {
968 my $message = $key_type->validate($key,$err);
969 $err->add_message({message=>$message,level=>$err->level});
970 }
971 return;
972 }
9448ea2c 973 }
678b4064 974 }
678b4064 975
a4ae4800 976 return 1;
977 };
678b4064 978 },
979 )
980);
981
2f8e2a40 982sub slurpy ($) {
8dbdca20 983 my ($tc) = @_;
984 return MooseX::Types::Structured::OverflowHandler->new(
2f8e2a40 985 type_constraint => $tc,
986 );
c116e19a 987}
e327145a 988
d24da8ec 989=head1 SEE ALSO
990
991The following modules or resources may be of interest.
992
22727dd5 993L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
a30fa891 994L<MooseX::Meta::TypeConstraint::Structured>
d24da8ec 995
d24da8ec 996=cut
46e0d91a 997
67a8bc04 9981;