1 package MooseX::Types::Structured;
2 # ABSTRACT: 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.13;
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 C<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 C<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 C<ArrayRef> or C<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 C<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 C<HashRef> and C<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 its value to things like C<< ['hello', 111] >> but C<< ['hello', 'world'] >>
141 would fail, as well as C<< ['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 C<Optional> type constraint, which is a variation of the
163 core Moose type constraint C<Maybe>. The main difference is that C<Optional> type
164 constraints are required to validate if they exist, while C<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 C<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 C<HashRef> with the C<Dict> type constraint as in this example:
193 subtype FirstNameLastName,
199 This would constrain a C<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 C<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 it's 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 set up 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 =for stopwords Subtyping
341 =head2 Subtyping a Structured type constraint
343 You need to exercise some care when you try to subtype a structured type as in
347 as Dict[name => Str];
349 subtype FriendlyPerson,
352 total_friends => Int,
355 This will actually work BUT you have to take care that the subtype has a
356 structure that does not contradict the structure of it's parent. For now the
357 above works, but I will clarify the syntax for this at a future point, so
358 it's recommended to avoid (should not really be needed so much anyway). For
359 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
360 patches are welcomed for discussion. If you find a good use for this, please
365 Coercions currently work for 'one level' deep. That is you can do:
380 ## Coerce an object of a particular class
381 from BlessedPersonObject, via {
388 ## Coerce from [$name, $age]
395 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
396 from Dict[fullname=>Fullname, dob=>DateTime], via {
397 my $age = $_->dob - DateTime->now;
398 my $firstn = $_->{fullname}->{first};
399 my $lastn = $_->{fullname}->{last}
401 name => $_->{fullname}->{first} .' '. ,
406 And that should just work as expected. However, if there are any 'inner'
407 coercions, such as a coercion on C<Fullname> or on C<DateTime>, that coercion
408 won't currently get activated.
410 Please see the test F<07-coerce.t> for a more detailed example. Discussion on
411 extending coercions to support this welcome on the Moose development channel or
416 Newer versions of L<MooseX::Types> support recursive type constraints. That is
417 you can include a type constraint as a contained type constraint of itself. For
428 This would declare a C<Person> subtype that contains a name and an optional
429 C<ArrayRef> of C<Person>s who are friends as in:
435 { name => 'Vincent' },
439 { name => 'Stephenie' },
446 Please take care to make sure the recursion node is either C<Optional>, or declare
447 a union with an non-recursive option such as:
468 Otherwise you will define a subtype that is impossible to validate since it is
469 infinitely recursive. For more information about defining recursive types,
470 please see the documentation in L<MooseX::Types> and the test cases.
472 =head1 TYPE CONSTRAINTS
474 This type library defines the following constraints.
476 =head2 Tuple[@constraints]
478 This defines an ArrayRef based constraint which allows you to validate a specific
479 list of contained constraints. For example:
481 Tuple[Int,Str]; ## Validates [1,'hello']
482 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
484 The Values of @constraints should ideally be L<MooseX::Types> declared type
485 constraints. We do support 'old style' L<Moose> string based constraints to a
486 limited degree but these string type constraints are considered deprecated.
487 There will be limited support for bugs resulting from mixing string and
488 L<MooseX::Types> in your structures. If you encounter such a bug and really
489 need it fixed, we will required a detailed test case at the minimum.
491 =head2 Dict[%constraints]
493 This defines a HashRef based constraint which allowed you to validate a specific
494 hashref. For example:
496 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
498 The keys in C<%constraints> follow the same rules as C<@constraints> in the above
501 =head2 Map[ $key_constraint, $value_constraint ]
503 This defines a C<HashRef>-based constraint in which both the keys and values are
504 required to meet certain constraints. For example, to map hostnames to IP
505 addresses, you might say:
507 Map[ HostName, IPAddress ]
509 The type constraint would only be met if every key was a valid C<HostName> and
510 every value was a valid C<IPAddress>.
512 =head2 Optional[$constraint]
514 This is primarily a helper constraint for C<Dict> and C<Tuple> type constraints. What
515 this allows is for you to assert that a given type constraint is allowed to be
516 null (but NOT undefined). If the value is null, then the type constraint passes
517 but if the value is defined it must validate against the type constraint. This
518 makes it easy to make a Dict where one or more of the keys doesn't have to exist
519 or a tuple where some of the values are not required. For example:
521 subtype Name() => as Dict[
524 middle=>Optional[Str],
527 ...creates a constraint that validates against a hashref with the keys 'first' and
528 'last' being strings and required while an optional key 'middle' is must be a
529 string if it appears but doesn't have to appear. So in this case both the
532 {first=>'John', middle=>'James', last=>'Napiorkowski'}
533 {first=>'Vanessa', last=>'Li'}
535 If you use the C<Maybe> type constraint instead, your values will also validate
536 against C<undef>, which may be incorrect for you.
538 =head1 EXPORTABLE SUBROUTINES
540 This type library makes available for export the following subroutines
542 =for stopwords slurpy
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 C<Tuple> can allow a slurpy C<ArrayRef> (or children of C<ArrayRef>s, including
587 another C<Tuple>) and a C<Dict> can allow a slurpy C<HashRef> (or children/subtypes of
588 HashRef, also including other C<Dict> constraints).
590 Please note 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 C<Dict> type
622 constraint and coercions. This example also shows structured types mixed which
623 other L<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 my $Optional = MooseX::Meta::TypeConstraint::Structured::Optional->new(
704 name => 'MooseX::Types::Structured::Optional',
705 package_defined_in => __PACKAGE__,
706 parent => find_type_constraint('Item'),
707 constraint => sub { 1 },
708 constraint_generator => sub {
709 my ($type_parameter, @args) = @_;
710 my $check = $type_parameter->_compiled_type_constraint();
713 ## Does the arg exist? Something exists if it's a 'real' value
714 ## or if it is set to undef.
715 if(exists($args[0])) {
716 ## If it exists, we need to validate it
719 ## But it's is okay if the value doesn't exists
727 my ($obj, $type) = @_;
729 return $obj->can('equals')
730 ? $obj->equals($type)
734 my $CompiledTC = sub {
737 my $method = '_compiled_type_constraint';
739 $obj->$IsType('Any') ? undef
740 : $obj->can($method) ? $obj->$method
741 : sub { $obj->check(shift) },
745 Moose::Util::TypeConstraints::register_type_constraint($Optional);
746 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
748 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
749 MooseX::Meta::TypeConstraint::Structured->new(
750 name => "MooseX::Types::Structured::Tuple" ,
751 parent => find_type_constraint('ArrayRef'),
752 constraint_generator=> sub {
753 ## Get the constraints and values to check
754 my ($self, $type_constraints) = @_;
755 $type_constraints ||= $self->type_constraints;
756 my @type_constraints = defined $type_constraints ?
757 @$type_constraints : ();
759 my $overflow_handler;
760 if($type_constraints[-1] && blessed $type_constraints[-1]
761 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
762 $overflow_handler = pop @type_constraints;
765 my $length = $#type_constraints;
766 foreach my $idx (0..$length) {
767 unless(blessed $type_constraints[$idx]) {
768 ($type_constraints[$idx] = find_type_constraint($type_constraints[$idx]))
769 || die "$type_constraints[$idx] is not a registered type";
773 my (@checks, @optional, $o_check, $is_compiled);
775 my ($values, $err) = @_;
776 my @values = defined $values ? @$values : ();
778 ## initialise on first time run
779 unless ($is_compiled) {
780 @checks = map { $_->$CompiledTC } @type_constraints;
781 @optional = map { $_->is_subtype_of($Optional) } @type_constraints;
782 $o_check = $overflow_handler->$CompiledTC
783 if $overflow_handler;
787 ## Perform the checking
789 for my $type_index (0 .. $#checks) {
791 my $type_constraint = $checks[ $type_index ];
794 my $value = shift @values;
797 unless $type_constraint;
799 unless($type_constraint->($value)) {
801 my $message = $type_constraints[ $type_index ]->validate($value,$err);
802 $err->add_message({message=>$message,level=>$err->level});
807 ## Test if the TC supports null values
808 unless ($optional[ $type_index ]) {
810 my $message = $type_constraints[ $type_index ]->get_message('NULL',$err);
811 $err->add_message({message=>$message,level=>$err->level});
818 ## Make sure there are no leftovers.
820 if($overflow_handler) {
821 return $o_check->([@values], $err);
824 my $message = "More values than Type Constraints!";
825 $err->add_message({message=>$message,level=>$err->level});
837 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
838 MooseX::Meta::TypeConstraint::Structured->new(
839 name => "MooseX::Types::Structured::Dict",
840 parent => find_type_constraint('HashRef'),
841 constraint_generator => sub {
842 ## Get the constraints and values to check
843 my ($self, $type_constraints) = @_;
844 $type_constraints = $self->type_constraints;
845 my @type_constraints = defined $type_constraints ?
846 @$type_constraints : ();
848 my $overflow_handler;
849 if($type_constraints[-1] && blessed $type_constraints[-1]
850 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
851 $overflow_handler = pop @type_constraints;
853 my %type_constraints = @type_constraints;
854 foreach my $key (keys %type_constraints) {
855 unless(blessed $type_constraints{$key}) {
856 ($type_constraints{$key} = find_type_constraint($type_constraints{$key}))
857 || die "$type_constraints{$key} is not a registered type";
861 my (%check, %optional, $o_check, $is_compiled);
863 my ($values, $err) = @_;
864 my %values = defined $values ? %$values: ();
866 unless ($is_compiled) {
867 %check = map { ($_ => $type_constraints{ $_ }->$CompiledTC) } keys %type_constraints;
868 %optional = map { ($_ => $type_constraints{ $_ }->is_subtype_of($Optional)) } keys %type_constraints;
869 $o_check = $overflow_handler->$CompiledTC
870 if $overflow_handler;
874 ## Perform the checking
876 for my $key (keys %check) {
877 my $type_constraint = $check{ $key };
879 if(exists $values{$key}) {
880 my $value = $values{$key};
881 delete $values{$key};
884 unless $type_constraint;
886 unless($type_constraint->($value)) {
888 my $message = $type_constraints{ $key }->validate($value,$err);
889 $err->add_message({message=>$message,level=>$err->level});
894 ## Test to see if the TC supports null values
895 unless ($optional{ $key }) {
897 my $message = $type_constraints{ $key }->get_message('NULL',$err);
898 $err->add_message({message=>$message,level=>$err->level});
905 ## Make sure there are no leftovers.
907 if($overflow_handler) {
908 return $o_check->(+{%values});
911 my $message = "More values than Type Constraints!";
912 $err->add_message({message=>$message,level=>$err->level});
924 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
925 MooseX::Meta::TypeConstraint::Structured->new(
926 name => "MooseX::Types::Structured::Map",
927 parent => find_type_constraint('HashRef'),
928 constraint_generator=> sub {
929 ## Get the constraints and values to check
930 my ($self, $type_constraints) = @_;
931 $type_constraints = $self->type_constraints;
932 my @constraints = defined $type_constraints ? @$type_constraints : ();
934 Carp::confess( "too many args for Map type" ) if @constraints > 2;
936 my ($key_type, $value_type) = @constraints == 2 ? @constraints
937 : @constraints == 1 ? (undef, @constraints)
940 my ($key_check, $value_check, $is_compiled);
942 my ($values, $err) = @_;
943 my %values = defined $values ? %$values: ();
945 unless ($is_compiled) {
946 ($key_check, $value_check)
947 = map { $_ ? $_->$CompiledTC : undef }
948 $key_type, $value_type;
952 ## Perform the checking
954 for my $value (values %$values) {
955 unless ($value_check->($value)) {
957 my $message = $value_type->validate($value,$err);
958 $err->add_message({message=>$message,level=>$err->level});
965 for my $key (keys %$values) {
966 unless ($key_check->($key)) {
968 my $message = $key_type->validate($key,$err);
969 $err->add_message({message=>$message,level=>$err->level});
984 return MooseX::Types::Structured::OverflowHandler->new(
985 type_constraint => $tc,
991 The following modules or resources may be of interest.
993 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
994 L<MooseX::Meta::TypeConstraint::Structured>