1 package MooseX::Types::Structured;
5 use Moose::Util::TypeConstraints;
6 use MooseX::Meta::TypeConstraint::Structured;
7 use MooseX::Types -declare => [qw(Dict Tuple Optional)];
8 use Sub::Exporter -setup => { exports => [ qw(Dict Tuple Optional slurpy) ] };
9 use Devel::PartialDump;
11 our $VERSION = '0.122';
12 our $AUTHORITY = 'cpan:JJNAPIORK';
16 MooseX::Types::Structured - Structured Type Constraints for Moose
20 The following is example usage for this module.
25 use MooseX::Types::Moose qw(Str Int HashRef);
26 use MooseX::Types::Structured qw(Dict Tuple Optional);
28 ## A name has a first and last part, but middle names are not required
33 middle => Optional[Str],
37 ## description is a string field followed by a HashRef of tagged data.
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 familar 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 subtype StringIntOptionalHashRef,
150 This defines a type constraint that validates values like:
152 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
155 Notice that the last type constraint in the structure is optional. This is
156 enabled via the helper Optional type constraint, which is a variation of the
157 core Moose type constraint 'Maybe'. The main difference is that Optional type
158 constraints are required to validate if they exist, while 'Maybe' permits
159 undefined values. So the following example would not validate:
161 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
163 Please note the subtle difference between undefined and null. If you wish to
164 allow both null and undefined, you should use the core Moose 'Maybe' type
167 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
168 use MooseX::Types::Moose qw(Maybe);
169 use MooseX::Types::Structured qw(Tuple);
171 subtype StringIntMaybeHashRef,
173 Str, Int, Maybe[HashRef]
176 This would validate the following:
178 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
179 ['World', 200, undef];
182 Structured constraints are not limited to arrays. You can define a structure
183 against a HashRef with 'Dict' as in this example:
185 subtype FirstNameLastName,
191 This would constrain a HashRef to something like:
193 {firstname => 'Christopher', lastname= > 'Parsons'};
195 but all the following would fail validation:
198 {first => 'Christopher', last => 'Parsons'};
201 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
204 ['Christopher', 'Christopher'];
206 These structures can be as simple or elaborate as you wish. You can even
207 combine various structured, parameterized and simple constraints all together:
212 Dict[name=>Str, age=>Int],
216 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
217 the type parameters can be visually arranged to your liking and to improve the
218 clarity of your meaning. You don't need to run then altogether onto a single
223 You should exercise some care as to whether or not your complex structured
224 constraints would be better off contained by a real object as in the following
227 package MyApp::MyStruct;
230 ## lazy way to make a bunch of attributes
231 has $_ for qw(full_name age_in_years);
233 package MyApp::MyClass;
236 has person => (isa => 'MyApp::MyStruct');
238 my $instance = MyApp::MyClass->new(
239 person=>MyApp::MyStruct->new(
245 This method may take some additional time to setup but will give you more
246 flexibility. However, structured constraints are highly compatible with this
247 method, granting some interesting possibilities for coercion. Try:
249 package MyApp::MyClass;
254 ## It's recommended your type declarations live in a separate class in order
255 ## to promote reusability and clarity. Inlined here for brevity.
257 use MooseX::Types::DateTime qw(DateTime);
258 use MooseX::Types -declare [qw(MyStruct)];
259 use MooseX::Types::Moose qw(Str Int);
260 use MooseX::Types::Structured qw(Dict);
262 ## Use class_type to create an ISA type constraint if your object doesn't
263 ## inherit from Moose::Object.
264 class_type 'MyApp::MyStruct';
266 ## Just a shorter version really.
268 as 'MyApp::MyStruct';
270 ## Add the coercions.
276 MyApp::MyStruct->new(%$_);
283 my $name = $_->{firstname} .' '. $_->{lastname};
284 my $age = DateTime->now - $_->{dob};
286 MyApp::MyStruct->new(
288 age_in_years=>$age->years,
292 has person => (isa=>MyStruct);
294 This would allow you to instantiate with something like:
296 my $obj = MyApp::MyClass->new( person => {
297 full_name=>'John Napiorkowski',
303 my $obj = MyApp::MyClass->new( person => {
305 firstname=>'Napiorkowski',
306 dob=>DateTime->new(year=>1969),
309 If you are not familiar with how coercions work, check out the L<Moose> cookbook
310 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
311 has additional examples and discussion.
313 =head2 Subtyping a Structured type constraint
315 You need to exercise some care when you try to subtype a structured type as in
319 as Dict[name => Str];
321 subtype FriendlyPerson,
324 total_friends => Int,
327 This will actually work BUT you have to take care that the subtype has a
328 structure that does not contradict the structure of it's parent. For now the
329 above works, but I will clarify the syntax for this at a future point, so
330 it's recommended to avoid (should not really be needed so much anyway). For
331 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
332 patches are welcomed for discussion. If you find a good use for this, please
337 Coercions currently work for 'one level' deep. That is you can do:
352 ## Coerce an object of a particular class
353 from BlessedPersonObject, via {
360 ## Coerce from [$name, $age]
367 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
368 from Dict[fullname=>Fullname, dob=>DateTime], via {
369 my $age = $_->dob - DateTime->now;
370 my $firstn = $_->{fullname}->{first};
371 my $lastn = $_->{fullname}->{last}
373 name => $_->{fullname}->{first} .' '. ,
378 And that should just work as expected. However, if there are any 'inner'
379 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
380 won't currently get activated.
382 Please see the test '07-coerce.t' for a more detailed example. Discussion on
383 extending coercions to support this welcome on the Moose development channel or
388 Newer versions of L<MooseX::Types> support recursive type constraints. That is
389 you can include a type constraint as a contained type constraint of itself. For
400 This would declare a Person subtype that contains a name and an optional
401 ArrayRef of Persons who are friends as in:
407 { name => 'Vincent' },
411 { name => 'Stephenie' },
418 Please take care to make sure the recursion node is either Optional, or declare
419 a Union with an non recursive option such as:
440 Otherwise you will define a subtype thatis impossible to validate since it is
441 infinitely recursive. For more information about defining recursive types,
442 please see the documentation in L<MooseX::Types> and the test cases.
444 =head1 TYPE CONSTRAINTS
446 This type library defines the following constraints.
448 =head2 Tuple[@constraints]
450 This defines an ArrayRef based constraint which allows you to validate a specific
451 list of contained constraints. For example:
453 Tuple[Int,Str]; ## Validates [1,'hello']
454 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
456 =head2 Dict[%constraints]
458 This defines a HashRef based constraint which allowed you to validate a specific
459 hashref. For example:
461 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
463 =head2 Optional[$constraint]
465 This is primarily a helper constraint for Dict and Tuple type constraints. What
466 this allows if for you to assert that a given type constraint is allowed to be
467 null (but NOT undefined). If the value is null, then the type constraint passes
468 but if the value is defined it must validate against the type constraint. This
469 makes it easy to make a Dict where one or more of the keys doesn't have to exist
470 or a tuple where some of the values are not required. For example:
472 subtype Name() => as Dict[
475 middle=>Optional[Str],
478 Creates a constraint that validates against a hashref with the keys 'first' and
479 'last' being strings and required while an optional key 'middle' is must be a
480 string if it appears but doesn't have to appear. So in this case both the
483 {first=>'John', middle=>'James', last=>'Napiorkowski'}
484 {first=>'Vanessa', last=>'Li'}
486 =head1 EXPORTABLE SUBROUTINES
488 This type library makes available for export the following subroutines
492 Structured type constraints by their nature are closed; that is validation will
493 depend on an exact match between your structure definition and the arguments to
494 be checked. Sometimes you might wish for a slightly looser amount of validation.
495 For example, you may wish to validate the first 3 elements of an array reference
496 and allow for an arbitrary number of additional elements. At first thought you
497 might think you could do it this way:
499 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
500 subtype AllowTailingArgs,
508 However what this will actually validate are structures like this:
510 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
512 In order to allow structured validation of, "and then some", arguments, you can
513 use the </slurpy> method against a type constraint. For example:
515 use MooseX::Types::Structured qw(Tuple slurpy);
517 subtype AllowTailingArgs,
522 slurpy ArrayRef[Int],
525 This will now work as expected, validating ArrayRef structures such as:
527 [1,"hello", $obj, 2,3,4,5,6,...]
529 A few caveats apply. First, the slurpy type constraint must be the last one in
530 the list of type constraint parameters. Second, the parent type of the slurpy
531 type constraint must match that of the containing type constraint. That means
532 that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
533 another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
534 HashRef, also including other Dict constraints).
536 Please note the the technical way this works 'under the hood' is that the
537 slurpy keywork transforms the target type constraint into a coderef. Please do
538 not try to create your own custom coderefs; always use the slurpy method. The
539 underlying technology may change in the future but the slurpy keyword will be
542 =head1 ERROR MESSAGES
544 Error reporting has been improved to return more useful debugging messages. Now
545 I will stringify the incoming check value with L<Devel::PartialDump> so that you
546 can see the actual structure that is tripping up validation. Also, I report the
547 'internal' validation error, so that if a particular element inside the
548 Structured Type is failing validation, you will see that. There's a limit to
549 how deep this internal reporting goes, but you shouldn't see any of the "failed
550 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
552 This support is continuing to expand, so it's best to use these messages for
553 debugging purposes and not for creating messages that 'escape into the wild'
554 such as error messages sent to the user.
556 Please see the test '12-error.t' for a more lengthy example. Your thoughts and
557 preferable tests or code patches very welcome!
561 Here are some additional example usage for structured types. All examples can
562 be found also in the 't/examples.t' test. Your contributions are also welcomed.
564 =head2 Normalize a HashRef
566 You need a hashref to conform to a canonical structure but are required accept a
567 bunch of different incoming structures. You can normalize using the Dict type
568 constraint and coercions. This example also shows structured types mixed which
569 other MooseX::Types libraries.
571 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
576 use MooseX::Types::Structured qw(Dict Tuple);
577 use MooseX::Types::DateTime qw(DateTime);
578 use MooseX::Types::Moose qw(Int Str Object);
579 use MooseX::Types -declare => [qw(Name Age Person)];
593 name => "$_->{first} $_->{last}",
603 ## DateTime needs to be inside of single quotes here to disambiguate the
604 ## class package from the DataTime type constraint imported via the
605 ## line "use MooseX::Types::DateTime qw(DateTime);"
607 name => "$_->{fullname}{first} $_->{fullname}{last}",
608 age => ($_->{dob} - 'DateTime'->now)->years,
611 has person => (is=>'rw', isa=>Person, coerce=>1);
613 And now you can instantiate with all the following:
617 name=>'John Napiorkowski',
625 last=>'Napiorkowski',
636 dob => 'DateTime'->new(
644 This technique is a way to support various ways to instantiate your class in a
645 clean and declarative way.
649 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
650 MooseX::Meta::TypeConstraint::Structured->new(
651 name => "MooseX::Types::Structured::Tuple" ,
652 parent => find_type_constraint('ArrayRef'),
653 constraint_generator=> sub {
654 ## Get the constraints and values to check
655 my ($type_constraints, $values) = @_;
656 my @type_constraints = defined $type_constraints ?
657 @$type_constraints : ();
659 my $overflow_handler;
660 if(ref $type_constraints[-1] eq 'CODE') {
661 $overflow_handler = pop @type_constraints;
664 my @values = defined $values ? @$values: ();
665 ## Perform the checking
666 while(@type_constraints) {
667 my $type_constraint = shift @type_constraints;
669 my $value = shift @values;
670 unless($type_constraint->check($value)) {
671 $_[2]->{message} = $type_constraint->get_message($value)
676 ## Test if the TC supports null values
677 unless($type_constraint->check()) {
678 $_[2]->{message} = $type_constraint->get_message('NULL')
684 ## Make sure there are no leftovers.
686 if($overflow_handler) {
687 return $overflow_handler->([@values], $_[2]);
689 $_[2]->{message} = "More values than Type Constraints!"
693 } elsif(@type_constraints) {
695 "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints)
705 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
706 MooseX::Meta::TypeConstraint::Structured->new(
707 name => "MooseX::Types::Structured::Dict",
708 parent => find_type_constraint('HashRef'),
709 constraint_generator=> sub {
710 ## Get the constraints and values to check
711 my ($type_constraints, $values) = @_;
712 my @type_constraints = defined $type_constraints ?
713 @$type_constraints : ();
715 my $overflow_handler;
716 if(ref $type_constraints[-1] eq 'CODE') {
717 $overflow_handler = pop @type_constraints;
719 my (%type_constraints) = @type_constraints;
720 my %values = defined $values ? %$values: ();
721 ## Perform the checking
722 while(%type_constraints) {
723 my($key, $type_constraint) = each %type_constraints;
724 delete $type_constraints{$key};
725 if(exists $values{$key}) {
726 my $value = $values{$key};
727 delete $values{$key};
728 unless($type_constraint->check($value)) {
729 $_[2]->{message} = $type_constraint->get_message($value)
734 ## Test to see if the TC supports null values
735 unless($type_constraint->check()) {
736 $_[2]->{message} = $type_constraint->get_message('NULL')
742 ## Make sure there are no leftovers.
744 if($overflow_handler) {
745 return $overflow_handler->(+{%values});
747 $_[2]->{message} = "More values than Type Constraints!"
751 } elsif(%type_constraints) {
753 "Not enough values for all defined type constraints. Remaining: ". join(', ',values %values)
764 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
765 name => 'MooseX::Types::Structured::Optional',
766 package_defined_in => __PACKAGE__,
767 parent => find_type_constraint('Item'),
768 constraint => sub { 1 },
769 constraint_generator => sub {
770 my ($type_parameter, @args) = @_;
771 my $check = $type_parameter->_compiled_type_constraint();
774 ## Does the arg exist? Something exists if it's a 'real' value
775 ## or if it is set to undef.
776 if(exists($args[0])) {
777 ## If it exists, we need to validate it
780 ## But it's is okay if the value doesn't exists
787 Moose::Util::TypeConstraints::register_type_constraint($Optional);
788 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
800 The following modules or resources may be of interest.
802 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
803 L<MooseX::Meta::TypeConstraint::Structured>
807 Here's a list of stuff I would be happy to get volunteers helping with:
809 All POD examples need test cases in t/documentation/*.t
810 Want to break out the examples section to a separate cookbook style POD.
811 Want more examples and best practice / usage guidance for authors
812 Need to clarify deep coercions,
813 Need to clarify subtypes of subtypes.
817 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
819 =head1 COPYRIGHT & LICENSE
821 This program is free software; you can redistribute it and/or modify
822 it under the same terms as Perl itself.