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 0.22 -declare => [qw(Dict Map Tuple Optional)];
11 use Sub::Exporter 0.982 -setup => [ qw(Dict Map Tuple Optional slurpy) ];
12 use Devel::PartialDump 0.10;
13 use Scalar::Util qw(blessed);
17 The following is example usage for this module.
22 use MooseX::Types::Moose qw(Str Int HashRef);
23 use MooseX::Types::Structured qw(Dict Tuple Optional);
25 ## A name has a first and last part, but middle names are not required
30 middle => Optional[Str],
34 ## description is a string field followed by a HashRef of tagged data.
42 ## Remainder of your class attributes and methods
44 Then you can instantiate this class with something like:
46 my $john = Person->new(
50 last => 'Napiorkowski',
53 'A cool guy who loves Perl and Moose.', {
54 married_to => 'Vanessa Li',
62 my $vanessa = Person->new(
67 description => ['A great student!'],
70 But all of these would cause a constraint error for the 'name' attribute:
72 ## Value for 'name' not a HashRef
73 Person->new( name => 'John' );
75 ## Value for 'name' has incorrect hash key and missing required keys
76 Person->new( name => {
80 ## Also incorrect keys
81 Person->new( name => {
86 ## key 'middle' incorrect type, should be a Str not a ArrayRef
87 Person->new( name => {
93 And these would cause a constraint error for the 'description' attribute:
95 ## Should be an ArrayRef
96 Person->new( description => 'Hello I am a String' );
98 ## First element must be a string not a HashRef.
99 Person->new (description => [{
104 Please see the test cases for more examples.
108 A structured type constraint is a standard container L<Moose> type constraint,
109 such as an ArrayRef or HashRef, which has been enhanced to allow you to
110 explicitly name all the allowed type constraints inside the structure. The
113 TypeConstraint[@TypeParameters or %TypeParameters]
115 Where 'TypeParameters' is an array reference or hash references of
116 L<Moose::Meta::TypeConstraint> objects.
118 This type library enables structured type constraints. It is built on top of the
119 L<MooseX::Types> library system, so you should review the documentation for that
120 if you are not familiar with it.
122 =head2 Comparing Parameterized types to Structured types
124 Parameterized constraints are built into core Moose and you are probably already
125 familiar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
126 have similar functionality, so their syntax is likewise similar. For example,
127 you could define a parameterized constraint like:
132 which would constrain a value to something like [1,2,3,...] and so on. On the
133 other hand, a structured type constraint explicitly names all it's allowed
134 'internal' type parameter constraints. For the example:
136 subtype StringFollowedByInt,
139 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
140 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
143 package MyApp::Types;
145 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
146 use MooseX::Types::Moose qw(Str Int);
147 use MooseX::Types::Structured qw(Tuple Optional);
149 subtype StringIntOptionalHashRef,
155 This defines a type constraint that validates values like:
157 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
160 Notice that the last type constraint in the structure is optional. This is
161 enabled via the helper Optional type constraint, which is a variation of the
162 core Moose type constraint 'Maybe'. The main difference is that Optional type
163 constraints are required to validate if they exist, while 'Maybe' permits
164 undefined values. So the following example would not validate:
166 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
168 Please note the subtle difference between undefined and null. If you wish to
169 allow both null and undefined, you should use the core Moose 'Maybe' type
172 package MyApp::Types;
174 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
175 use MooseX::Types::Moose qw(Str Int Maybe);
176 use MooseX::Types::Structured qw(Tuple);
178 subtype StringIntMaybeHashRef,
180 Str, Int, Maybe[HashRef]
183 This would validate the following:
185 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
186 ['World', 200, undef];
189 Structured constraints are not limited to arrays. You can define a structure
190 against a HashRef with the 'Dict' type constaint as in this example:
192 subtype FirstNameLastName,
198 This would constrain a HashRef that validates something like:
200 {firstname => 'Christopher', lastname => 'Parsons'};
202 but all the following would fail validation:
205 {first => 'Christopher', last => 'Parsons'};
208 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
211 ['Christopher', 'Parsons'];
213 These structures can be as simple or elaborate as you wish. You can even
214 combine various structured, parameterized and simple constraints all together:
219 Dict[name=>Str, age=>Int],
225 [1, {name=>'John', age=>25},[10,11,12]];
227 Please notice how the type parameters can be visually arranged to your liking
228 and to improve the clarity of your meaning. You don't need to run then
229 altogether onto a single line. Additionally, since the 'Dict' type constraint
230 defines a hash constraint, the key order is not meaningful. For example:
241 {key1 => 1, key2 => "Hi!", key3 => 2};
242 {key2 => "Hi!", key1 => 100, key3 => 300};
244 As you would expect, since underneath its just a plain old Perl hash at work.
248 You should exercise some care as to whether or not your complex structured
249 constraints would be better off contained by a real object as in the following
252 package MyApp::MyStruct;
255 ## lazy way to make a bunch of attributes
256 has $_ for qw(full_name age_in_years);
258 package MyApp::MyClass;
261 has person => (isa => 'MyApp::MyStruct');
263 my $instance = MyApp::MyClass->new(
264 person=>MyApp::MyStruct->new(
270 This method may take some additional time to setup but will give you more
271 flexibility. However, structured constraints are highly compatible with this
272 method, granting some interesting possibilities for coercion. Try:
274 package MyApp::MyClass;
279 ## It's recommended your type declarations live in a separate class in order
280 ## to promote reusability and clarity. Inlined here for brevity.
282 use MooseX::Types::DateTime qw(DateTime);
283 use MooseX::Types -declare [qw(MyStruct)];
284 use MooseX::Types::Moose qw(Str Int);
285 use MooseX::Types::Structured qw(Dict);
287 ## Use class_type to create an ISA type constraint if your object doesn't
288 ## inherit from Moose::Object.
289 class_type 'MyApp::MyStruct';
291 ## Just a shorter version really.
293 as 'MyApp::MyStruct';
295 ## Add the coercions.
301 MyApp::MyStruct->new(%$_);
308 my $name = $_->{firstname} .' '. $_->{lastname};
309 my $age = DateTime->now - $_->{dob};
311 MyApp::MyStruct->new(
313 age_in_years=>$age->years,
317 has person => (isa=>MyStruct);
319 This would allow you to instantiate with something like:
321 my $obj = MyApp::MyClass->new( person => {
322 full_name=>'John Napiorkowski',
328 my $obj = MyApp::MyClass->new( person => {
330 firstname=>'Napiorkowski',
331 dob=>DateTime->new(year=>1969),
334 If you are not familiar with how coercions work, check out the L<Moose> cookbook
335 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
336 has additional examples and discussion.
338 =head2 Subtyping a Structured type constraint
340 You need to exercise some care when you try to subtype a structured type as in
344 as Dict[name => Str];
346 subtype FriendlyPerson,
349 total_friends => Int,
352 This will actually work BUT you have to take care that the subtype has a
353 structure that does not contradict the structure of it's parent. For now the
354 above works, but I will clarify the syntax for this at a future point, so
355 it's recommended to avoid (should not really be needed so much anyway). For
356 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
357 patches are welcomed for discussion. If you find a good use for this, please
362 Coercions currently work for 'one level' deep. That is you can do:
377 ## Coerce an object of a particular class
378 from BlessedPersonObject, via {
385 ## Coerce from [$name, $age]
392 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
393 from Dict[fullname=>Fullname, dob=>DateTime], via {
394 my $age = $_->dob - DateTime->now;
395 my $firstn = $_->{fullname}->{first};
396 my $lastn = $_->{fullname}->{last}
398 name => $_->{fullname}->{first} .' '. ,
403 And that should just work as expected. However, if there are any 'inner'
404 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
405 won't currently get activated.
407 Please see the test '07-coerce.t' for a more detailed example. Discussion on
408 extending coercions to support this welcome on the Moose development channel or
413 Newer versions of L<MooseX::Types> support recursive type constraints. That is
414 you can include a type constraint as a contained type constraint of itself. For
425 This would declare a Person subtype that contains a name and an optional
426 ArrayRef of Persons who are friends as in:
432 { name => 'Vincent' },
436 { name => 'Stephenie' },
443 Please take care to make sure the recursion node is either Optional, or declare
444 a Union with an non recursive option such as:
465 Otherwise you will define a subtype thatis impossible to validate since it is
466 infinitely recursive. For more information about defining recursive types,
467 please see the documentation in L<MooseX::Types> and the test cases.
469 =head1 TYPE CONSTRAINTS
471 This type library defines the following constraints.
473 =head2 Tuple[@constraints]
475 This defines an ArrayRef based constraint which allows you to validate a specific
476 list of contained constraints. For example:
478 Tuple[Int,Str]; ## Validates [1,'hello']
479 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
481 The Values of @constraints should ideally be L<MooseX::Types> declared type
482 constraints. We do support 'old style' L<Moose> string based constraints to a
483 limited degree but these string type constraints are considered deprecated.
484 There will be limited support for bugs resulting from mixing string and
485 L<MooseX::Types> in your structures. If you encounter such a bug and really
486 need it fixed, we will required a detailed test case at the minimum.
488 =head2 Dict[%constraints]
490 This defines a HashRef based constraint which allowed you to validate a specific
491 hashref. For example:
493 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
495 The keys in %constraints follow the same rules as @constraints in the above
498 =head2 Map[ $key_constraint, $value_constraint ]
500 This defines a HashRef based constraint in which both the keys and values are
501 required to meet certain constraints. For example, to map hostnames to IP
502 addresses, you might say:
504 Map[ HostName, IPAddress ]
506 The type constraint would only be met if every key was a valid HostName and
507 every value was a valid IPAddress.
509 =head2 Optional[$constraint]
511 This is primarily a helper constraint for Dict and Tuple type constraints. What
512 this allows is for you to assert that a given type constraint is allowed to be
513 null (but NOT undefined). If the value is null, then the type constraint passes
514 but if the value is defined it must validate against the type constraint. This
515 makes it easy to make a Dict where one or more of the keys doesn't have to exist
516 or a tuple where some of the values are not required. For example:
518 subtype Name() => as Dict[
521 middle=>Optional[Str],
524 Creates a constraint that validates against a hashref with the keys 'first' and
525 'last' being strings and required while an optional key 'middle' is must be a
526 string if it appears but doesn't have to appear. So in this case both the
529 {first=>'John', middle=>'James', last=>'Napiorkowski'}
530 {first=>'Vanessa', last=>'Li'}
532 If you use the 'Maybe' type constraint instead, your values will also validate
533 against 'undef', which may be incorrect for you.
535 =head1 EXPORTABLE SUBROUTINES
537 This type library makes available for export the following subroutines
541 Structured type constraints by their nature are closed; that is validation will
542 depend on an exact match between your structure definition and the arguments to
543 be checked. Sometimes you might wish for a slightly looser amount of validation.
544 For example, you may wish to validate the first 3 elements of an array reference
545 and allow for an arbitrary number of additional elements. At first thought you
546 might think you could do it this way:
548 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
549 subtype AllowTailingArgs,
557 However what this will actually validate are structures like this:
559 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
561 In order to allow structured validation of, "and then some", arguments, you can
562 use the L</slurpy> method against a type constraint. For example:
564 use MooseX::Types::Structured qw(Tuple slurpy);
566 subtype AllowTailingArgs,
571 slurpy ArrayRef[Int],
574 This will now work as expected, validating ArrayRef structures such as:
576 [1,"hello", $obj, 2,3,4,5,6,...]
578 A few caveats apply. First, the slurpy type constraint must be the last one in
579 the list of type constraint parameters. Second, the parent type of the slurpy
580 type constraint must match that of the containing type constraint. That means
581 that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
582 another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
583 HashRef, also including other Dict constraints).
585 Please note the the technical way this works 'under the hood' is that the
586 slurpy keyword transforms the target type constraint into a coderef. Please do
587 not try to create your own custom coderefs; always use the slurpy method. The
588 underlying technology may change in the future but the slurpy keyword will be
591 =head1 ERROR MESSAGES
593 Error reporting has been improved to return more useful debugging messages. Now
594 I will stringify the incoming check value with L<Devel::PartialDump> so that you
595 can see the actual structure that is tripping up validation. Also, I report the
596 'internal' validation error, so that if a particular element inside the
597 Structured Type is failing validation, you will see that. There's a limit to
598 how deep this internal reporting goes, but you shouldn't see any of the "failed
599 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
601 This support is continuing to expand, so it's best to use these messages for
602 debugging purposes and not for creating messages that 'escape into the wild'
603 such as error messages sent to the user.
605 Please see the test '12-error.t' for a more lengthy example. Your thoughts and
606 preferable tests or code patches very welcome!
610 Here are some additional example usage for structured types. All examples can
611 be found also in the 't/examples.t' test. Your contributions are also welcomed.
613 =head2 Normalize a HashRef
615 You need a hashref to conform to a canonical structure but are required accept a
616 bunch of different incoming structures. You can normalize using the Dict type
617 constraint and coercions. This example also shows structured types mixed which
618 other MooseX::Types libraries.
620 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
625 use MooseX::Types::Structured qw(Dict Tuple);
626 use MooseX::Types::DateTime qw(DateTime);
627 use MooseX::Types::Moose qw(Int Str Object);
628 use MooseX::Types -declare => [qw(Name Age Person)];
642 name => "$_->{first} $_->{last}",
652 ## DateTime needs to be inside of single quotes here to disambiguate the
653 ## class package from the DataTime type constraint imported via the
654 ## line "use MooseX::Types::DateTime qw(DateTime);"
656 name => "$_->{fullname}{first} $_->{fullname}{last}",
657 age => ($_->{dob} - 'DateTime'->now)->years,
660 has person => (is=>'rw', isa=>Person, coerce=>1);
662 And now you can instantiate with all the following:
666 name=>'John Napiorkowski',
674 last=>'Napiorkowski',
685 dob => 'DateTime'->new(
693 This technique is a way to support various ways to instantiate your class in a
694 clean and declarative way.
698 my $Optional = MooseX::Meta::TypeConstraint::Structured::Optional->new(
699 name => 'MooseX::Types::Structured::Optional',
700 package_defined_in => __PACKAGE__,
701 parent => find_type_constraint('Item'),
702 constraint => sub { 1 },
703 constraint_generator => sub {
704 my ($type_parameter, @args) = @_;
705 my $check = $type_parameter->_compiled_type_constraint();
708 ## Does the arg exist? Something exists if it's a 'real' value
709 ## or if it is set to undef.
710 if(exists($args[0])) {
711 ## If it exists, we need to validate it
714 ## But it's is okay if the value doesn't exists
721 Moose::Util::TypeConstraints::register_type_constraint($Optional);
722 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
724 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
725 MooseX::Meta::TypeConstraint::Structured->new(
726 name => "MooseX::Types::Structured::Tuple" ,
727 parent => find_type_constraint('ArrayRef'),
728 constraint_generator=> sub {
729 ## Get the constraints and values to check
730 my ($type_constraints, $values) = @_;
731 my @type_constraints = defined $type_constraints ?
732 @$type_constraints : ();
734 my $overflow_handler;
735 if($type_constraints[-1] && blessed $type_constraints[-1]
736 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
737 $overflow_handler = pop @type_constraints;
740 my @values = defined $values ? @$values: ();
741 ## Perform the checking
742 while(@type_constraints) {
743 my $type_constraint = shift @type_constraints;
745 my $value = shift @values;
746 unless($type_constraint->check($value)) {
747 $_[2]->{message} = $type_constraint->get_message($value)
752 ## Test if the TC supports null values
753 unless ($type_constraint->is_subtype_of($Optional)) {
754 $_[2]->{message} = $type_constraint->get_message('NULL')
760 ## Make sure there are no leftovers.
762 if($overflow_handler) {
763 return $overflow_handler->check([@values], $_[2]);
765 $_[2]->{message} = "More values than Type Constraints!"
769 } elsif(@type_constraints) {
771 "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints)
781 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
782 MooseX::Meta::TypeConstraint::Structured->new(
783 name => "MooseX::Types::Structured::Dict",
784 parent => find_type_constraint('HashRef'),
785 constraint_generator=> sub {
786 ## Get the constraints and values to check
787 my ($type_constraints, $values) = @_;
788 my @type_constraints = defined $type_constraints ?
789 @$type_constraints : ();
791 my $overflow_handler;
792 if($type_constraints[-1] && blessed $type_constraints[-1]
793 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
794 $overflow_handler = pop @type_constraints;
796 my (%type_constraints) = @type_constraints;
797 my %values = defined $values ? %$values: ();
798 ## Perform the checking
799 while(%type_constraints) {
800 my($key, $type_constraint) = each %type_constraints;
801 delete $type_constraints{$key};
802 if(exists $values{$key}) {
803 my $value = $values{$key};
804 delete $values{$key};
805 unless($type_constraint->check($value)) {
806 $_[2]->{message} = $type_constraint->get_message($value)
811 ## Test to see if the TC supports null values
812 unless ($type_constraint->is_subtype_of($Optional)) {
813 $_[2]->{message} = $type_constraint->get_message('NULL')
819 ## Make sure there are no leftovers.
821 if($overflow_handler) {
822 return $overflow_handler->check(+{%values});
824 $_[2]->{message} = "More values than Type Constraints!"
828 } elsif(%type_constraints) {
830 "Not enough values for all defined type constraints. Remaining: ". join(', ',values %values)
840 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
841 MooseX::Meta::TypeConstraint::Structured->new(
842 name => "MooseX::Types::Structured::Map",
843 parent => find_type_constraint('HashRef'),
844 constraint_generator=> sub {
845 ## Get the constraints and values to check
846 my ($type_constraints, $values) = @_;
847 my @constraints = defined $type_constraints ? @$type_constraints : ();
849 Carp::confess( "too many args for Map type" ) if @constraints > 2;
851 my ($key_type, $value_type) = @constraints == 2 ? @constraints
852 : @constraints == 1 ? (undef, @constraints)
855 my %values = defined $values ? %$values: ();
856 ## Perform the checking
858 for my $value (values %$values) {
859 unless ($value_type->check($value)) {
860 $_[2]->{message} = $value_type->get_message($value) if ref $_[2];
867 for my $key (keys %$values) {
868 unless ($key_type->check($key)) {
869 $_[2]->{message} = $key_type->get_message($key) if ref $_[2];
882 return MooseX::Types::Structured::OverflowHandler->new(
883 type_constraint => $tc,
889 The following modules or resources may be of interest.
891 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
892 L<MooseX::Meta::TypeConstraint::Structured>
896 Here's a list of stuff I would be happy to get volunteers helping with:
898 * All POD examples need test cases in t/documentation/*.t
899 * Want to break out the examples section to a separate cookbook style POD.
900 * Want more examples and best practice / usage guidance for authors
901 * Need to clarify deep coercions,