1 package MooseX::Types::Structured;
5 use Moose::Util::TypeConstraints;
6 use MooseX::Meta::TypeConstraint::Structured;
7 use MooseX::Types -declare => [qw(Dict Tuple)];
10 our $AUTHORITY = 'cpan:JJNAPIORK';
14 MooseX::Types::Structured - Structured Type Constraints for Moose
18 The following is example usage for this module.
20 package MyApp::MyClass;
23 use MooseX::Types::Moose qw(Str Int);
24 use MooseX::Types::Structured qw(Dict Tuple);
26 has name => (isa=>Dict[first_name=>Str, last_name=>Str]);
28 Then you can instantiate this class with something like:
30 my $instance = MyApp::MyClass->new(
33 last_name=>'Napiorkowski',
37 But all of these would cause a constraint error for the 'name' attribute:
39 MyApp::MyClass->new( name=>'John' );
40 MyApp::MyClass->new( name=>{first_name=>'John'} );
41 MyApp::MyClass->new( name=>{first_name=>'John', age=>39} );
43 Please see the test cases for more examples.
47 A structured type constraint is a standard container L</Moose> type constraint,
48 such as an arrayref or hashref, which has been enhanced to allow you to
49 explicitly name all the allow type constraints inside the structure. The
52 TypeConstraint[TypeParameters]
54 Where 'TypeParameters' is an array or hash of L</Moose::Meta::TypeConstraint>
57 This type library enables structured type constraints. It is build on top of the
58 L<MooseX::Types> library system, so you should review the documentation for that
59 if you are not familiar with it.
61 =head2 Comparing Parameterized types to Structured types
63 Parameterized constraints are built into the core Moose types 'HashRef' and
64 'ArrayRef'. Structured types have similar functionality, so their syntax is
65 likewise similar. For example, you could define a parameterized constraint like:
70 which would constraint a value to something like [1,2,3,...] and so on. On the
71 other hand, a structured type constraint explicitly names all it's allowed type
72 parameter constraints. For the example:
74 subtype StringFollowedByInt,
77 would constrain it's value to something like ['hello', 111] but ['hello', 'world']
78 would fail, as well as ['hello', 111, 'world'] and so on.
80 Structured Constraints are not limited to arrays. You can define a structure
81 against a hashref with 'Dict' as in this example:
83 subtype FirstNameLastName,
84 as Dict[firste=>Str, lastname=>Str];
86 This would constrain a hashref to something like:
88 {firstname=>'Vanessa', lastname=>'Li'};
90 but all the following would fail validation:
92 {first=>'Vanessa', last=>'Li'};
93 {firstname=>'Vanessa', lastname=>'Li', middlename=>'NA'};
96 These structures can be as simple or elaborate as you wish. You can even
97 combine various structured, parameterized and simple constraints all together:
102 Dict[name=>Str, age=>Int],
106 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
107 the type parameters can be visually arranged to your liking and to improve the
108 clarity of your meaning. You don't need to run then altogether onto a single
113 You should exercise some care as to whether or not your complex structured
114 constraints would be better off contained by a real object as in the following
117 package MyApp::MyStruct;
120 has $_ for qw(name age);
122 package MyApp::MyClass;
125 has person => (isa=>'MyApp::MyStruct');
127 my $instance = MyApp::MyClass->new(
128 person=>MyApp::MyStruct->new(name=>'John', age=>39),
131 This method may take some additional time to setup but will give you more
132 flexibility. However, structured constraints are highly compatible with this
133 method, granting some interesting possibilities for coercion. Try:
136 as 'MyApp::MyStruct';
139 from (Dict[name=>Str, age=>Int]),
140 via { MyApp::MyStruct->new(%$_) },
141 from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
143 my $name = $_->{first_name} .' '. $_->{last_name};
144 my $age = DateTime->now - $_->{dob};
145 MyApp::MyStruct->new( name=>$name, age=>$age->years );
148 =head2 Subtyping a structured subtype
150 You need to exercise some care when you try to subtype a structured type
154 as Dict[name=>Str, age=>Int];
156 subtype FriendlyPerson,
157 as Person[name=>Str, age=>Int, totalFriends=>Int];
159 This will actually work BUT you have to take care that the subtype has a
160 structure that does not contradict the structure of it's parent. For now the
161 above works, but I will clarify the syntax for this at a future point, so
162 it's recommended to avoid (should not realy be needed so much anyway). For
163 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
164 patches are welcomed for discussion.
168 Coercions currently work for 'one level' deep. That is you can do:
171 as Dict[name=>Str, age=>Int];
174 as Dict[first=>Str, last=>Str];
177 ## Coerce an object of a particular class
178 from BlessedPersonObject,
179 via { +{name=>$_->name, age=>$_->age} },
180 ## Coerce from [$name, $age]
182 via { +{name=>$_->[0], age=>$_->[1] },
183 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
184 from Dict[fullname=>Fullname, dob=>DateTime],
186 my $age = $_->dob - DateTime->now;
188 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
193 And that should just work as expected. However, if there are any 'inner'
194 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
195 won't currently get activated.
197 Please see the test '07-coerce.t' for a more detailed example.
199 =head1 TYPE CONSTRAINTS
201 This type library defines the following constraints.
203 =head2 Tuple[@constraints]
205 This defines an arrayref based constraint which allows you to validate a specific
206 list of constraints. For example:
208 Tuple[Int,Str]; ## Validates [1,'hello']
209 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
211 =head2 Dict [%constraints]
213 This defines a hashref based constraint which allowed you to validate a specific
214 hashref. For example:
216 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
220 Here are some additional example usage for structured types. All examples can
221 be found also in the 't/examples.t' test. Your contributions are also welcomed.
223 =head2 Normalize a HashRef
225 You need a hashref to conform to a canonical structure but are required accept a
226 bunch of different incoming structures. You can normalize using the Dict type
227 constraint and coercions. This example also shows structured types mixed which
228 other MooseX::Types libraries.
230 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
235 use MooseX::Types::Structured qw(Dict Tuple);
236 use MooseX::Types::DateTime qw(DateTime);
237 use MooseX::Types::Moose qw(Int Str Object);
238 use MooseX::Types -declare => [qw(Name Age Person)];
241 as Dict[name=>Str, age=>Int];
244 from Dict[first=>Str, last=>Str, years=>Int],
246 name => "$_->{first} $_->{last}",
249 from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime],
251 name => "$_->{fullname}{first} $_->{fullname}{last}",
252 age => ($_->{dob} - 'DateTime'->now)->years,
255 has person => (is=>'rw', isa=>Person, coerce=>1);
259 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
260 MooseX::Meta::TypeConstraint::Structured->new(
261 name => "MooseX::Types::Structured::Tuple" ,
262 parent => find_type_constraint('ArrayRef'),
263 constraint_generator=> sub {
264 ## Get the constraints and values to check
265 my @type_constraints = @{shift @_};
266 my @values = @{shift @_};
267 ## Perform the checking
268 while(@type_constraints) {
269 my $type_constraint = shift @type_constraints;
271 my $value = shift @values;
272 unless($type_constraint->check($value)) {
279 ## Make sure there are no leftovers.
282 } elsif(@type_constraints) {
291 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
292 MooseX::Meta::TypeConstraint::Structured->new(
293 name => "MooseX::Types::Structured::Dict",
294 parent => find_type_constraint('HashRef'),
295 constraint_generator=> sub {
296 ## Get the constraints and values to check
297 my %type_constraints = @{shift @_};
298 my %values = %{shift @_};
299 ## Perform the checking
300 while(%type_constraints) {
301 my($key, $type_constraint) = each %type_constraints;
302 delete $type_constraints{$key};
303 if(exists $values{$key}) {
304 my $value = $values{$key};
305 delete $values{$key};
306 unless($type_constraint->check($value)) {
313 ## Make sure there are no leftovers.
316 } elsif(%type_constraints) {
327 The following modules or resources may be of interest.
329 L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
330 L<MooseX::Meta::TypeConstraint::Structured>
334 Need to clarify deep coercions, need to clarify subtypes of subtypes.
338 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
340 =head1 COPYRIGHT & LICENSE
342 This program is free software; you can redistribute it and/or modify
343 it under the same terms as Perl itself.