1 package MooseX::Types::Structured;
4 use Moose::Util::TypeConstraints;
5 use MooseX::Meta::TypeConstraint::Structured;
6 use MooseX::Types -declare => [qw(Dict Tuple Optional)];
9 our $AUTHORITY = 'cpan:JJNAPIORK';
13 MooseX::Types::Structured - Structured Type Constraints for Moose
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 Then you can instantiate this class with something like:
44 my $john = Person->new(
48 last => 'Napiorkowski',
51 'A cool guy who loves Perl and Moose.', {
52 married_to => 'Vanessa Li',
60 my $vanessa = Person->new(
65 description => ['A great student!'],
68 But all of these would cause a constraint error for the 'name' attribute:
70 ## Value for 'name' not a HashRef
71 Person->new( name => 'John' );
73 ## Value for 'name' has incorrect hash key and missing required keys
74 Person->new( name => {
78 ## Also incorrect keys
79 Person->new( name => {
84 ## key 'middle' incorrect type, should be a Str not a ArrayRef
85 Person->new( name => {
91 And these would cause a constraint error for the 'description' attribute:
93 ## Should be an ArrayRef
94 Person->new( description => 'Hello I am a String' );
96 ## First element must be a string not a HashRef.
97 Person->new (description => [{
102 Please see the test cases for more examples.
106 A structured type constraint is a standard container L<Moose> type constraint,
107 such as an ArrayRef or HashRef, which has been enhanced to allow you to
108 explicitly name all the allowed type constraints inside the structure. The
111 TypeConstraint[@TypeParameters or %TypeParameters]
113 Where 'TypeParameters' is an array reference or hash references of
114 L<Moose::Meta::TypeConstraint> objects.
116 This type library enables structured type constraints. It is built on top of the
117 L<MooseX::Types> library system, so you should review the documentation for that
118 if you are not familiar with it.
120 =head2 Comparing Parameterized types to Structured types
122 Parameterized constraints are built into core Moose and you are probably already
123 familar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
124 have similar functionality, so their syntax is likewise similar. For example,
125 you could define a parameterized constraint like:
130 which would constrain a value to something like [1,2,3,...] and so on. On the
131 other hand, a structured type constraint explicitly names all it's allowed
132 'internal' type parameter constraints. For the example:
134 subtype StringFollowedByInt,
137 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
138 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
141 subtype StringIntOptionalHashRef,
147 This defines a type constraint that validates values like:
149 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
152 Notice that the last type constraint in the structure is optional. This is
153 enabled via the helper Optional type constraint, which is a variation of the
154 core Moose type constraint 'Maybe'. The main difference is that Optional type
155 constraints are required to validate if they exist, while 'Maybe' permits
156 undefined values. So the following example would not validate:
158 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
160 Please note the subtle difference between undefined and null. If you wish to
161 allow both null and undefined, you should use the core Moose 'Maybe' type
164 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
165 use MooseX::Types::Moose qw(Maybe);
166 use MooseX::Types::Structured qw(Tuple);
168 subtype StringIntMaybeHashRef,
170 Str, Int, Maybe[HashRef]
173 This would validate the following:
175 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
176 ['World', 200, undef];
179 Structured constraints are not limited to arrays. You can define a structure
180 against a HashRef with 'Dict' as in this example:
182 subtype FirstNameLastName,
188 This would constrain a HashRef to something like:
190 {firstname => 'Christopher', lastname= > 'Parsons'};
192 but all the following would fail validation:
195 {first => 'Christopher', last => 'Parsons'};
198 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
201 ['Christopher', 'Christopher'];
203 These structures can be as simple or elaborate as you wish. You can even
204 combine various structured, parameterized and simple constraints all together:
209 Dict[name=>Str, age=>Int],
213 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
214 the type parameters can be visually arranged to your liking and to improve the
215 clarity of your meaning. You don't need to run then altogether onto a single
220 You should exercise some care as to whether or not your complex structured
221 constraints would be better off contained by a real object as in the following
224 package MyApp::MyStruct;
227 ## lazy way to make a bunch of attributes
228 has $_ for qw(full_name age_in_years);
230 package MyApp::MyClass;
233 has person => (isa => 'MyApp::MyStruct');
235 my $instance = MyApp::MyClass->new(
236 person=>MyApp::MyStruct->new(
242 This method may take some additional time to setup but will give you more
243 flexibility. However, structured constraints are highly compatible with this
244 method, granting some interesting possibilities for coercion. Try:
246 package MyApp::MyClass;
251 ## It's recommended your type declarations live in a separate class in order
252 ## to promote reusability and clarity. Inlined here for brevity.
254 use MooseX::Types::DateTime qw(DateTime);
255 use MooseX::Types -declare [qw(MyStruct)];
256 use MooseX::Types::Moose qw(Str Int);
257 use MooseX::Types::Structured qw(Dict);
259 ## Use class_type to create an ISA type constraint if your object doesn't
260 ## inherit from Moose::Object.
261 class_type 'MyApp::MyStruct';
263 ## Just a shorter version really.
265 as 'MyApp::MyStruct';
267 ## Add the coercions.
273 MyApp::MyStruct->new(%$_);
280 my $name = $_->{firstname} .' '. $_->{lastname};
281 my $age = DateTime->now - $_->{dob};
283 MyApp::MyStruct->new(
285 age_in_years=>$age->years,
289 has person => (isa=>MyStruct);
291 This would allow you to instantiate with something like:
293 my $obj = MyApp::MyClass->new( person => {
294 full_name=>'John Napiorkowski',
300 my $obj = MyApp::MyClass->new( person => {
302 firstname=>'Napiorkowski',
303 dob=>DateTime->new(year=>1969),
306 If you are not familiar with how coercions work, check out the L<Moose> cookbook
307 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
308 has additional examples and discussion.
310 =head2 Subtyping a Structured type constraint
312 You need to exercise some care when you try to subtype a structured type as in
316 as Dict[name => Str];
318 subtype FriendlyPerson,
321 total_friends => Int,
324 This will actually work BUT you have to take care that the subtype has a
325 structure that does not contradict the structure of it's parent. For now the
326 above works, but I will clarify the syntax for this at a future point, so
327 it's recommended to avoid (should not really be needed so much anyway). For
328 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
329 patches are welcomed for discussion. If you find a good use for this, please
334 Coercions currently work for 'one level' deep. That is you can do:
349 ## Coerce an object of a particular class
350 from BlessedPersonObject, via {
357 ## Coerce from [$name, $age]
364 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
365 from Dict[fullname=>Fullname, dob=>DateTime], via {
366 my $age = $_->dob - DateTime->now;
367 my $firstn = $_->{fullname}->{first};
368 my $lastn = $_->{fullname}->{last}
370 name => $_->{fullname}->{first} .' '. ,
375 And that should just work as expected. However, if there are any 'inner'
376 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
377 won't currently get activated.
379 Please see the test '07-coerce.t' for a more detailed example. Discussion on
380 extending coercions to support this welcome on the Moose development channel or
385 Newer versions of L<MooseX::Types> support recursive type constraints. That is
386 you can include a type constraint as a contained type constraint of itself. For
397 This would declare a Person subtype that contains a name and an optional
398 ArrayRef of Persons who are friends as in:
404 { name => 'Vincent' },
408 { name => 'Stephenie' },
415 Please take care to make sure the recursion node is either Optional, or declare
416 a Union with an non recursive option such as:
437 Otherwise you will define a subtype thatis impossible to validate since it is
438 infinitely recursive. For more information about defining recursive types,
439 please see the documentation in L<MooseX::Types> and the test cases.
441 =head1 TYPE CONSTRAINTS
443 This type library defines the following constraints.
445 =head2 Tuple[@constraints]
447 This defines an ArrayRef based constraint which allows you to validate a specific
448 list of contained constraints. For example:
450 Tuple[Int,Str]; ## Validates [1,'hello']
451 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
453 =head2 Dict[%constraints]
455 This defines a HashRef based constraint which allowed you to validate a specific
456 hashref. For example:
458 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
460 =head2 Optional[$constraint]
462 This is primarily a helper constraint for Dict and Tuple type constraints. What
463 this allows if for you to assert that a given type constraint is allowed to be
464 null (but NOT undefined). If the value is null, then the type constraint passes
465 but if the value is defined it must validate against the type constraint. This
466 makes it easy to make a Dict where one or more of the keys doesn't have to exist
467 or a tuple where some of the values are not required. For example:
469 subtype Name() => as Dict[
472 middle=>Optional[Str],
475 Creates a constraint that validates against a hashref with the keys 'first' and
476 'last' being strings and required while an optional key 'middle' is must be a
477 string if it appears but doesn't have to appear. So in this case both the
480 {first=>'John', middle=>'James', last=>'Napiorkowski'}
481 {first=>'Vanessa', last=>'Li'}
485 Here are some additional example usage for structured types. All examples can
486 be found also in the 't/examples.t' test. Your contributions are also welcomed.
488 =head2 Normalize a HashRef
490 You need a hashref to conform to a canonical structure but are required accept a
491 bunch of different incoming structures. You can normalize using the Dict type
492 constraint and coercions. This example also shows structured types mixed which
493 other MooseX::Types libraries.
495 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
500 use MooseX::Types::Structured qw(Dict Tuple);
501 use MooseX::Types::DateTime qw(DateTime);
502 use MooseX::Types::Moose qw(Int Str Object);
503 use MooseX::Types -declare => [qw(Name Age Person)];
517 name => "$_->{first} $_->{last}",
527 ## DateTime needs to be inside of single quotes here to disambiguate the
528 ## class package from the DataTime type constraint imported via the
529 ## line "use MooseX::Types::DateTime qw(DateTime);"
531 name => "$_->{fullname}{first} $_->{fullname}{last}",
532 age => ($_->{dob} - 'DateTime'->now)->years,
535 has person => (is=>'rw', isa=>Person, coerce=>1);
537 And now you can instantiate with all the following:
540 name=>'John Napiorkowski',
546 last=>'Napiorkowski',
555 dob => 'DateTime'->new(
562 This technique is a way to support various ways to instantiate your class in a
563 clean and declarative way.
567 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
568 MooseX::Meta::TypeConstraint::Structured->new(
569 name => "MooseX::Types::Structured::Tuple" ,
570 parent => find_type_constraint('ArrayRef'),
571 constraint_generator=> sub {
572 ## Get the constraints and values to check
573 my ($type_constraints, $values) = @_;
574 my @type_constraints = defined $type_constraints ?
575 @$type_constraints : ();
577 my $overflow_handler;
578 if(ref $type_constraints[-1] eq 'CODE') {
579 $overflow_handler = pop @type_constraints;
582 my @values = defined $values ? @$values: ();
583 ## Perform the checking
584 while(@type_constraints) {
585 my $type_constraint = shift @type_constraints;
587 my $value = shift @values;
588 unless($type_constraint->check($value)) {
592 ## Test if the TC supports null values
593 unless($type_constraint->check()) {
598 ## Make sure there are no leftovers.
600 if($overflow_handler) {
601 return $overflow_handler->(@values);
605 } elsif(@type_constraints) {
606 warn "I failed due to left over TC";
615 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
616 MooseX::Meta::TypeConstraint::Structured->new(
617 name => "MooseX::Types::Structured::Dict",
618 parent => find_type_constraint('HashRef'),
619 constraint_generator=> sub {
620 ## Get the constraints and values to check
621 my ($type_constraints, $values) = @_;
622 my @type_constraints = defined $type_constraints ?
623 @$type_constraints : ();
625 my $overflow_handler;
626 if(ref $type_constraints[-1] eq 'CODE') {
627 $overflow_handler = pop @type_constraints;
629 my (%type_constraints) = @type_constraints;
630 my %values = defined $values ? %$values: ();
631 ## Perform the checking
632 while(%type_constraints) {
633 my($key, $type_constraint) = each %type_constraints;
634 delete $type_constraints{$key};
635 if(exists $values{$key}) {
636 my $value = $values{$key};
637 delete $values{$key};
638 unless($type_constraint->check($value)) {
642 ## Test to see if the TC supports null values
643 unless($type_constraint->check()) {
648 ## Make sure there are no leftovers.
650 if($overflow_handler) {
651 return $overflow_handler->(%values);
655 } elsif(%type_constraints) {
665 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
666 name => 'MooseX::Types::Structured::Optional',
667 package_defined_in => __PACKAGE__,
668 parent => find_type_constraint('Item'),
669 constraint => sub { 1 },
670 constraint_generator => sub {
671 my ($type_parameter, @args) = @_;
672 my $check = $type_parameter->_compiled_type_constraint();
675 ## Does the arg exist? Something exists if it's a 'real' value
676 ## or if it is set to undef.
677 if(exists($args[0])) {
678 ## If it exists, we need to validate it
681 ## But it's is okay if the value doesn't exists
688 Moose::Util::TypeConstraints::register_type_constraint($Optional);
689 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
695 The following modules or resources may be of interest.
697 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
698 L<MooseX::Meta::TypeConstraint::Structured>
702 Here's a list of stuff I would be happy to get volunteers helping with:
704 All POD examples need test cases in t/documentation/*.t
705 Want to break out the examples section to a separate cookbook style POD.
706 Want more examples and best practice / usage guidance for authors
707 Need to clarify deep coercions,
708 Need to clarify subtypes of subtypes.
712 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
714 =head1 COPYRIGHT & LICENSE
716 This program is free software; you can redistribute it and/or modify
717 it under the same terms as Perl itself.