1 package MooseX::Types::Structured;
2 # ABSTRACT: MooseX::Types::Structured - Structured Type Constraints for Moose
6 use Moose::Util::TypeConstraints 1.06;
7 use MooseX::Meta::TypeConstraint::Structured;
8 use MooseX::Meta::TypeConstraint::Structured::Optional;
9 use MooseX::Types::Structured::OverflowHandler;
10 use MooseX::Types::Structured::MessageStack;
11 use MooseX::Types 0.22 -declare => [qw(Dict Map Tuple Optional)];
12 use Sub::Exporter 0.982 -setup => [ qw(Dict Map Tuple Optional slurpy) ];
13 use Devel::PartialDump 0.10;
14 use Scalar::Util qw(blessed);
18 The following is example usage for this module.
23 use MooseX::Types::Moose qw(Str Int HashRef);
24 use MooseX::Types::Structured qw(Dict Tuple Optional);
26 ## A name has a first and last part, but middle names are not required
31 middle => Optional[Str],
35 ## description is a string field followed by a HashRef of tagged data.
43 ## Remainder of your class attributes and methods
45 Then you can instantiate this class with something like:
47 my $john = Person->new(
51 last => 'Napiorkowski',
54 'A cool guy who loves Perl and Moose.', {
55 married_to => 'Vanessa Li',
63 my $vanessa = Person->new(
68 description => ['A great student!'],
71 But all of these would cause a constraint error for the 'name' attribute:
73 ## Value for 'name' not a HashRef
74 Person->new( name => 'John' );
76 ## Value for 'name' has incorrect hash key and missing required keys
77 Person->new( name => {
81 ## Also incorrect keys
82 Person->new( name => {
87 ## key 'middle' incorrect type, should be a Str not a ArrayRef
88 Person->new( name => {
94 And these would cause a constraint error for the 'description' attribute:
96 ## Should be an ArrayRef
97 Person->new( description => 'Hello I am a String' );
99 ## First element must be a string not a HashRef.
100 Person->new (description => [{
105 Please see the test cases for more examples.
109 A structured type constraint is a standard container L<Moose> type constraint,
110 such as an ArrayRef or HashRef, which has been enhanced to allow you to
111 explicitly name all the allowed type constraints inside the structure. The
114 TypeConstraint[@TypeParameters or %TypeParameters]
116 Where 'TypeParameters' is an array reference or hash references of
117 L<Moose::Meta::TypeConstraint> objects.
119 This type library enables structured type constraints. It is built on top of the
120 L<MooseX::Types> library system, so you should review the documentation for that
121 if you are not familiar with it.
123 =head2 Comparing Parameterized types to Structured types
125 Parameterized constraints are built into core Moose and you are probably already
126 familiar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
127 have similar functionality, so their syntax is likewise similar. For example,
128 you could define a parameterized constraint like:
133 which would constrain a value to something like [1,2,3,...] and so on. On the
134 other hand, a structured type constraint explicitly names all it's allowed
135 'internal' type parameter constraints. For the example:
137 subtype StringFollowedByInt,
140 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
141 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
144 package MyApp::Types;
146 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
147 use MooseX::Types::Moose qw(Str Int);
148 use MooseX::Types::Structured qw(Tuple Optional);
150 subtype StringIntOptionalHashRef,
156 This defines a type constraint that validates values like:
158 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
161 Notice that the last type constraint in the structure is optional. This is
162 enabled via the helper Optional type constraint, which is a variation of the
163 core Moose type constraint 'Maybe'. The main difference is that Optional type
164 constraints are required to validate if they exist, while 'Maybe' permits
165 undefined values. So the following example would not validate:
167 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
169 Please note the subtle difference between undefined and null. If you wish to
170 allow both null and undefined, you should use the core Moose 'Maybe' type
173 package MyApp::Types;
175 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
176 use MooseX::Types::Moose qw(Str Int Maybe);
177 use MooseX::Types::Structured qw(Tuple);
179 subtype StringIntMaybeHashRef,
181 Str, Int, Maybe[HashRef]
184 This would validate the following:
186 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
187 ['World', 200, undef];
190 Structured constraints are not limited to arrays. You can define a structure
191 against a HashRef with the 'Dict' type constaint as in this example:
193 subtype FirstNameLastName,
199 This would constrain a HashRef that validates something like:
201 {firstname => 'Christopher', lastname => 'Parsons'};
203 but all the following would fail validation:
206 {first => 'Christopher', last => 'Parsons'};
209 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
212 ['Christopher', 'Parsons'];
214 These structures can be as simple or elaborate as you wish. You can even
215 combine various structured, parameterized and simple constraints all together:
220 Dict[name=>Str, age=>Int],
226 [1, {name=>'John', age=>25},[10,11,12]];
228 Please notice how the type parameters can be visually arranged to your liking
229 and to improve the clarity of your meaning. You don't need to run then
230 altogether onto a single line. Additionally, since the 'Dict' type constraint
231 defines a hash constraint, the key order is not meaningful. For example:
242 {key1 => 1, key2 => "Hi!", key3 => 2};
243 {key2 => "Hi!", key1 => 100, key3 => 300};
245 As you would expect, since underneath its just a plain old Perl hash at work.
249 You should exercise some care as to whether or not your complex structured
250 constraints would be better off contained by a real object as in the following
253 package MyApp::MyStruct;
256 ## lazy way to make a bunch of attributes
257 has $_ for qw(full_name age_in_years);
259 package MyApp::MyClass;
262 has person => (isa => 'MyApp::MyStruct');
264 my $instance = MyApp::MyClass->new(
265 person=>MyApp::MyStruct->new(
271 This method may take some additional time to setup but will give you more
272 flexibility. However, structured constraints are highly compatible with this
273 method, granting some interesting possibilities for coercion. Try:
275 package MyApp::MyClass;
280 ## It's recommended your type declarations live in a separate class in order
281 ## to promote reusability and clarity. Inlined here for brevity.
283 use MooseX::Types::DateTime qw(DateTime);
284 use MooseX::Types -declare [qw(MyStruct)];
285 use MooseX::Types::Moose qw(Str Int);
286 use MooseX::Types::Structured qw(Dict);
288 ## Use class_type to create an ISA type constraint if your object doesn't
289 ## inherit from Moose::Object.
290 class_type 'MyApp::MyStruct';
292 ## Just a shorter version really.
294 as 'MyApp::MyStruct';
296 ## Add the coercions.
302 MyApp::MyStruct->new(%$_);
309 my $name = $_->{firstname} .' '. $_->{lastname};
310 my $age = DateTime->now - $_->{dob};
312 MyApp::MyStruct->new(
314 age_in_years=>$age->years,
318 has person => (isa=>MyStruct);
320 This would allow you to instantiate with something like:
322 my $obj = MyApp::MyClass->new( person => {
323 full_name=>'John Napiorkowski',
329 my $obj = MyApp::MyClass->new( person => {
331 firstname=>'Napiorkowski',
332 dob=>DateTime->new(year=>1969),
335 If you are not familiar with how coercions work, check out the L<Moose> cookbook
336 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
337 has additional examples and discussion.
339 =head2 Subtyping a Structured type constraint
341 You need to exercise some care when you try to subtype a structured type as in
345 as Dict[name => Str];
347 subtype FriendlyPerson,
350 total_friends => Int,
353 This will actually work BUT you have to take care that the subtype has a
354 structure that does not contradict the structure of it's parent. For now the
355 above works, but I will clarify the syntax for this at a future point, so
356 it's recommended to avoid (should not really be needed so much anyway). For
357 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
358 patches are welcomed for discussion. If you find a good use for this, please
363 Coercions currently work for 'one level' deep. That is you can do:
378 ## Coerce an object of a particular class
379 from BlessedPersonObject, via {
386 ## Coerce from [$name, $age]
393 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
394 from Dict[fullname=>Fullname, dob=>DateTime], via {
395 my $age = $_->dob - DateTime->now;
396 my $firstn = $_->{fullname}->{first};
397 my $lastn = $_->{fullname}->{last}
399 name => $_->{fullname}->{first} .' '. ,
404 And that should just work as expected. However, if there are any 'inner'
405 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
406 won't currently get activated.
408 Please see the test '07-coerce.t' for a more detailed example. Discussion on
409 extending coercions to support this welcome on the Moose development channel or
414 Newer versions of L<MooseX::Types> support recursive type constraints. That is
415 you can include a type constraint as a contained type constraint of itself. For
426 This would declare a Person subtype that contains a name and an optional
427 ArrayRef of Persons who are friends as in:
433 { name => 'Vincent' },
437 { name => 'Stephenie' },
444 Please take care to make sure the recursion node is either Optional, or declare
445 a Union with an non recursive option such as:
466 Otherwise you will define a subtype thatis impossible to validate since it is
467 infinitely recursive. For more information about defining recursive types,
468 please see the documentation in L<MooseX::Types> and the test cases.
470 =head1 TYPE CONSTRAINTS
472 This type library defines the following constraints.
474 =head2 Tuple[@constraints]
476 This defines an ArrayRef based constraint which allows you to validate a specific
477 list of contained constraints. For example:
479 Tuple[Int,Str]; ## Validates [1,'hello']
480 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
482 The Values of @constraints should ideally be L<MooseX::Types> declared type
483 constraints. We do support 'old style' L<Moose> string based constraints to a
484 limited degree but these string type constraints are considered deprecated.
485 There will be limited support for bugs resulting from mixing string and
486 L<MooseX::Types> in your structures. If you encounter such a bug and really
487 need it fixed, we will required a detailed test case at the minimum.
489 =head2 Dict[%constraints]
491 This defines a HashRef based constraint which allowed you to validate a specific
492 hashref. For example:
494 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
496 The keys in %constraints follow the same rules as @constraints in the above
499 =head2 Map[ $key_constraint, $value_constraint ]
501 This defines a HashRef based constraint in which both the keys and values are
502 required to meet certain constraints. For example, to map hostnames to IP
503 addresses, you might say:
505 Map[ HostName, IPAddress ]
507 The type constraint would only be met if every key was a valid HostName and
508 every value was a valid IPAddress.
510 =head2 Optional[$constraint]
512 This is primarily a helper constraint for Dict and Tuple type constraints. What
513 this allows is for you to assert that a given type constraint is allowed to be
514 null (but NOT undefined). If the value is null, then the type constraint passes
515 but if the value is defined it must validate against the type constraint. This
516 makes it easy to make a Dict where one or more of the keys doesn't have to exist
517 or a tuple where some of the values are not required. For example:
519 subtype Name() => as Dict[
522 middle=>Optional[Str],
525 Creates a constraint that validates against a hashref with the keys 'first' and
526 'last' being strings and required while an optional key 'middle' is must be a
527 string if it appears but doesn't have to appear. So in this case both the
530 {first=>'John', middle=>'James', last=>'Napiorkowski'}
531 {first=>'Vanessa', last=>'Li'}
533 If you use the 'Maybe' type constraint instead, your values will also validate
534 against 'undef', which may be incorrect for you.
536 =head1 EXPORTABLE SUBROUTINES
538 This type library makes available for export the following subroutines
542 Structured type constraints by their nature are closed; that is validation will
543 depend on an exact match between your structure definition and the arguments to
544 be checked. Sometimes you might wish for a slightly looser amount of validation.
545 For example, you may wish to validate the first 3 elements of an array reference
546 and allow for an arbitrary number of additional elements. At first thought you
547 might think you could do it this way:
549 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
550 subtype AllowTailingArgs,
558 However what this will actually validate are structures like this:
560 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
562 In order to allow structured validation of, "and then some", arguments, you can
563 use the L</slurpy> method against a type constraint. For example:
565 use MooseX::Types::Structured qw(Tuple slurpy);
567 subtype AllowTailingArgs,
572 slurpy ArrayRef[Int],
575 This will now work as expected, validating ArrayRef structures such as:
577 [1,"hello", $obj, 2,3,4,5,6,...]
579 A few caveats apply. First, the slurpy type constraint must be the last one in
580 the list of type constraint parameters. Second, the parent type of the slurpy
581 type constraint must match that of the containing type constraint. That means
582 that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
583 another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
584 HashRef, also including other Dict constraints).
586 Please note the the technical way this works 'under the hood' is that the
587 slurpy keyword transforms the target type constraint into a coderef. Please do
588 not try to create your own custom coderefs; always use the slurpy method. The
589 underlying technology may change in the future but the slurpy keyword will be
592 =head1 ERROR MESSAGES
594 Error reporting has been improved to return more useful debugging messages. Now
595 I will stringify the incoming check value with L<Devel::PartialDump> so that you
596 can see the actual structure that is tripping up validation. Also, I report the
597 'internal' validation error, so that if a particular element inside the
598 Structured Type is failing validation, you will see that. There's a limit to
599 how deep this internal reporting goes, but you shouldn't see any of the "failed
600 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
602 This support is continuing to expand, so it's best to use these messages for
603 debugging purposes and not for creating messages that 'escape into the wild'
604 such as error messages sent to the user.
606 Please see the test '12-error.t' for a more lengthy example. Your thoughts and
607 preferable tests or code patches very welcome!
611 Here are some additional example usage for structured types. All examples can
612 be found also in the 't/examples.t' test. Your contributions are also welcomed.
614 =head2 Normalize a HashRef
616 You need a hashref to conform to a canonical structure but are required accept a
617 bunch of different incoming structures. You can normalize using the Dict type
618 constraint and coercions. This example also shows structured types mixed which
619 other MooseX::Types libraries.
621 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
626 use MooseX::Types::Structured qw(Dict Tuple);
627 use MooseX::Types::DateTime qw(DateTime);
628 use MooseX::Types::Moose qw(Int Str Object);
629 use MooseX::Types -declare => [qw(Name Age Person)];
643 name => "$_->{first} $_->{last}",
653 ## DateTime needs to be inside of single quotes here to disambiguate the
654 ## class package from the DataTime type constraint imported via the
655 ## line "use MooseX::Types::DateTime qw(DateTime);"
657 name => "$_->{fullname}{first} $_->{fullname}{last}",
658 age => ($_->{dob} - 'DateTime'->now)->years,
661 has person => (is=>'rw', isa=>Person, coerce=>1);
663 And now you can instantiate with all the following:
667 name=>'John Napiorkowski',
675 last=>'Napiorkowski',
686 dob => 'DateTime'->new(
694 This technique is a way to support various ways to instantiate your class in a
695 clean and declarative way.
699 my $Optional = MooseX::Meta::TypeConstraint::Structured::Optional->new(
700 name => 'MooseX::Types::Structured::Optional',
701 package_defined_in => __PACKAGE__,
702 parent => find_type_constraint('Item'),
703 constraint => sub { 1 },
704 constraint_generator => sub {
705 my ($type_parameter, @args) = @_;
706 my $check = $type_parameter->_compiled_type_constraint();
709 ## Does the arg exist? Something exists if it's a 'real' value
710 ## or if it is set to undef.
711 if(exists($args[0])) {
712 ## If it exists, we need to validate it
715 ## But it's is okay if the value doesn't exists
722 Moose::Util::TypeConstraints::register_type_constraint($Optional);
723 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
725 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
726 MooseX::Meta::TypeConstraint::Structured->new(
727 name => "MooseX::Types::Structured::Tuple" ,
728 parent => find_type_constraint('ArrayRef'),
729 constraint_generator=> sub {
730 ## Get the constraints and values to check
731 my ($type_constraints, $values) = @_;
732 my @type_constraints = defined $type_constraints ?
733 @$type_constraints : ();
735 my $overflow_handler;
736 if($type_constraints[-1] && blessed $type_constraints[-1]
737 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
738 $overflow_handler = pop @type_constraints;
741 my @values = defined $values ? @$values: ();
742 ## Perform the checking
743 while(@type_constraints) {
744 my $type_constraint = shift @type_constraints;
746 my $value = shift @values;
747 unless($type_constraint->check($value)) {
749 my $message = $type_constraint->validate($value,$_[2]);
750 $_[2]->add_message({message=>$message,level=>$_[2]->level});
755 ## Test if the TC supports null values
756 unless ($type_constraint->is_subtype_of($Optional)) {
758 my $message = $type_constraint->get_message('NULL',$_[2]);
759 $_[2]->add_message({message=>$message,level=>$_[2]->level});
765 ## Make sure there are no leftovers.
767 if($overflow_handler) {
768 return $overflow_handler->check([@values], $_[2]);
771 my $message = "More values than Type Constraints!";
772 $_[2]->add_message({message=>$message,level=>$_[2]->level});
776 } elsif(@type_constraints) {
778 my $message = "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints);
779 $_[2]->add_message({message=>$message,level=>$_[2]->level});
789 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
790 MooseX::Meta::TypeConstraint::Structured->new(
791 name => "MooseX::Types::Structured::Dict",
792 parent => find_type_constraint('HashRef'),
793 constraint_generator => sub {
794 ## Get the constraints and values to check
795 my ($type_constraints, $values) = @_;
796 my @type_constraints = defined $type_constraints ?
797 @$type_constraints : ();
799 my $overflow_handler;
800 if($type_constraints[-1] && blessed $type_constraints[-1]
801 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
802 $overflow_handler = pop @type_constraints;
804 my (%type_constraints) = @type_constraints;
805 my %values = defined $values ? %$values: ();
806 ## Perform the checking
807 while(%type_constraints) {
808 my($key, $type_constraint) = each %type_constraints;
809 delete $type_constraints{$key};
810 if(exists $values{$key}) {
811 my $value = $values{$key};
812 delete $values{$key};
813 unless($type_constraint->check($value)) {
815 my $message = $type_constraint->validate($value,$_[2]);
816 $_[2]->add_message({message=>$message,level=>$_[2]->level});
821 ## Test to see if the TC supports null values
822 unless ($type_constraint->is_subtype_of($Optional)) {
824 my $message = $type_constraint->get_message('NULL',$_[2]);
825 $_[2]->add_message({message=>$message,level=>$_[2]->level});
831 ## Make sure there are no leftovers.
833 if($overflow_handler) {
834 return $overflow_handler->check(+{%values});
837 my $message = "More values than Type Constraints!";
838 $_[2]->add_message({message=>$message,level=>$_[2]->level});
842 } elsif(%type_constraints) {
844 my $message = "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints);
845 $_[2]->add_message({message=>$message,level=>$_[2]->level});
855 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
856 MooseX::Meta::TypeConstraint::Structured->new(
857 name => "MooseX::Types::Structured::Map",
858 parent => find_type_constraint('HashRef'),
859 constraint_generator=> sub {
860 ## Get the constraints and values to check
861 my ($type_constraints, $values) = @_;
862 my @constraints = defined $type_constraints ? @$type_constraints : ();
864 Carp::confess( "too many args for Map type" ) if @constraints > 2;
866 my ($key_type, $value_type) = @constraints == 2 ? @constraints
867 : @constraints == 1 ? (undef, @constraints)
870 my %values = defined $values ? %$values: ();
871 ## Perform the checking
873 for my $value (values %$values) {
874 unless ($value_type->check($value)) {
876 my $message = $value_type->validate($value,$_[2]);
877 $_[2]->add_message({message=>$message,level=>$_[2]->level});
885 for my $key (keys %$values) {
886 unless ($key_type->check($key)) {
888 my $message = $key_type->validate($key,$_[2]);
889 $_[2]->add_message({message=>$message,level=>$_[2]->level});
903 return MooseX::Types::Structured::OverflowHandler->new(
904 type_constraint => $tc,
910 The following modules or resources may be of interest.
912 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
913 L<MooseX::Meta::TypeConstraint::Structured>
917 Here's a list of stuff I would be happy to get volunteers helping with:
919 * All POD examples need test cases in t/documentation/*.t
920 * Want to break out the examples section to a separate cookbook style POD.
921 * Want more examples and best practice / usage guidance for authors
922 * Need to clarify deep coercions,