1 package MooseX::Types::Structured;
5 use Moose::Util::TypeConstraints;
6 use MooseX::Meta::TypeConstraint::Structured;
7 use MooseX::Meta::TypeConstraint::Structured::Optional;
8 use MooseX::Types::Structured::OverflowHandler;
9 use MooseX::Types -declare => [qw(Dict Map Tuple Optional)];
10 use Sub::Exporter -setup => [ qw(Dict Map Tuple Optional slurpy) ];
11 use Devel::PartialDump;
12 use Scalar::Util qw(blessed);
14 our $VERSION = '0.20';
15 our $AUTHORITY = 'cpan:JJNAPIORK';
19 MooseX::Types::Structured - Structured Type Constraints for Moose
23 The following is example usage for this module.
28 use MooseX::Types::Moose qw(Str Int HashRef);
29 use MooseX::Types::Structured qw(Dict Tuple Optional);
31 ## A name has a first and last part, but middle names are not required
36 middle => Optional[Str],
40 ## description is a string field followed by a HashRef of tagged data.
48 ## Remainder of your class attributes and methods
50 Then you can instantiate this class with something like:
52 my $john = Person->new(
56 last => 'Napiorkowski',
59 'A cool guy who loves Perl and Moose.', {
60 married_to => 'Vanessa Li',
68 my $vanessa = Person->new(
73 description => ['A great student!'],
76 But all of these would cause a constraint error for the 'name' attribute:
78 ## Value for 'name' not a HashRef
79 Person->new( name => 'John' );
81 ## Value for 'name' has incorrect hash key and missing required keys
82 Person->new( name => {
86 ## Also incorrect keys
87 Person->new( name => {
92 ## key 'middle' incorrect type, should be a Str not a ArrayRef
93 Person->new( name => {
99 And these would cause a constraint error for the 'description' attribute:
101 ## Should be an ArrayRef
102 Person->new( description => 'Hello I am a String' );
104 ## First element must be a string not a HashRef.
105 Person->new (description => [{
110 Please see the test cases for more examples.
114 A structured type constraint is a standard container L<Moose> type constraint,
115 such as an ArrayRef or HashRef, which has been enhanced to allow you to
116 explicitly name all the allowed type constraints inside the structure. The
119 TypeConstraint[@TypeParameters or %TypeParameters]
121 Where 'TypeParameters' is an array reference or hash references of
122 L<Moose::Meta::TypeConstraint> objects.
124 This type library enables structured type constraints. It is built on top of the
125 L<MooseX::Types> library system, so you should review the documentation for that
126 if you are not familiar with it.
128 =head2 Comparing Parameterized types to Structured types
130 Parameterized constraints are built into core Moose and you are probably already
131 familar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
132 have similar functionality, so their syntax is likewise similar. For example,
133 you could define a parameterized constraint like:
138 which would constrain a value to something like [1,2,3,...] and so on. On the
139 other hand, a structured type constraint explicitly names all it's allowed
140 'internal' type parameter constraints. For the example:
142 subtype StringFollowedByInt,
145 would constrain it's value to things like ['hello', 111] but ['hello', 'world']
146 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
149 package MyApp::Types;
151 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
152 use MooseX::Types::Moose qw(Str Int);
153 use MooseX::Types::Structured qw(Tuple Optional);
155 subtype StringIntOptionalHashRef,
161 This defines a type constraint that validates values like:
163 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
166 Notice that the last type constraint in the structure is optional. This is
167 enabled via the helper Optional type constraint, which is a variation of the
168 core Moose type constraint 'Maybe'. The main difference is that Optional type
169 constraints are required to validate if they exist, while 'Maybe' permits
170 undefined values. So the following example would not validate:
172 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
174 Please note the subtle difference between undefined and null. If you wish to
175 allow both null and undefined, you should use the core Moose 'Maybe' type
178 package MyApp::Types;
180 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
181 use MooseX::Types::Moose qw(Str Int Maybe);
182 use MooseX::Types::Structured qw(Tuple);
184 subtype StringIntMaybeHashRef,
186 Str, Int, Maybe[HashRef]
189 This would validate the following:
191 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
192 ['World', 200, undef];
195 Structured constraints are not limited to arrays. You can define a structure
196 against a HashRef with the 'Dict' type constaint as in this example:
198 subtype FirstNameLastName,
204 This would constrain a HashRef that validates something like:
206 {firstname => 'Christopher', lastname => 'Parsons'};
208 but all the following would fail validation:
211 {first => 'Christopher', last => 'Parsons'};
214 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
217 ['Christopher', 'Parsons'];
219 These structures can be as simple or elaborate as you wish. You can even
220 combine various structured, parameterized and simple constraints all together:
225 Dict[name=>Str, age=>Int],
231 [1, {name=>'John', age=>25},[10,11,12]];
233 Please notice how the type parameters can be visually arranged to your liking
234 and to improve the clarity of your meaning. You don't need to run then
235 altogether onto a single line. Additionally, since the 'Dict' type constraint
236 defines a hash constraint, the key order is not meaningful. For example:
247 {key1 => 1, key2 => "Hi!", key3 => 2};
248 {key2 => "Hi!", key1 => 100, key3 => 300};
250 As you would expect, since underneath its just a plain old Perl hash at work.
254 You should exercise some care as to whether or not your complex structured
255 constraints would be better off contained by a real object as in the following
258 package MyApp::MyStruct;
261 ## lazy way to make a bunch of attributes
262 has $_ for qw(full_name age_in_years);
264 package MyApp::MyClass;
267 has person => (isa => 'MyApp::MyStruct');
269 my $instance = MyApp::MyClass->new(
270 person=>MyApp::MyStruct->new(
276 This method may take some additional time to setup but will give you more
277 flexibility. However, structured constraints are highly compatible with this
278 method, granting some interesting possibilities for coercion. Try:
280 package MyApp::MyClass;
285 ## It's recommended your type declarations live in a separate class in order
286 ## to promote reusability and clarity. Inlined here for brevity.
288 use MooseX::Types::DateTime qw(DateTime);
289 use MooseX::Types -declare [qw(MyStruct)];
290 use MooseX::Types::Moose qw(Str Int);
291 use MooseX::Types::Structured qw(Dict);
293 ## Use class_type to create an ISA type constraint if your object doesn't
294 ## inherit from Moose::Object.
295 class_type 'MyApp::MyStruct';
297 ## Just a shorter version really.
299 as 'MyApp::MyStruct';
301 ## Add the coercions.
307 MyApp::MyStruct->new(%$_);
314 my $name = $_->{firstname} .' '. $_->{lastname};
315 my $age = DateTime->now - $_->{dob};
317 MyApp::MyStruct->new(
319 age_in_years=>$age->years,
323 has person => (isa=>MyStruct);
325 This would allow you to instantiate with something like:
327 my $obj = MyApp::MyClass->new( person => {
328 full_name=>'John Napiorkowski',
334 my $obj = MyApp::MyClass->new( person => {
336 firstname=>'Napiorkowski',
337 dob=>DateTime->new(year=>1969),
340 If you are not familiar with how coercions work, check out the L<Moose> cookbook
341 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
342 has additional examples and discussion.
344 =head2 Subtyping a Structured type constraint
346 You need to exercise some care when you try to subtype a structured type as in
350 as Dict[name => Str];
352 subtype FriendlyPerson,
355 total_friends => Int,
358 This will actually work BUT you have to take care that the subtype has a
359 structure that does not contradict the structure of it's parent. For now the
360 above works, but I will clarify the syntax for this at a future point, so
361 it's recommended to avoid (should not really be needed so much anyway). For
362 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
363 patches are welcomed for discussion. If you find a good use for this, please
368 Coercions currently work for 'one level' deep. That is you can do:
383 ## Coerce an object of a particular class
384 from BlessedPersonObject, via {
391 ## Coerce from [$name, $age]
398 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
399 from Dict[fullname=>Fullname, dob=>DateTime], via {
400 my $age = $_->dob - DateTime->now;
401 my $firstn = $_->{fullname}->{first};
402 my $lastn = $_->{fullname}->{last}
404 name => $_->{fullname}->{first} .' '. ,
409 And that should just work as expected. However, if there are any 'inner'
410 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
411 won't currently get activated.
413 Please see the test '07-coerce.t' for a more detailed example. Discussion on
414 extending coercions to support this welcome on the Moose development channel or
419 Newer versions of L<MooseX::Types> support recursive type constraints. That is
420 you can include a type constraint as a contained type constraint of itself. For
431 This would declare a Person subtype that contains a name and an optional
432 ArrayRef of Persons who are friends as in:
438 { name => 'Vincent' },
442 { name => 'Stephenie' },
449 Please take care to make sure the recursion node is either Optional, or declare
450 a Union with an non recursive option such as:
471 Otherwise you will define a subtype thatis impossible to validate since it is
472 infinitely recursive. For more information about defining recursive types,
473 please see the documentation in L<MooseX::Types> and the test cases.
475 =head1 TYPE CONSTRAINTS
477 This type library defines the following constraints.
479 =head2 Tuple[@constraints]
481 This defines an ArrayRef based constraint which allows you to validate a specific
482 list of contained constraints. For example:
484 Tuple[Int,Str]; ## Validates [1,'hello']
485 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
487 The Values of @constraints should ideally be L<MooseX::Types> declared type
488 constraints. We do support 'old style' L<Moose> string based constraints to a
489 limited degree but these string type constraints are considered deprecated.
490 There will be limited support for bugs resulting from mixing string and
491 L<MooseX::Types> in your structures. If you encounter such a bug and really
492 need it fixed, we will required a detailed test case at the minimum.
494 =head2 Dict[%constraints]
496 This defines a HashRef based constraint which allowed you to validate a specific
497 hashref. For example:
499 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
501 The keys in %constraints follow the same rules as @constraints in the above
504 =head2 Map[ $key_constraint, $value_constraint ]
506 This defines a HashRef based constraint in which both the keys and values are
507 required to meet certain constraints. For example, to map hostnames to IP
508 addresses, you might say:
510 Map[ HostName, IPAddress ]
512 The type constraint would only be met if every key was a valid HostName and
513 every value was a valid IPAddress.
515 =head2 Optional[$constraint]
517 This is primarily a helper constraint for Dict and Tuple type constraints. What
518 this allows is for you to assert that a given type constraint is allowed to be
519 null (but NOT undefined). If the value is null, then the type constraint passes
520 but if the value is defined it must validate against the type constraint. This
521 makes it easy to make a Dict where one or more of the keys doesn't have to exist
522 or a tuple where some of the values are not required. For example:
524 subtype Name() => as Dict[
527 middle=>Optional[Str],
530 Creates a constraint that validates against a hashref with the keys 'first' and
531 'last' being strings and required while an optional key 'middle' is must be a
532 string if it appears but doesn't have to appear. So in this case both the
535 {first=>'John', middle=>'James', last=>'Napiorkowski'}
536 {first=>'Vanessa', last=>'Li'}
538 If you use the 'Maybe' type constraint instead, your values will also validate
539 against 'undef', which may be incorrect for you.
541 =head1 EXPORTABLE SUBROUTINES
543 This type library makes available for export the following subroutines
547 Structured type constraints by their nature are closed; that is validation will
548 depend on an exact match between your structure definition and the arguments to
549 be checked. Sometimes you might wish for a slightly looser amount of validation.
550 For example, you may wish to validate the first 3 elements of an array reference
551 and allow for an arbitrary number of additional elements. At first thought you
552 might think you could do it this way:
554 # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...]
555 subtype AllowTailingArgs,
563 However what this will actually validate are structures like this:
565 [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef
567 In order to allow structured validation of, "and then some", arguments, you can
568 use the L</slurpy> method against a type constraint. For example:
570 use MooseX::Types::Structured qw(Tuple slurpy);
572 subtype AllowTailingArgs,
577 slurpy ArrayRef[Int],
580 This will now work as expected, validating ArrayRef structures such as:
582 [1,"hello", $obj, 2,3,4,5,6,...]
584 A few caveats apply. First, the slurpy type constraint must be the last one in
585 the list of type constraint parameters. Second, the parent type of the slurpy
586 type constraint must match that of the containing type constraint. That means
587 that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including
588 another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of
589 HashRef, also including other Dict constraints).
591 Please note the the technical way this works 'under the hood' is that the
592 slurpy keyword transforms the target type constraint into a coderef. Please do
593 not try to create your own custom coderefs; always use the slurpy method. The
594 underlying technology may change in the future but the slurpy keyword will be
597 =head1 ERROR MESSAGES
599 Error reporting has been improved to return more useful debugging messages. Now
600 I will stringify the incoming check value with L<Devel::PartialDump> so that you
601 can see the actual structure that is tripping up validation. Also, I report the
602 'internal' validation error, so that if a particular element inside the
603 Structured Type is failing validation, you will see that. There's a limit to
604 how deep this internal reporting goes, but you shouldn't see any of the "failed
605 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
607 This support is continuing to expand, so it's best to use these messages for
608 debugging purposes and not for creating messages that 'escape into the wild'
609 such as error messages sent to the user.
611 Please see the test '12-error.t' for a more lengthy example. Your thoughts and
612 preferable tests or code patches very welcome!
616 Here are some additional example usage for structured types. All examples can
617 be found also in the 't/examples.t' test. Your contributions are also welcomed.
619 =head2 Normalize a HashRef
621 You need a hashref to conform to a canonical structure but are required accept a
622 bunch of different incoming structures. You can normalize using the Dict type
623 constraint and coercions. This example also shows structured types mixed which
624 other MooseX::Types libraries.
626 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
631 use MooseX::Types::Structured qw(Dict Tuple);
632 use MooseX::Types::DateTime qw(DateTime);
633 use MooseX::Types::Moose qw(Int Str Object);
634 use MooseX::Types -declare => [qw(Name Age Person)];
648 name => "$_->{first} $_->{last}",
658 ## DateTime needs to be inside of single quotes here to disambiguate the
659 ## class package from the DataTime type constraint imported via the
660 ## line "use MooseX::Types::DateTime qw(DateTime);"
662 name => "$_->{fullname}{first} $_->{fullname}{last}",
663 age => ($_->{dob} - 'DateTime'->now)->years,
666 has person => (is=>'rw', isa=>Person, coerce=>1);
668 And now you can instantiate with all the following:
672 name=>'John Napiorkowski',
680 last=>'Napiorkowski',
691 dob => 'DateTime'->new(
699 This technique is a way to support various ways to instantiate your class in a
700 clean and declarative way.
704 my $Optional = MooseX::Meta::TypeConstraint::Structured::Optional->new(
705 name => 'MooseX::Types::Structured::Optional',
706 package_defined_in => __PACKAGE__,
707 parent => find_type_constraint('Item'),
708 constraint => sub { 1 },
709 constraint_generator => sub {
710 my ($type_parameter, @args) = @_;
711 my $check = $type_parameter->_compiled_type_constraint();
714 ## Does the arg exist? Something exists if it's a 'real' value
715 ## or if it is set to undef.
716 if(exists($args[0])) {
717 ## If it exists, we need to validate it
720 ## But it's is okay if the value doesn't exists
727 Moose::Util::TypeConstraints::register_type_constraint($Optional);
728 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
730 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
731 MooseX::Meta::TypeConstraint::Structured->new(
732 name => "MooseX::Types::Structured::Tuple" ,
733 parent => find_type_constraint('ArrayRef'),
734 constraint_generator=> sub {
735 ## Get the constraints and values to check
736 my ($type_constraints, $values) = @_;
737 my @type_constraints = defined $type_constraints ?
738 @$type_constraints : ();
740 my $overflow_handler;
741 if($type_constraints[-1] && blessed $type_constraints[-1]
742 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
743 $overflow_handler = pop @type_constraints;
746 my @values = defined $values ? @$values: ();
747 ## Perform the checking
748 while(@type_constraints) {
749 my $type_constraint = shift @type_constraints;
751 my $value = shift @values;
752 unless($type_constraint->check($value)) {
753 $_[2]->{message} = $type_constraint->get_message($value)
758 ## Test if the TC supports null values
759 unless ($type_constraint->is_subtype_of($Optional)) {
760 $_[2]->{message} = $type_constraint->get_message('NULL')
766 ## Make sure there are no leftovers.
768 if($overflow_handler) {
769 return $overflow_handler->check([@values], $_[2]);
771 $_[2]->{message} = "More values than Type Constraints!"
775 } elsif(@type_constraints) {
777 "Not enough values for all defined type constraints. Remaining: ". join(', ',@type_constraints)
787 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
788 MooseX::Meta::TypeConstraint::Structured->new(
789 name => "MooseX::Types::Structured::Dict",
790 parent => find_type_constraint('HashRef'),
791 constraint_generator=> sub {
792 ## Get the constraints and values to check
793 my ($type_constraints, $values) = @_;
794 my @type_constraints = defined $type_constraints ?
795 @$type_constraints : ();
797 my $overflow_handler;
798 if($type_constraints[-1] && blessed $type_constraints[-1]
799 && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
800 $overflow_handler = pop @type_constraints;
802 my (%type_constraints) = @type_constraints;
803 my %values = defined $values ? %$values: ();
804 ## Perform the checking
805 while(%type_constraints) {
806 my($key, $type_constraint) = each %type_constraints;
807 delete $type_constraints{$key};
808 if(exists $values{$key}) {
809 my $value = $values{$key};
810 delete $values{$key};
811 unless($type_constraint->check($value)) {
812 $_[2]->{message} = $type_constraint->get_message($value)
817 ## Test to see if the TC supports null values
818 unless ($type_constraint->is_subtype_of($Optional)) {
819 $_[2]->{message} = $type_constraint->get_message('NULL')
825 ## Make sure there are no leftovers.
827 if($overflow_handler) {
828 return $overflow_handler->check(+{%values});
830 $_[2]->{message} = "More values than Type Constraints!"
834 } elsif(%type_constraints) {
836 "Not enough values for all defined type constraints. Remaining: ". join(', ',values %values)
846 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
847 MooseX::Meta::TypeConstraint::Structured->new(
848 name => "MooseX::Types::Structured::Map",
849 parent => find_type_constraint('HashRef'),
850 constraint_generator=> sub {
851 ## Get the constraints and values to check
852 my ($type_constraints, $values) = @_;
853 my @constraints = defined $type_constraints ? @$type_constraints : ();
855 Carp::confess( "too many args for Map type" ) if @constraints > 2;
857 my ($key_type, $value_type) = @constraints == 2 ? @constraints
858 : @constraints == 1 ? (undef, @constraints)
861 my %values = defined $values ? %$values: ();
862 ## Perform the checking
864 for my $value (values %$values) {
865 unless ($value_type->check($value)) {
866 $_[2]->{message} = $value_type->get_message($value) if ref $_[2];
873 for my $key (keys %$values) {
874 unless ($key_type->check($key)) {
875 $_[2]->{message} = $key_type->get_message($key) if ref $_[2];
888 return MooseX::Types::Structured::OverflowHandler->new(
889 type_constraint => $tc,
895 The following modules or resources may be of interest.
897 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
898 L<MooseX::Meta::TypeConstraint::Structured>
902 Here's a list of stuff I would be happy to get volunteers helping with:
904 * All POD examples need test cases in t/documentation/*.t
905 * Want to break out the examples section to a separate cookbook style POD.
906 * Want more examples and best practice / usage guidance for authors
907 * Need to clarify deep coercions,
911 John Napiorkowski <jjnapiork@cpan.org>
915 The following people have contributed to this module and agree with the listed
916 Copyright & license information included below:
918 Florian Ragwitz, <rafl@debian.org>
919 Yuval Kogman, <nothingmuch@woobling.org>
920 Tomas Doran, <bobtfish@bobtfish.net>
922 =head1 COPYRIGHT & LICENSE
924 Copyright 2008-2009, John Napiorkowski <jjnapiork@cpan.org>
926 This program is free software; you can redistribute it and/or modify it under
927 the same terms as Perl itself.