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.
19 package MyApp::MyClass;
22 use MooseX::Types::Moose qw(Str Int);
23 use MooseX::Types::Structured qw(Dict Optional);
25 ## A name has a first and last part, but middle names are not required
30 middle=>Optional[Str],
34 Then you can instantiate this class with something like:
36 my $john = MyApp::MyClass->new(
46 my $vanessa = MyApp::MyClass->new(
53 But all of these would cause a constraint error for the 'name' attribute:
55 MyApp::MyClass->new( name=>'John' );
56 MyApp::MyClass->new( name=>{first_name=>'John'} );
57 MyApp::MyClass->new( name=>{first_name=>'John', age=>39} );
58 MyApp::MyClass->new( name=>{first=>'Vanessa', middle=>[1,2], last=>'Li'} );
60 Please see the test cases for more examples.
64 A structured type constraint is a standard container L<Moose> type constraint,
65 such as an arrayref or hashref, which has been enhanced to allow you to
66 explicitly name all the allow type constraints inside the structure. The
69 TypeConstraint[@TypeParameters|%TypeParameters]
71 Where 'TypeParameters' is an array or hash of L<Moose::Meta::TypeConstraint>.
73 This type library enables structured type constraints. It is built on top of the
74 L<MooseX::Types> library system, so you should review the documentation for that
75 if you are not familiar with it.
77 =head2 Comparing Parameterized types to Structured types
79 Parameterized constraints are built into core Moose and you are probably already
80 familuar with the type constraints 'HashRef' and 'ArrayRef'. Structured types
81 have similar functionality, so their syntax is likewise similar. For example,
82 you could define a parameterized constraint like:
87 which would constraint a value to something like [1,2,3,...] and so on. On the
88 other hand, a structured type constraint explicitly names all it's allowed
89 'internal' type parameter constraints. For the example:
91 subtype StringFollowedByInt,
94 would constrain it's value to something like ['hello', 111] but ['hello', 'world']
95 would fail, as well as ['hello', 111, 'world'] and so on. Here's another
98 subtype StringIntOptionalHashRef,
104 This defines a type constraint that validates values like:
106 ['Hello', 100, {key1=>'value1', key2=>'value2'}];
109 Notice that the last type constraint in the structure is optional. This is
110 enabled via the helper Optional type constraint, which is a variation of the
111 core Moose type constraint Maybe. The main difference is that Optional type
112 constraints are required to validate if they exist, while Maybe permits undefined
113 values. So the following example would not validate:
115 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
117 Please note the subtle difference between undefined and null. If you wish to
118 allow both null and undefined, you should use the core Moose Maybe type constraint
121 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
122 use MooseX::Types::Moose qw(Maybe);
123 use MooseX::Types::Structured qw(Tuple);
125 subtype StringIntOptionalHashRef,
127 Str, Int, Maybe[HashRef]
130 This would validate the following:
132 ['Hello', 100, {key1=>'value1', key2=>'value2'}];
133 ['World', 200, undef];
136 Structured Constraints are not limited to arrays. You can define a structure
137 against a hashref with 'Dict' as in this example:
139 subtype FirstNameLastName,
140 as Dict[firstname=>Str, lastname=>Str];
142 This would constrain a hashref to something like:
144 {firstname=>'Vanessa', lastname=>'Li'};
146 but all the following would fail validation:
148 {first=>'Vanessa', last=>'Li'};
149 {firstname=>'Vanessa', lastname=>'Li', middlename=>'NA'};
152 These structures can be as simple or elaborate as you wish. You can even
153 combine various structured, parameterized and simple constraints all together:
158 Dict[name=>Str, age=>Int],
162 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
163 the type parameters can be visually arranged to your liking and to improve the
164 clarity of your meaning. You don't need to run then altogether onto a single
169 You should exercise some care as to whether or not your complex structured
170 constraints would be better off contained by a real object as in the following
173 package MyApp::MyStruct;
176 has $_ for qw(full_name age_in_years);
178 package MyApp::MyClass;
181 has person => (isa=>'MyApp::MyStruct');
183 my $instance = MyApp::MyClass->new(
184 person=>MyApp::MyStruct->new(full_name=>'John', age_in_years=>39),
187 This method may take some additional time to setup but will give you more
188 flexibility. However, structured constraints are highly compatible with this
189 method, granting some interesting possibilities for coercion. Try:
192 use MooseX::Types::DateTime qw(DateTime);
193 use MooseX::Types -declare [qw(MyStruct)];
194 use MooseX::Types::Moose qw(Str Int);
195 use MooseX::Types::Structured qw(Dict);
197 ## Use class_type to create an ISA type constraint if your object doesn't
198 ## inherit from Moose::Object.
199 class_type 'MyApp::MyStruct';
201 ## Just a shorter version really.
203 as 'MyApp::MyStruct';
205 ## Add the coercions.
211 MyApp::MyStruct->new(%$_);
218 my $name = $_->{firstname} .' '. $_->{lastname};
219 my $age = DateTime->now - $_->{dob};
220 MyApp::MyStruct->new( full_name=>$name, age_in_years=>$age->years );
223 If you are not familiar with how coercions work, check out the L<Moose> cookbook
224 entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions>
225 has additional examples and discussion.
227 =head2 Subtyping a Structured type constraint
229 You need to exercise some care when you try to subtype a structured type
233 as Dict[name=>Str, age=>Int];
235 subtype FriendlyPerson,
236 as Person[name=>Str, age=>Int, totalFriends=>Int];
238 This will actually work BUT you have to take care that the subtype has a
239 structure that does not contradict the structure of it's parent. For now the
240 above works, but I will clarify the syntax for this at a future point, so
241 it's recommended to avoid (should not really be needed so much anyway). For
242 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
243 patches are welcomed for discussion.
247 Coercions currently work for 'one level' deep. That is you can do:
250 as Dict[name=>Str, age=>Int];
253 as Dict[first=>Str, last=>Str];
256 ## Coerce an object of a particular class
257 from BlessedPersonObject,
258 via { +{name=>$_->name, age=>$_->age} },
259 ## Coerce from [$name, $age]
261 via { +{name=>$_->[0], age=>$_->[1] },
262 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
263 from Dict[fullname=>Fullname, dob=>DateTime],
265 my $age = $_->dob - DateTime->now;
267 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
272 And that should just work as expected. However, if there are any 'inner'
273 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
274 won't currently get activated.
276 Please see the test '07-coerce.t' for a more detailed example. Discussion on
277 extending coercions to support this welcome on the Moose development channel or
280 =head1 TYPE CONSTRAINTS
282 This type library defines the following constraints.
284 =head2 Tuple[@constraints]
286 This defines an arrayref based constraint which allows you to validate a specific
287 list of constraints. For example:
289 Tuple[Int,Str]; ## Validates [1,'hello']
290 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
292 =head2 Dict[%constraints]
294 This defines a hashref based constraint which allowed you to validate a specific
295 hashref. For example:
297 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
299 =head2 Optional[$constraint]
301 This is primarily a helper constraint for Dict and Tuple type constraints. What
302 this allows if for you to assert that a given type constraint is allowed to be
303 null (but NOT undefined). If the value is null, then the type constraint passes
304 but if the value is defined it must validate against the type constraint. This
305 makes it easy to make a Dict where one or more of the keys doesn't have to exist
306 or a tuple where some of the values are not required. For example:
308 subtype Name() => as Dict[
311 middle=>Optional[Str],
314 Creates a constraint that validates against a hashref with the keys 'first' and
315 'last' being strings and required while an optional key 'middle' is must be a
316 string if it appears but doesn't have to appear. So in this case both the
319 {first=>'John', middle=>'James', last=>'Napiorkowski'}
320 {first=>'Vanessa', last=>'Li'}
324 Here are some additional example usage for structured types. All examples can
325 be found also in the 't/examples.t' test. Your contributions are also welcomed.
327 =head2 Normalize a HashRef
329 You need a hashref to conform to a canonical structure but are required accept a
330 bunch of different incoming structures. You can normalize using the Dict type
331 constraint and coercions. This example also shows structured types mixed which
332 other MooseX::Types libraries.
334 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
339 use MooseX::Types::Structured qw(Dict Tuple);
340 use MooseX::Types::DateTime qw(DateTime);
341 use MooseX::Types::Moose qw(Int Str Object);
342 use MooseX::Types -declare => [qw(Name Age Person)];
345 as Dict[name=>Str, age=>Int];
348 from Dict[first=>Str, last=>Str, years=>Int],
350 name => "$_->{first} $_->{last}",
353 from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime],
355 name => "$_->{fullname}{first} $_->{fullname}{last}",
356 age => ($_->{dob} - 'DateTime'->now)->years,
359 has person => (is=>'rw', isa=>Person, coerce=>1);
363 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
364 MooseX::Meta::TypeConstraint::Structured->new(
365 name => "MooseX::Types::Structured::Tuple" ,
366 parent => find_type_constraint('ArrayRef'),
367 constraint_generator=> sub {
368 ## Get the constraints and values to check
369 my ($type_constraints, $values) = @_;
370 my @type_constraints = defined $type_constraints ? @$type_constraints: ();
371 my @values = defined $values ? @$values: ();
372 ## Perform the checking
373 while(@type_constraints) {
374 my $type_constraint = shift @type_constraints;
376 my $value = shift @values;
377 unless($type_constraint->check($value)) {
381 unless($type_constraint->check()) {
386 ## Make sure there are no leftovers.
389 } elsif(@type_constraints) {
398 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
399 MooseX::Meta::TypeConstraint::Structured->new(
400 name => "MooseX::Types::Structured::Dict",
401 parent => find_type_constraint('HashRef'),
402 constraint_generator=> sub {
403 ## Get the constraints and values to check
404 my ($type_constraints, $values) = @_;
405 my %type_constraints = defined $type_constraints ? @$type_constraints: ();
406 my %values = defined $values ? %$values: ();
407 ## Perform the checking
408 while(%type_constraints) {
409 my($key, $type_constraint) = each %type_constraints;
410 delete $type_constraints{$key};
411 if(exists $values{$key}) {
412 my $value = $values{$key};
413 delete $values{$key};
414 unless($type_constraint->check($value)) {
418 unless($type_constraint->check()) {
423 ## Make sure there are no leftovers.
426 } elsif(%type_constraints) {
436 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
437 name => 'MooseX::Types::Structured::Optional',
438 package_defined_in => __PACKAGE__,
439 parent => find_type_constraint('Item'),
440 constraint => sub { 1 },
441 constraint_generator => sub {
442 my ($type_parameter, @args) = @_;
443 my $check = $type_parameter->_compiled_type_constraint();
446 if(exists($args[0])) {
447 ## If it exists, we need to validate it
450 ## But it's is okay if the value doesn't exists
457 Moose::Util::TypeConstraints::register_type_constraint($Optional);
458 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
464 The following modules or resources may be of interest.
466 L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>,
467 L<MooseX::Meta::TypeConstraint::Structured>
471 Need to clarify deep coercions, need to clarify subtypes of subtypes.
475 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
477 =head1 COPYRIGHT & LICENSE
479 This program is free software; you can redistribute it and/or modify
480 it under the same terms as Perl itself.