1 package MooseX::Types::Structured;
5 use Moose::Util::TypeConstraints;
6 use MooseX::Meta::TypeConstraint::Structured;
7 use MooseX::Meta::TypeConstraint::Structured::Optional;
8 use MooseX::Types::Structured::OverflowHandler;
9 use MooseX::Types::Structured::TypeError;
10 use MooseX::Types -declare => [qw(Dict Map Tuple Optional)];
11 use Sub::Exporter -setup => [ qw(Dict Map Tuple Optional slurpy) ];
12 use Devel::PartialDump;
13 use Scalar::Util qw(blessed);
15 our $VERSION = '0.20';
16 our $AUTHORITY = 'cpan:JJNAPIORK';
20 MooseX::Types::Structured - Structured Type Constraints for Moose
24 The following is example usage for this module.
29 use MooseX::Types::Moose qw(Str Int HashRef);
30 use MooseX::Types::Structured qw(Dict Tuple Optional);
32 ## A name has a first and last part, but middle names are not required
37 middle => Optional[Str],
41 ## description is a string field followed by a HashRef of tagged data.
49 ## Remainder of your class attributes and methods
51 Then you can instantiate this class with something like:
53 my $john = Person->new(
57 last => 'Napiorkowski',
60 'A cool guy who loves Perl and Moose.', {
61 married_to => 'Vanessa Li',
69 my $vanessa = Person->new(
74 description => ['A great student!'],
77 But all of these would cause a constraint error for the 'name' attribute:
79 ## Value for 'name' not a HashRef
80 Person->new( name => 'John' );
82 ## Value for 'name' has incorrect hash key and missing required keys
83 Person->new( name => {
87 ## Also incorrect keys
88 Person->new( name => {
93 ## key 'middle' incorrect type, should be a Str not a ArrayRef
94 Person->new( name => {
100 And these would cause a constraint error for the 'description' attribute:
102 ## Should be an ArrayRef
103 Person->new( description => 'Hello I am a String' );
105 ## First element must be a string not a HashRef.
106 Person->new (description => [{
111 Please see the test cases for more examples.
115 A structured type constraint is a standard container L<Moose> type constraint,
116 such as an ArrayRef or HashRef, which has been enhanced to allow you to
117 explicitly name all the allowed type constraints inside the structure. The
120 TypeConstraint[@TypeParameters or %TypeParameters]
122 Where 'TypeParameters' is an array reference or hash references of
123 L<Moose::Meta::TypeConstraint> objects.
125 This type library enables structured type constraints. It is built on top of the
126 L<MooseX::Types> library system, so you should review the documentation for that
127 if you are not familiar with it.
129 =head2 Comparing Parameterized types to Structured types
131 Parameterized constraints are built into core Moose and you are probably already
132 familiar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
133 have similar functionality, so their syntax is likewise similar. For example,
134 you could define a parameterized constraint like:
139 which would constrain a value to something like [1,2,3,...] and so on. On the
140 other hand, a structured type constraint explicitly names all it's allowed
141 'internal' type parameter constraints. For the example:
143 subtype StringFollowedByInt,
146 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
147 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
150 package MyApp::Types;
152 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
153 use MooseX::Types::Moose qw(Str Int);
154 use MooseX::Types::Structured qw(Tuple Optional);
156 subtype StringIntOptionalHashRef,
162 This defines a type constraint that validates values like:
164 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
167 Notice that the last type constraint in the structure is optional. This is
168 enabled via the helper Optional type constraint, which is a variation of the
169 core Moose type constraint 'Maybe'. The main difference is that Optional type
170 constraints are required to validate if they exist, while 'Maybe' permits
171 undefined values. So the following example would not validate:
173 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
175 Please note the subtle difference between undefined and null. If you wish to
176 allow both null and undefined, you should use the core Moose 'Maybe' type
179 package MyApp::Types;
181 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
182 use MooseX::Types::Moose qw(Str Int Maybe);
183 use MooseX::Types::Structured qw(Tuple);
185 subtype StringIntMaybeHashRef,
187 Str, Int, Maybe[HashRef]
190 This would validate the following:
192 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
193 ['World', 200, undef];
196 Structured constraints are not limited to arrays. You can define a structure
197 against a HashRef with the 'Dict' type constaint as in this example:
199 subtype FirstNameLastName,
205 This would constrain a HashRef that validates something like:
207 {firstname => 'Christopher', lastname => 'Parsons'};
209 but all the following would fail validation:
212 {first => 'Christopher', last => 'Parsons'};
215 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
218 ['Christopher', 'Parsons'];
220 These structures can be as simple or elaborate as you wish. You can even
221 combine various structured, parameterized and simple constraints all together:
226 Dict[name=>Str, age=>Int],
232 [1, {name=>'John', age=>25},[10,11,12]];
234 Please notice how the type parameters can be visually arranged to your liking
235 and to improve the clarity of your meaning. You don't need to run then
236 altogether onto a single line. Additionally, since the 'Dict' type constraint
237 defines a hash constraint, the key order is not meaningful. For example:
248 {key1 => 1, key2 => "Hi!", key3 => 2};
249 {key2 => "Hi!", key1 => 100, key3 => 300};
251 As you would expect, since underneath its just a plain old Perl hash at work.
255 You should exercise some care as to whether or not your complex structured
256 constraints would be better off contained by a real object as in the following
259 package MyApp::MyStruct;
262 ## lazy way to make a bunch of attributes
263 has $_ for qw(full_name age_in_years);
265 package MyApp::MyClass;
268 has person => (isa => 'MyApp::MyStruct');
270 my $instance = MyApp::MyClass->new(
271 person=>MyApp::MyStruct->new(
277 This method may take some additional time to setup but will give you more
278 flexibility. However, structured constraints are highly compatible with this
279 method, granting some interesting possibilities for coercion. Try:
281 package MyApp::MyClass;
286 ## It's recommended your type declarations live in a separate class in order
287 ## to promote reusability and clarity. Inlined here for brevity.
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);
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';
298 ## Just a shorter version really.
300 as 'MyApp::MyStruct';
302 ## Add the coercions.
308 MyApp::MyStruct->new(%$_);
315 my $name = $_->{firstname} .' '. $_->{lastname};
316 my $age = DateTime->now - $_->{dob};
318 MyApp::MyStruct->new(
320 age_in_years=>$age->years,
324 has person => (isa=>MyStruct);
326 This would allow you to instantiate with something like:
328 my $obj = MyApp::MyClass->new( person => {
329 full_name=>'John Napiorkowski',
335 my $obj = MyApp::MyClass->new( person => {
337 firstname=>'Napiorkowski',
338 dob=>DateTime->new(year=>1969),
341 If you are not familiar with how coercions work, check out the L<Moose> cookbook
342 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
343 has additional examples and discussion.
345 =head2 Subtyping a Structured type constraint
347 You need to exercise some care when you try to subtype a structured type as in
351 as Dict[name => Str];
353 subtype FriendlyPerson,
356 total_friends => Int,
359 This will actually work BUT you have to take care that the subtype has a
360 structure that does not contradict the structure of it's parent. For now the
361 above works, but I will clarify the syntax for this at a future point, so
362 it's recommended to avoid (should not really be needed so much anyway). For
363 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
364 patches are welcomed for discussion. If you find a good use for this, please
369 Coercions currently work for 'one level' deep. That is you can do:
384 ## Coerce an object of a particular class
385 from BlessedPersonObject, via {
392 ## Coerce from [$name, $age]
399 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
400 from Dict[fullname=>Fullname, dob=>DateTime], via {
401 my $age = $_->dob - DateTime->now;
402 my $firstn = $_->{fullname}->{first};
403 my $lastn = $_->{fullname}->{last}
405 name => $_->{fullname}->{first} .' '. ,
410 And that should just work as expected. However, if there are any 'inner'
411 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
412 won't currently get activated.
414 Please see the test '07-coerce.t' for a more detailed example. Discussion on
415 extending coercions to support this welcome on the Moose development channel or
420 Newer versions of L<MooseX::Types> support recursive type constraints. That is
421 you can include a type constraint as a contained type constraint of itself. For
432 This would declare a Person subtype that contains a name and an optional
433 ArrayRef of Persons who are friends as in:
439 { name => 'Vincent' },
443 { name => 'Stephenie' },
450 Please take care to make sure the recursion node is either Optional, or declare
451 a Union with an non recursive option such as:
472 Otherwise you will define a subtype thatis impossible to validate since it is
473 infinitely recursive. For more information about defining recursive types,
474 please see the documentation in L<MooseX::Types> and the test cases.
476 =head1 TYPE CONSTRAINTS
478 This type library defines the following constraints.
480 =head2 Tuple[@constraints]
482 This defines an ArrayRef based constraint which allows you to validate a specific
483 list of contained constraints. For example:
485 Tuple[Int,Str]; ## Validates [1,'hello']
486 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
488 The Values of @constraints should ideally be L<MooseX::Types> declared type
489 constraints. We do support 'old style' L<Moose> string based constraints to a
490 limited degree but these string type constraints are considered deprecated.
491 There will be limited support for bugs resulting from mixing string and
492 L<MooseX::Types> in your structures. If you encounter such a bug and really
493 need it fixed, we will required a detailed test case at the minimum.
495 =head2 Dict[%constraints]
497 This defines a HashRef based constraint which allowed you to validate a specific
498 hashref. For example:
500 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
502 The keys in %constraints follow the same rules as @constraints in the above
505 =head2 Map[ $key_constraint, $value_constraint ]
507 This defines a HashRef based constraint in which both the keys and values are
508 required to meet certain constraints. For example, to map hostnames to IP
509 addresses, you might say:
511 Map[ HostName, IPAddress ]
513 The type constraint would only be met if every key was a valid HostName and
514 every value was a valid IPAddress.
516 =head2 Optional[$constraint]
518 This is primarily a helper constraint for Dict and Tuple type constraints. What
519 this allows is for you to assert that a given type constraint is allowed to be
520 null (but NOT undefined). If the value is null, then the type constraint passes
521 but if the value is defined it must validate against the type constraint. This
522 makes it easy to make a Dict where one or more of the keys doesn't have to exist
523 or a tuple where some of the values are not required. For example:
525 subtype Name() => as Dict[
528 middle=>Optional[Str],
531 Creates 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
533 string if it appears but doesn't have to appear. So in this case both the
536 {first=>'John', middle=>'James', last=>'Napiorkowski'}
537 {first=>'Vanessa', last=>'Li'}
539 If you use the 'Maybe' type constraint instead, your values will also validate
540 against 'undef', which may be incorrect for you.
542 =head1 EXPORTABLE SUBROUTINES
544 This type library makes available for export the following subroutines
548 Structured type constraints by their nature are closed; that is validation will
549 depend on an exact match between your structure definition and the arguments to
550 be checked. Sometimes you might wish for a slightly looser amount of validation.
551 For example, you may wish to validate the first 3 elements of an array reference
552 and allow for an arbitrary number of additional elements. At first thought you
553 might think you could do it this way:
555 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
556 subtype AllowTailingArgs,
564 However what this will actually validate are structures like this:
566 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
568 In order to allow structured validation of, "and then some", arguments, you can
569 use the L</slurpy> method against a type constraint. For example:
571 use MooseX::Types::Structured qw(Tuple slurpy);
573 subtype AllowTailingArgs,
578 slurpy ArrayRef[Int],
581 This will now work as expected, validating ArrayRef structures such as:
583 [1,"hello", $obj, 2,3,4,5,6,...]
585 A few caveats apply. First, the slurpy type constraint must be the last one in
586 the list of type constraint parameters. Second, the parent type of the slurpy
587 type constraint must match that of the containing type constraint. That means
588 that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
589 another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
590 HashRef, also including other Dict constraints).
592 Please note the the technical way this works 'under the hood' is that the
593 slurpy keyword transforms the target type constraint into a coderef. Please do
594 not try to create your own custom coderefs; always use the slurpy method. The
595 underlying technology may change in the future but the slurpy keyword will be
598 =head1 ERROR MESSAGES
600 Error reporting has been improved to return more useful debugging messages. Now
601 I will stringify the incoming check value with L<Devel::PartialDump> so that you
602 can 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
604 Structured Type is failing validation, you will see that. There's a limit to
605 how deep this internal reporting goes, but you shouldn't see any of the "failed
606 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
608 This support is continuing to expand, so it's best to use these messages for
609 debugging purposes and not for creating messages that 'escape into the wild'
610 such as error messages sent to the user.
612 Please see the test '12-error.t' for a more lengthy example. Your thoughts and
613 preferable tests or code patches very welcome!
617 Here are some additional example usage for structured types. All examples can
618 be found also in the 't/examples.t' test. Your contributions are also welcomed.
620 =head2 Normalize a HashRef
622 You need a hashref to conform to a canonical structure but are required accept a
623 bunch of different incoming structures. You can normalize using the Dict type
624 constraint and coercions. This example also shows structured types mixed which
625 other MooseX::Types libraries.
627 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
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)];
649 name => "$_->{first} $_->{last}",
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);"
663 name => "$_->{fullname}{first} $_->{fullname}{last}",
664 age => ($_->{dob} - 'DateTime'->now)->years,
667 has person => (is=>'rw', isa=>Person, coerce=>1);
669 And now you can instantiate with all the following:
673 name=>'John Napiorkowski',
681 last=>'Napiorkowski',
692 dob => 'DateTime'->new(
700 This technique is a way to support various ways to instantiate your class in a
701 clean and declarative way.
705 my $Optional = MooseX::Meta::TypeConstraint::Structured::Optional->new(
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();
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
721 ## But it's is okay if the value doesn't exists
728 Moose::Util::TypeConstraints::register_type_constraint($Optional);
729 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
731 Moose::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'),
735 constraint_generator=> sub {
736 ## Get the constraints and values to check
737 my ($type_constraints, $values) = @_;
738 my @type_constraints = defined $type_constraints ?
739 @$type_constraints : ();
741 my $overflow_handler;
742 if($type_constraints[-1] && blessed $type_constraints[-1]
743 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
744 $overflow_handler = pop @type_constraints;
747 my @values = defined $values ? @$values: ();
748 ## Perform the checking
750 while(@type_constraints) {
751 my $type_constraint = shift @type_constraints;
753 my $value = shift @values;
754 unless($type_constraint->check($value)) {
755 $_[2]->{message} = MooseX::Types::Structured::TypeError->new(
756 constraint => $type_constraint,
764 ## Test if the TC supports null values
765 unless ($type_constraint->is_subtype_of($Optional)) {
766 $_[2]->{message} = MooseX::Types::Structured::TypeError->new(
767 constraint => $type_constraint,
776 ## Make sure there are no leftovers.
778 if($overflow_handler) {
779 return $overflow_handler->check([@values], $_[2]);
781 $_[2]->{message} = "More values than Type Constraints!"
785 } elsif(@type_constraints) {
787 "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints)
797 Moose::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'),
801 constraint_generator=> sub {
802 ## Get the constraints and values to check
803 my ($type_constraints, $values) = @_;
804 my @type_constraints = defined $type_constraints ?
805 @$type_constraints : ();
807 my $overflow_handler;
808 if($type_constraints[-1] && blessed $type_constraints[-1]
809 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
810 $overflow_handler = pop @type_constraints;
812 my (%type_constraints) = @type_constraints;
813 my %values = defined $values ? %$values: ();
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)) {
822 $_[2]->{message} = MooseX::Types::Structured::TypeError->new(
823 constraint => $type_constraint,
831 ## Test to see if the TC supports null values
832 unless ($type_constraint->is_subtype_of($Optional)) {
833 $_[2]->{message} = MooseX::Types::Structured::TypeError->new(
834 constraint => $type_constraint,
842 ## Make sure there are no leftovers.
844 if($overflow_handler) {
845 return $overflow_handler->check(+{%values});
847 $_[2]->{message} = "More values than Type Constraints!"
851 } elsif(%type_constraints) {
853 "Not enough values for all defined type constraints. Remaining: ". join(', ',values %values)
863 Moose::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'),
867 constraint_generator=> sub {
868 ## Get the constraints and values to check
869 my ($type_constraints, $values) = @_;
870 my @constraints = defined $type_constraints ? @$type_constraints : ();
872 Carp::confess( "too many args for Map type" ) if @constraints > 2;
874 my ($key_type, $value_type) = @constraints == 2 ? @constraints
875 : @constraints == 1 ? (undef, @constraints)
878 my %values = defined $values ? %$values: ();
879 ## Perform the checking
881 for my $value (values %$values) {
882 unless ($value_type->check($value)) {
883 $_[2]->{message} = $value_type->get_message($value) if ref $_[2];
890 for my $key (keys %$values) {
891 unless ($key_type->check($key)) {
892 $_[2]->{message} = $key_type->get_message($key) if ref $_[2];
905 return MooseX::Types::Structured::OverflowHandler->new(
906 type_constraint => $tc,
912 The following modules or resources may be of interest.
914 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
915 L<MooseX::Meta::TypeConstraint::Structured>
919 Here's a list of stuff I would be happy to get volunteers helping with:
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
924 * Need to clarify deep coercions,
928 John Napiorkowski <jjnapiork@cpan.org>
932 The following people have contributed to this module and agree with the listed
933 Copyright & license information included below:
935 Florian Ragwitz, <rafl@debian.org>
936 Yuval Kogman, <nothingmuch@woobling.org>
937 Tomas Doran, <bobtfish@bobtfish.net>
939 =head1 COPYRIGHT & LICENSE
941 Copyright 2008-2009, John Napiorkowski <jjnapiork@cpan.org>
943 This program is free software; you can redistribute it and/or modify it under
944 the same terms as Perl itself.