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 Map Tuple Optional)];
9 use Sub::Exporter -setup => [ qw(Dict Map Tuple Optional slurpy) ];
10 use Devel::PartialDump;
11 use Scalar::Util qw(blessed);
13 our $VERSION = '0.19';
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 ## Remainder of your class attributes and methods
49 Then you can instantiate this class with something like:
51 my $john = Person->new(
55 last => 'Napiorkowski',
58 'A cool guy who loves Perl and Moose.', {
59 married_to => 'Vanessa Li',
67 my $vanessa = Person->new(
72 description => ['A great student!'],
75 But all of these would cause a constraint error for the 'name' attribute:
77 ## Value for 'name' not a HashRef
78 Person->new( name => 'John' );
80 ## Value for 'name' has incorrect hash key and missing required keys
81 Person->new( name => {
85 ## Also incorrect keys
86 Person->new( name => {
91 ## key 'middle' incorrect type, should be a Str not a ArrayRef
92 Person->new( name => {
98 And these would cause a constraint error for the 'description' attribute:
100 ## Should be an ArrayRef
101 Person->new( description => 'Hello I am a String' );
103 ## First element must be a string not a HashRef.
104 Person->new (description => [{
109 Please see the test cases for more examples.
113 A structured type constraint is a standard container L<Moose> type constraint,
114 such as an ArrayRef or HashRef, which has been enhanced to allow you to
115 explicitly name all the allowed type constraints inside the structure. The
118 TypeConstraint[@TypeParameters or %TypeParameters]
120 Where 'TypeParameters' is an array reference or hash references of
121 L<Moose::Meta::TypeConstraint> objects.
123 This type library enables structured type constraints. It is built on top of the
124 L<MooseX::Types> library system, so you should review the documentation for that
125 if you are not familiar with it.
127 =head2 Comparing Parameterized types to Structured types
129 Parameterized constraints are built into core Moose and you are probably already
130 familar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
131 have similar functionality, so their syntax is likewise similar. For example,
132 you could define a parameterized constraint like:
137 which would constrain a value to something like [1,2,3,...] and so on. On the
138 other hand, a structured type constraint explicitly names all it's allowed
139 'internal' type parameter constraints. For the example:
141 subtype StringFollowedByInt,
144 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
145 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
148 package MyApp::Types;
150 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
151 use MooseX::Types::Moose qw(Str Int);
152 use MooseX::Types::Structured qw(Tuple Optional);
154 subtype StringIntOptionalHashRef,
160 This defines a type constraint that validates values like:
162 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
165 Notice that the last type constraint in the structure is optional. This is
166 enabled via the helper Optional type constraint, which is a variation of the
167 core Moose type constraint 'Maybe'. The main difference is that Optional type
168 constraints are required to validate if they exist, while 'Maybe' permits
169 undefined values. So the following example would not validate:
171 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
173 Please note the subtle difference between undefined and null. If you wish to
174 allow both null and undefined, you should use the core Moose 'Maybe' type
177 package MyApp::Types;
179 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
180 use MooseX::Types::Moose qw(Str Int Maybe);
181 use MooseX::Types::Structured qw(Tuple);
183 subtype StringIntMaybeHashRef,
185 Str, Int, Maybe[HashRef]
188 This would validate the following:
190 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
191 ['World', 200, undef];
194 Structured constraints are not limited to arrays. You can define a structure
195 against a HashRef with the 'Dict' type constaint as in this example:
197 subtype FirstNameLastName,
203 This would constrain a HashRef that validates something like:
205 {firstname => 'Christopher', lastname => 'Parsons'};
207 but all the following would fail validation:
210 {first => 'Christopher', last => 'Parsons'};
213 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
216 ['Christopher', 'Parsons'];
218 These structures can be as simple or elaborate as you wish. You can even
219 combine various structured, parameterized and simple constraints all together:
224 Dict[name=>Str, age=>Int],
230 [1, {name=>'John', age=>25},[10,11,12]];
232 Please notice how the type parameters can be visually arranged to your liking
233 and to improve the clarity of your meaning. You don't need to run then
234 altogether onto a single line. Additionally, since the 'Dict' type constraint
235 defines a hash constraint, the key order is not meaningful. For example:
246 {key1 => 1, key2 => "Hi!", key3 => 2};
247 {key2 => "Hi!", key1 => 100, key3 => 300};
249 As you would expect, since underneath its just a plain old Perl hash at work.
253 You should exercise some care as to whether or not your complex structured
254 constraints would be better off contained by a real object as in the following
257 package MyApp::MyStruct;
260 ## lazy way to make a bunch of attributes
261 has $_ for qw(full_name age_in_years);
263 package MyApp::MyClass;
266 has person => (isa => 'MyApp::MyStruct');
268 my $instance = MyApp::MyClass->new(
269 person=>MyApp::MyStruct->new(
275 This method may take some additional time to setup but will give you more
276 flexibility. However, structured constraints are highly compatible with this
277 method, granting some interesting possibilities for coercion. Try:
279 package MyApp::MyClass;
284 ## It's recommended your type declarations live in a separate class in order
285 ## to promote reusability and clarity. Inlined here for brevity.
287 use MooseX::Types::DateTime qw(DateTime);
288 use MooseX::Types -declare [qw(MyStruct)];
289 use MooseX::Types::Moose qw(Str Int);
290 use MooseX::Types::Structured qw(Dict);
292 ## Use class_type to create an ISA type constraint if your object doesn't
293 ## inherit from Moose::Object.
294 class_type 'MyApp::MyStruct';
296 ## Just a shorter version really.
298 as 'MyApp::MyStruct';
300 ## Add the coercions.
306 MyApp::MyStruct->new(%$_);
313 my $name = $_->{firstname} .' '. $_->{lastname};
314 my $age = DateTime->now - $_->{dob};
316 MyApp::MyStruct->new(
318 age_in_years=>$age->years,
322 has person => (isa=>MyStruct);
324 This would allow you to instantiate with something like:
326 my $obj = MyApp::MyClass->new( person => {
327 full_name=>'John Napiorkowski',
333 my $obj = MyApp::MyClass->new( person => {
335 firstname=>'Napiorkowski',
336 dob=>DateTime->new(year=>1969),
339 If you are not familiar with how coercions work, check out the L<Moose> cookbook
340 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
341 has additional examples and discussion.
343 =head2 Subtyping a Structured type constraint
345 You need to exercise some care when you try to subtype a structured type as in
349 as Dict[name => Str];
351 subtype FriendlyPerson,
354 total_friends => Int,
357 This will actually work BUT you have to take care that the subtype has a
358 structure that does not contradict the structure of it's parent. For now the
359 above works, but I will clarify the syntax for this at a future point, so
360 it's recommended to avoid (should not really be needed so much anyway). For
361 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
362 patches are welcomed for discussion. If you find a good use for this, please
367 Coercions currently work for 'one level' deep. That is you can do:
382 ## Coerce an object of a particular class
383 from BlessedPersonObject, via {
390 ## Coerce from [$name, $age]
397 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
398 from Dict[fullname=>Fullname, dob=>DateTime], via {
399 my $age = $_->dob - DateTime->now;
400 my $firstn = $_->{fullname}->{first};
401 my $lastn = $_->{fullname}->{last}
403 name => $_->{fullname}->{first} .' '. ,
408 And that should just work as expected. However, if there are any 'inner'
409 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
410 won't currently get activated.
412 Please see the test '07-coerce.t' for a more detailed example. Discussion on
413 extending coercions to support this welcome on the Moose development channel or
418 Newer versions of L<MooseX::Types> support recursive type constraints. That is
419 you can include a type constraint as a contained type constraint of itself. For
430 This would declare a Person subtype that contains a name and an optional
431 ArrayRef of Persons who are friends as in:
437 { name => 'Vincent' },
441 { name => 'Stephenie' },
448 Please take care to make sure the recursion node is either Optional, or declare
449 a Union with an non recursive option such as:
470 Otherwise you will define a subtype thatis impossible to validate since it is
471 infinitely recursive. For more information about defining recursive types,
472 please see the documentation in L<MooseX::Types> and the test cases.
474 =head1 TYPE CONSTRAINTS
476 This type library defines the following constraints.
478 =head2 Tuple[@constraints]
480 This defines an ArrayRef based constraint which allows you to validate a specific
481 list of contained constraints. For example:
483 Tuple[Int,Str]; ## Validates [1,'hello']
484 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
486 The Values of @constraints should ideally be L<MooseX::Types> declared type
487 constraints. We do support 'old style' L<Moose> string based constraints to a
488 limited degree but these string type constraints are considered deprecated.
489 There will be limited support for bugs resulting from mixing string and
490 L<MooseX::Types> in your structures. If you encounter such a bug and really
491 need it fixed, we will required a detailed test case at the minimum.
493 =head2 Dict[%constraints]
495 This defines a HashRef based constraint which allowed you to validate a specific
496 hashref. For example:
498 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
500 The keys in %constraints follow the same rules as @constraints in the above
503 =head2 Map[ $key_constraint, $value_constraint ]
505 This defines a HashRef based constraint in which both the keys and values are
506 required to meet certain constraints. For example, to map hostnames to IP
507 addresses, you might say:
509 Map[ HostName, IPAddress ]
511 The type constraint would only be met if every key was a valid HostName and
512 every value was a valid IPAddress.
514 =head2 Optional[$constraint]
516 This is primarily a helper constraint for Dict and Tuple type constraints. What
517 this allows is for you to assert that a given type constraint is allowed to be
518 null (but NOT undefined). If the value is null, then the type constraint passes
519 but if the value is defined it must validate against the type constraint. This
520 makes it easy to make a Dict where one or more of the keys doesn't have to exist
521 or a tuple where some of the values are not required. For example:
523 subtype Name() => as Dict[
526 middle=>Optional[Str],
529 Creates a constraint that validates against a hashref with the keys 'first' and
530 'last' being strings and required while an optional key 'middle' is must be a
531 string if it appears but doesn't have to appear. So in this case both the
534 {first=>'John', middle=>'James', last=>'Napiorkowski'}
535 {first=>'Vanessa', last=>'Li'}
537 If you use the 'Maybe' type constraint instead, your values will also validate
538 against 'undef', which may be incorrect for you.
540 =head1 EXPORTABLE SUBROUTINES
542 This type library makes available for export the following subroutines
546 Structured type constraints by their nature are closed; that is validation will
547 depend on an exact match between your structure definition and the arguments to
548 be checked. Sometimes you might wish for a slightly looser amount of validation.
549 For example, you may wish to validate the first 3 elements of an array reference
550 and allow for an arbitrary number of additional elements. At first thought you
551 might think you could do it this way:
553 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
554 subtype AllowTailingArgs,
562 However what this will actually validate are structures like this:
564 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
566 In order to allow structured validation of, "and then some", arguments, you can
567 use the L</slurpy> method against a type constraint. For example:
569 use MooseX::Types::Structured qw(Tuple slurpy);
571 subtype AllowTailingArgs,
576 slurpy ArrayRef[Int],
579 This will now work as expected, validating ArrayRef structures such as:
581 [1,"hello", $obj, 2,3,4,5,6,...]
583 A few caveats apply. First, the slurpy type constraint must be the last one in
584 the list of type constraint parameters. Second, the parent type of the slurpy
585 type constraint must match that of the containing type constraint. That means
586 that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
587 another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
588 HashRef, also including other Dict constraints).
590 Please note the the technical way this works 'under the hood' is that the
591 slurpy keyword transforms the target type constraint into a coderef. Please do
592 not try to create your own custom coderefs; always use the slurpy method. The
593 underlying technology may change in the future but the slurpy keyword will be
596 =head1 ERROR MESSAGES
598 Error reporting has been improved to return more useful debugging messages. Now
599 I will stringify the incoming check value with L<Devel::PartialDump> so that you
600 can 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
602 Structured Type is failing validation, you will see that. There's a limit to
603 how deep this internal reporting goes, but you shouldn't see any of the "failed
604 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
606 This support is continuing to expand, so it's best to use these messages for
607 debugging purposes and not for creating messages that 'escape into the wild'
608 such as error messages sent to the user.
610 Please see the test '12-error.t' for a more lengthy example. Your thoughts and
611 preferable tests or code patches very welcome!
615 Here are some additional example usage for structured types. All examples can
616 be found also in the 't/examples.t' test. Your contributions are also welcomed.
618 =head2 Normalize a HashRef
620 You need a hashref to conform to a canonical structure but are required accept a
621 bunch of different incoming structures. You can normalize using the Dict type
622 constraint and coercions. This example also shows structured types mixed which
623 other MooseX::Types libraries.
625 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
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)];
647 name => "$_->{first} $_->{last}",
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);"
661 name => "$_->{fullname}{first} $_->{fullname}{last}",
662 age => ($_->{dob} - 'DateTime'->now)->years,
665 has person => (is=>'rw', isa=>Person, coerce=>1);
667 And now you can instantiate with all the following:
671 name=>'John Napiorkowski',
679 last=>'Napiorkowski',
690 dob => 'DateTime'->new(
698 This technique is a way to support various ways to instantiate your class in a
699 clean and declarative way.
703 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
704 MooseX::Meta::TypeConstraint::Structured->new(
705 name => "MooseX::Types::Structured::Tuple" ,
706 parent => find_type_constraint('ArrayRef'),
707 constraint_generator=> sub {
708 ## Get the constraints and values to check
709 my ($type_constraints, $values) = @_;
710 my @type_constraints = defined $type_constraints ?
711 @$type_constraints : ();
713 my $overflow_handler;
714 if($type_constraints[-1] && blessed $type_constraints[-1]
715 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
716 $overflow_handler = pop @type_constraints;
719 my @values = defined $values ? @$values: ();
720 ## Perform the checking
721 while(@type_constraints) {
722 my $type_constraint = shift @type_constraints;
724 my $value = shift @values;
725 unless($type_constraint->check($value)) {
726 $_[2]->{message} = $type_constraint->get_message($value)
731 ## Test if the TC supports null values
732 unless($type_constraint->check()) {
733 $_[2]->{message} = $type_constraint->get_message('NULL')
739 ## Make sure there are no leftovers.
741 if($overflow_handler) {
742 return $overflow_handler->check([@values], $_[2]);
744 $_[2]->{message} = "More values than Type Constraints!"
748 } elsif(@type_constraints) {
750 "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints)
760 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
761 MooseX::Meta::TypeConstraint::Structured->new(
762 name => "MooseX::Types::Structured::Dict",
763 parent => find_type_constraint('HashRef'),
764 constraint_generator=> sub {
765 ## Get the constraints and values to check
766 my ($type_constraints, $values) = @_;
767 my @type_constraints = defined $type_constraints ?
768 @$type_constraints : ();
770 my $overflow_handler;
771 if($type_constraints[-1] && blessed $type_constraints[-1]
772 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
773 $overflow_handler = pop @type_constraints;
775 my (%type_constraints) = @type_constraints;
776 my %values = defined $values ? %$values: ();
777 ## Perform the checking
778 while(%type_constraints) {
779 my($key, $type_constraint) = each %type_constraints;
780 delete $type_constraints{$key};
781 if(exists $values{$key}) {
782 my $value = $values{$key};
783 delete $values{$key};
784 unless($type_constraint->check($value)) {
785 $_[2]->{message} = $type_constraint->get_message($value)
790 ## Test to see if the TC supports null values
791 unless($type_constraint->check()) {
792 $_[2]->{message} = $type_constraint->get_message('NULL')
798 ## Make sure there are no leftovers.
800 if($overflow_handler) {
801 return $overflow_handler->check(+{%values});
803 $_[2]->{message} = "More values than Type Constraints!"
807 } elsif(%type_constraints) {
809 "Not enough values for all defined type constraints. Remaining: ". join(', ',values %values)
819 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
820 MooseX::Meta::TypeConstraint::Structured->new(
821 name => "MooseX::Types::Structured::Map",
822 parent => find_type_constraint('HashRef'),
823 constraint_generator=> sub {
824 ## Get the constraints and values to check
825 my ($type_constraints, $values) = @_;
826 my @constraints = defined $type_constraints ? @$type_constraints : ();
828 Carp::confess( "too many args for Map type" ) if @constraints > 2;
830 my ($key_type, $value_type) = @constraints == 2 ? @constraints
831 : @constraints == 1 ? (undef, @constraints)
834 my %values = defined $values ? %$values: ();
835 ## Perform the checking
837 for my $value (values %$values) {
838 unless ($value_type->check($value)) {
839 $_[2]->{message} = $value_type->get_message($value) if ref $_[2];
846 for my $key (keys %$values) {
847 unless ($key_type->check($key)) {
848 $_[2]->{message} = $key_type->get_message($key) if ref $_[2];
860 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
861 name => 'MooseX::Types::Structured::Optional',
862 package_defined_in => __PACKAGE__,
863 parent => find_type_constraint('Item'),
864 constraint => sub { 1 },
865 constraint_generator => sub {
866 my ($type_parameter, @args) = @_;
867 my $check = $type_parameter->_compiled_type_constraint();
870 ## Does the arg exist? Something exists if it's a 'real' value
871 ## or if it is set to undef.
872 if(exists($args[0])) {
873 ## If it exists, we need to validate it
876 ## But it's is okay if the value doesn't exists
883 Moose::Util::TypeConstraints::register_type_constraint($Optional);
884 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
889 return MooseX::Types::Structured::OverflowHandler->new(
890 type_constraint => $tc,
896 The following modules or resources may be of interest.
898 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
899 L<MooseX::Meta::TypeConstraint::Structured>
903 Here's a list of stuff I would be happy to get volunteers helping with:
905 * All POD examples need test cases in t/documentation/*.t
906 * Want to break out the examples section to a separate cookbook style POD.
907 * Want more examples and best practice / usage guidance for authors
908 * Need to clarify deep coercions,
912 John Napiorkowski <jjnapiork@cpan.org>
916 The following people have contributed to this module and agree with the listed
917 Copyright & license information included below:
919 Florian Ragwitz, <rafl@debian.org>
920 Yuval Kogman, <nothingmuch@woobling.org>
921 Tomas Doran, <bobtfish@bobtfish.net>
923 =head1 COPYRIGHT & LICENSE
925 Copyright 2008-2009, John Napiorkowski <jjnapiork@cpan.org>
927 This program is free software; you can redistribute it and/or modify it under
928 the same terms as Perl itself.