1 package MooseX::Types::Structured;
5 use Moose::Util::TypeConstraints;
6 use MooseX::Meta::TypeConstraint::Structured;
7 use MooseX::Types::Structured::OverflowHandler;
8 use MooseX::Types -declare => [qw(Dict Tuple Optional)];
9 use Sub::Exporter -setup => { exports => [ qw(Dict Tuple Optional slurpy) ] };
10 use Devel::PartialDump;
11 use Scalar::Util qw(blessed);
13 our $VERSION = '0.14';
14 our $AUTHORITY = 'cpan:JJNAPIORK';
18 MooseX::Types::Structured - Structured Type Constraints for Moose
22 The following is example usage for this module.
27 use MooseX::Types::Moose qw(Str Int HashRef);
28 use MooseX::Types::Structured qw(Dict Tuple Optional);
30 ## A name has a first and last part, but middle names are not required
35 middle => Optional[Str],
39 ## description is a string field followed by a HashRef of tagged data.
47 Then you can instantiate this class with something like:
49 my $john = Person->new(
53 last => 'Napiorkowski',
56 'A cool guy who loves Perl and Moose.', {
57 married_to => 'Vanessa Li',
65 my $vanessa = Person->new(
70 description => ['A great student!'],
73 But all of these would cause a constraint error for the 'name' attribute:
75 ## Value for 'name' not a HashRef
76 Person->new( name => 'John' );
78 ## Value for 'name' has incorrect hash key and missing required keys
79 Person->new( name => {
83 ## Also incorrect keys
84 Person->new( name => {
89 ## key 'middle' incorrect type, should be a Str not a ArrayRef
90 Person->new( name => {
96 And these would cause a constraint error for the 'description' attribute:
98 ## Should be an ArrayRef
99 Person->new( description => 'Hello I am a String' );
101 ## First element must be a string not a HashRef.
102 Person->new (description => [{
107 Please see the test cases for more examples.
111 A structured type constraint is a standard container L<Moose> type constraint,
112 such as an ArrayRef or HashRef, which has been enhanced to allow you to
113 explicitly name all the allowed type constraints inside the structure. The
116 TypeConstraint[@TypeParameters or %TypeParameters]
118 Where 'TypeParameters' is an array reference or hash references of
119 L<Moose::Meta::TypeConstraint> objects.
121 This type library enables structured type constraints. It is built on top of the
122 L<MooseX::Types> library system, so you should review the documentation for that
123 if you are not familiar with it.
125 =head2 Comparing Parameterized types to Structured types
127 Parameterized constraints are built into core Moose and you are probably already
128 familar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
129 have similar functionality, so their syntax is likewise similar. For example,
130 you could define a parameterized constraint like:
135 which would constrain a value to something like [1,2,3,...] and so on. On the
136 other hand, a structured type constraint explicitly names all it's allowed
137 'internal' type parameter constraints. For the example:
139 subtype StringFollowedByInt,
142 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
143 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
146 subtype StringIntOptionalHashRef,
152 This defines a type constraint that validates values like:
154 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
157 Notice that the last type constraint in the structure is optional. This is
158 enabled via the helper Optional type constraint, which is a variation of the
159 core Moose type constraint 'Maybe'. The main difference is that Optional type
160 constraints are required to validate if they exist, while 'Maybe' permits
161 undefined values. So the following example would not validate:
163 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
165 Please note the subtle difference between undefined and null. If you wish to
166 allow both null and undefined, you should use the core Moose 'Maybe' type
169 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
170 use MooseX::Types::Moose qw(Maybe);
171 use MooseX::Types::Structured qw(Tuple);
173 subtype StringIntMaybeHashRef,
175 Str, Int, Maybe[HashRef]
178 This would validate the following:
180 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
181 ['World', 200, undef];
184 Structured constraints are not limited to arrays. You can define a structure
185 against a HashRef with 'Dict' as in this example:
187 subtype FirstNameLastName,
193 This would constrain a HashRef to something like:
195 {firstname => 'Christopher', lastname= > 'Parsons'};
197 but all the following would fail validation:
200 {first => 'Christopher', last => 'Parsons'};
203 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
206 ['Christopher', 'Christopher'];
208 These structures can be as simple or elaborate as you wish. You can even
209 combine various structured, parameterized and simple constraints all together:
214 Dict[name=>Str, age=>Int],
218 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
219 the type parameters can be visually arranged to your liking and to improve the
220 clarity of your meaning. You don't need to run then altogether onto a single
225 You should exercise some care as to whether or not your complex structured
226 constraints would be better off contained by a real object as in the following
229 package MyApp::MyStruct;
232 ## lazy way to make a bunch of attributes
233 has $_ for qw(full_name age_in_years);
235 package MyApp::MyClass;
238 has person => (isa => 'MyApp::MyStruct');
240 my $instance = MyApp::MyClass->new(
241 person=>MyApp::MyStruct->new(
247 This method may take some additional time to setup but will give you more
248 flexibility. However, structured constraints are highly compatible with this
249 method, granting some interesting possibilities for coercion. Try:
251 package MyApp::MyClass;
256 ## It's recommended your type declarations live in a separate class in order
257 ## to promote reusability and clarity. Inlined here for brevity.
259 use MooseX::Types::DateTime qw(DateTime);
260 use MooseX::Types -declare [qw(MyStruct)];
261 use MooseX::Types::Moose qw(Str Int);
262 use MooseX::Types::Structured qw(Dict);
264 ## Use class_type to create an ISA type constraint if your object doesn't
265 ## inherit from Moose::Object.
266 class_type 'MyApp::MyStruct';
268 ## Just a shorter version really.
270 as 'MyApp::MyStruct';
272 ## Add the coercions.
278 MyApp::MyStruct->new(%$_);
285 my $name = $_->{firstname} .' '. $_->{lastname};
286 my $age = DateTime->now - $_->{dob};
288 MyApp::MyStruct->new(
290 age_in_years=>$age->years,
294 has person => (isa=>MyStruct);
296 This would allow you to instantiate with something like:
298 my $obj = MyApp::MyClass->new( person => {
299 full_name=>'John Napiorkowski',
305 my $obj = MyApp::MyClass->new( person => {
307 firstname=>'Napiorkowski',
308 dob=>DateTime->new(year=>1969),
311 If you are not familiar with how coercions work, check out the L<Moose> cookbook
312 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
313 has additional examples and discussion.
315 =head2 Subtyping a Structured type constraint
317 You need to exercise some care when you try to subtype a structured type as in
321 as Dict[name => Str];
323 subtype FriendlyPerson,
326 total_friends => Int,
329 This will actually work BUT you have to take care that the subtype has a
330 structure that does not contradict the structure of it's parent. For now the
331 above works, but I will clarify the syntax for this at a future point, so
332 it's recommended to avoid (should not really be needed so much anyway). For
333 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
334 patches are welcomed for discussion. If you find a good use for this, please
339 Coercions currently work for 'one level' deep. That is you can do:
354 ## Coerce an object of a particular class
355 from BlessedPersonObject, via {
362 ## Coerce from [$name, $age]
369 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
370 from Dict[fullname=>Fullname, dob=>DateTime], via {
371 my $age = $_->dob - DateTime->now;
372 my $firstn = $_->{fullname}->{first};
373 my $lastn = $_->{fullname}->{last}
375 name => $_->{fullname}->{first} .' '. ,
380 And that should just work as expected. However, if there are any 'inner'
381 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
382 won't currently get activated.
384 Please see the test '07-coerce.t' for a more detailed example. Discussion on
385 extending coercions to support this welcome on the Moose development channel or
390 Newer versions of L<MooseX::Types> support recursive type constraints. That is
391 you can include a type constraint as a contained type constraint of itself. For
402 This would declare a Person subtype that contains a name and an optional
403 ArrayRef of Persons who are friends as in:
409 { name => 'Vincent' },
413 { name => 'Stephenie' },
420 Please take care to make sure the recursion node is either Optional, or declare
421 a Union with an non recursive option such as:
442 Otherwise you will define a subtype thatis impossible to validate since it is
443 infinitely recursive. For more information about defining recursive types,
444 please see the documentation in L<MooseX::Types> and the test cases.
446 =head1 TYPE CONSTRAINTS
448 This type library defines the following constraints.
450 =head2 Tuple[@constraints]
452 This defines an ArrayRef based constraint which allows you to validate a specific
453 list of contained constraints. For example:
455 Tuple[Int,Str]; ## Validates [1,'hello']
456 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
458 =head2 Dict[%constraints]
460 This defines a HashRef based constraint which allowed you to validate a specific
461 hashref. For example:
463 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
465 =head2 Optional[$constraint]
467 This is primarily a helper constraint for Dict and Tuple type constraints. What
468 this allows if for you to assert that a given type constraint is allowed to be
469 null (but NOT undefined). If the value is null, then the type constraint passes
470 but if the value is defined it must validate against the type constraint. This
471 makes it easy to make a Dict where one or more of the keys doesn't have to exist
472 or a tuple where some of the values are not required. For example:
474 subtype Name() => as Dict[
477 middle=>Optional[Str],
480 Creates a constraint that validates against a hashref with the keys 'first' and
481 'last' being strings and required while an optional key 'middle' is must be a
482 string if it appears but doesn't have to appear. So in this case both the
485 {first=>'John', middle=>'James', last=>'Napiorkowski'}
486 {first=>'Vanessa', last=>'Li'}
488 =head1 EXPORTABLE SUBROUTINES
490 This type library makes available for export the following subroutines
494 Structured type constraints by their nature are closed; that is validation will
495 depend on an exact match between your structure definition and the arguments to
496 be checked. Sometimes you might wish for a slightly looser amount of validation.
497 For example, you may wish to validate the first 3 elements of an array reference
498 and allow for an arbitrary number of additional elements. At first thought you
499 might think you could do it this way:
501 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
502 subtype AllowTailingArgs,
510 However what this will actually validate are structures like this:
512 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
514 In order to allow structured validation of, "and then some", arguments, you can
515 use the </slurpy> method against a type constraint. For example:
517 use MooseX::Types::Structured qw(Tuple slurpy);
519 subtype AllowTailingArgs,
524 slurpy ArrayRef[Int],
527 This will now work as expected, validating ArrayRef structures such as:
529 [1,"hello", $obj, 2,3,4,5,6,...]
531 A few caveats apply. First, the slurpy type constraint must be the last one in
532 the list of type constraint parameters. Second, the parent type of the slurpy
533 type constraint must match that of the containing type constraint. That means
534 that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
535 another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
536 HashRef, also including other Dict constraints).
538 Please note the the technical way this works 'under the hood' is that the
539 slurpy keywork transforms the target type constraint into a coderef. Please do
540 not try to create your own custom coderefs; always use the slurpy method. The
541 underlying technology may change in the future but the slurpy keyword will be
544 =head1 ERROR MESSAGES
546 Error reporting has been improved to return more useful debugging messages. Now
547 I will stringify the incoming check value with L<Devel::PartialDump> so that you
548 can see the actual structure that is tripping up validation. Also, I report the
549 'internal' validation error, so that if a particular element inside the
550 Structured Type is failing validation, you will see that. There's a limit to
551 how deep this internal reporting goes, but you shouldn't see any of the "failed
552 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
554 This support is continuing to expand, so it's best to use these messages for
555 debugging purposes and not for creating messages that 'escape into the wild'
556 such as error messages sent to the user.
558 Please see the test '12-error.t' for a more lengthy example. Your thoughts and
559 preferable tests or code patches very welcome!
563 Here are some additional example usage for structured types. All examples can
564 be found also in the 't/examples.t' test. Your contributions are also welcomed.
566 =head2 Normalize a HashRef
568 You need a hashref to conform to a canonical structure but are required accept a
569 bunch of different incoming structures. You can normalize using the Dict type
570 constraint and coercions. This example also shows structured types mixed which
571 other MooseX::Types libraries.
573 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
578 use MooseX::Types::Structured qw(Dict Tuple);
579 use MooseX::Types::DateTime qw(DateTime);
580 use MooseX::Types::Moose qw(Int Str Object);
581 use MooseX::Types -declare => [qw(Name Age Person)];
595 name => "$_->{first} $_->{last}",
605 ## DateTime needs to be inside of single quotes here to disambiguate the
606 ## class package from the DataTime type constraint imported via the
607 ## line "use MooseX::Types::DateTime qw(DateTime);"
609 name => "$_->{fullname}{first} $_->{fullname}{last}",
610 age => ($_->{dob} - 'DateTime'->now)->years,
613 has person => (is=>'rw', isa=>Person, coerce=>1);
615 And now you can instantiate with all the following:
619 name=>'John Napiorkowski',
627 last=>'Napiorkowski',
638 dob => 'DateTime'->new(
646 This technique is a way to support various ways to instantiate your class in a
647 clean and declarative way.
651 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
652 MooseX::Meta::TypeConstraint::Structured->new(
653 name => "MooseX::Types::Structured::Tuple" ,
654 parent => find_type_constraint('ArrayRef'),
655 constraint_generator=> sub {
656 ## Get the constraints and values to check
657 my ($type_constraints, $values) = @_;
658 my @type_constraints = defined $type_constraints ?
659 @$type_constraints : ();
661 my $overflow_handler;
662 if($type_constraints[-1] && blessed $type_constraints[-1]
663 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
664 $overflow_handler = pop @type_constraints;
667 my @values = defined $values ? @$values: ();
668 ## Perform the checking
669 while(@type_constraints) {
670 my $type_constraint = shift @type_constraints;
672 my $value = shift @values;
673 unless($type_constraint->check($value)) {
674 $_[2]->{message} = $type_constraint->get_message($value)
679 ## Test if the TC supports null values
680 unless($type_constraint->check()) {
681 $_[2]->{message} = $type_constraint->get_message('NULL')
687 ## Make sure there are no leftovers.
689 if($overflow_handler) {
690 return $overflow_handler->check([@values], $_[2]);
692 $_[2]->{message} = "More values than Type Constraints!"
696 } elsif(@type_constraints) {
698 "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints)
708 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
709 MooseX::Meta::TypeConstraint::Structured->new(
710 name => "MooseX::Types::Structured::Dict",
711 parent => find_type_constraint('HashRef'),
712 constraint_generator=> sub {
713 ## Get the constraints and values to check
714 my ($type_constraints, $values) = @_;
715 my @type_constraints = defined $type_constraints ?
716 @$type_constraints : ();
718 my $overflow_handler;
719 if($type_constraints[-1] && blessed $type_constraints[-1]
720 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
721 $overflow_handler = pop @type_constraints;
723 my (%type_constraints) = @type_constraints;
724 my %values = defined $values ? %$values: ();
725 ## Perform the checking
726 while(%type_constraints) {
727 my($key, $type_constraint) = each %type_constraints;
728 delete $type_constraints{$key};
729 if(exists $values{$key}) {
730 my $value = $values{$key};
731 delete $values{$key};
732 unless($type_constraint->check($value)) {
733 $_[2]->{message} = $type_constraint->get_message($value)
738 ## Test to see if the TC supports null values
739 unless($type_constraint->check()) {
740 $_[2]->{message} = $type_constraint->get_message('NULL')
746 ## Make sure there are no leftovers.
748 if($overflow_handler) {
749 return $overflow_handler->check(+{%values});
751 $_[2]->{message} = "More values than Type Constraints!"
755 } elsif(%type_constraints) {
757 "Not enough values for all defined type constraints. Remaining: ". join(', ',values %values)
768 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
769 name => 'MooseX::Types::Structured::Optional',
770 package_defined_in => __PACKAGE__,
771 parent => find_type_constraint('Item'),
772 constraint => sub { 1 },
773 constraint_generator => sub {
774 my ($type_parameter, @args) = @_;
775 my $check = $type_parameter->_compiled_type_constraint();
778 ## Does the arg exist? Something exists if it's a 'real' value
779 ## or if it is set to undef.
780 if(exists($args[0])) {
781 ## If it exists, we need to validate it
784 ## But it's is okay if the value doesn't exists
791 Moose::Util::TypeConstraints::register_type_constraint($Optional);
792 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
797 return MooseX::Types::Structured::OverflowHandler->new(
798 type_constraint => $tc,
804 The following modules or resources may be of interest.
806 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
807 L<MooseX::Meta::TypeConstraint::Structured>
811 Here's a list of stuff I would be happy to get volunteers helping with:
813 All POD examples need test cases in t/documentation/*.t
814 Want to break out the examples section to a separate cookbook style POD.
815 Want more examples and best practice / usage guidance for authors
816 Need to clarify deep coercions,
817 Need to clarify subtypes of subtypes.
821 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
823 =head1 COPYRIGHT & LICENSE
825 This program is free software; you can redistribute it and/or modify
826 it under the same terms as Perl itself.