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