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(
31 name=>{first_name=>'John', last_name=>'Napiorkowski'},
34 But all of these would cause an error:
36 my $instance = MyApp::MyClass->new(name=>'John');
37 my $instance = MyApp::MyClass->new(name=>{first_name=>'John'});
38 my $instance = MyApp::MyClass->new(name=>{first_name=>'John', age=>39});
40 Please see the test cases for more examples.
44 A structured type constraint is a standard container L</Moose> type constraint,
45 such as an arrayref or hashref, which has been enhanced to allow you to
46 explicitly name all the allow type constraints inside the structure. The
49 TypeConstraint[TypeParameters]
51 Where TypeParameters is a list of type constraints.
53 This type library enables structured type constraints. It is build on top of the
54 L<MooseX::Types> library system, so you should review the documentation for that
55 if you are not familiar with it.
57 =head2 Comparing Parameterized types to Structured types
59 Parameterized constraints are built into the core Moose types 'HashRef' and
60 'ArrayRef'. Structured types have similar functionality, so their syntax is
61 likewise similar. For example, you could define a parameterized constraint like:
66 which would constraint a value to something like [1,2,3,...] and so on. On the
67 other hand, a structured type constraint explicitly names all it's allowed type
68 parameter constraints. For the example:
70 subtype StringFollowedByInt,
73 would constrain it's value to something like ['hello', 111] but ['hello', 'world']
74 would fail, as well as ['hello', 111, 'world']
76 These structures can be as simple or elaborate as you wish. You can even
77 combine various structured, parameterized and simple constraints all together:
82 Dict[name=>Str, age=>Int],
86 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
87 the type parameters can be visually arranged to your liking and to improve the
88 clarity of your meaning. You don't need to run then altogether onto a single
93 You should exercise some care as to whether or not your complex structured
94 constraints would be better off contained by a real object as in the following
97 package MyApp::MyStruct;
100 has $_ for qw(name age);
102 package MyApp::MyClass;
105 has person => (isa=>'MyApp::MyStruct');
107 my $instance = MyApp::MyClass->new(
108 person=>MyApp::MyStruct->new(name=>'John', age=>39),
111 This method may take some additional time to setup but will give you more
112 flexibility. However, structured constraints are highly compatible with this
113 method, granting some interesting possibilities for coercion. Try:
116 as 'MyApp::MyStruct';
119 from (Dict[name=>Str, age=>Int]),
120 via { MyApp::MyStruct->new(%$_) },
121 from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
123 my $name = $_->{first_name} .' '. $_->{last_name};
124 my $age = DateTime->now - $_->{dob};
125 MyApp::MyStruct->new( name=>$name, age=>$age->years );
128 =head2 Subtyping a structured subtype
130 You need to exercise some care when you try to subtype a structured type
134 as Dict[name=>Str, age=>iIt];
136 subtype FriendlyPerson,
137 as Person[name=>Str, age=>Int, totalFriends=>Int];
139 This will actually work BUT you have to take care that the subtype has a
140 structure that does not contradict the structure of it's parent. For now the
141 above works, but I will clarify the syntax for this at a future point, so
142 it's recommended to avoid (should not realy be needed so much anyway). For
143 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
144 patches are welcomed for discussion.
148 Coercions currently work for 'one level' deep. That is you can do:
151 as Dict[name=>Str, age=>Int];
154 as Dict[first=>Str, last=>Str];
157 from BlessedPersonObject,
158 via { +{name=>$_->name, age=>$_->age} },
160 via { +{name=>$_->[0], age=>$_->[1] },
161 from Dict[fullname=>Fullname, dob=>DateTime],
163 my $age = $_->dob - DateTime->now;
165 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
170 And that should just work as expected. However, if there are any 'inner'
171 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
172 won't currently get activated.
174 Please see the test '07-coerce.t' for a more detailed example.
176 =head1 TYPE CONSTRAINTS
178 This type library defines the following constraints.
180 =head2 Tuple[@constraints]
182 This defines an arrayref based constraint which allows you to validate a specific
183 list of constraints. For example:
185 Tuple[Int,Str]; ## Validates [1,'hello']
186 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
188 =head2 Dict [%constraints]
190 This defines a hashref based constraint which allowed you to validate a specific
191 hashref. For example:
193 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
197 Here are some additional example usage for structured types. All examples can
198 be found also in the 't/examples.t' test. Your contributions are also welcomed.
200 =head2 Normalize a HashRef
202 You need a hashref to conform to a canonical structure but are required accept a
203 bunch of different incoming structures. You can normalize using the Dict type
204 constraint and coercions. This example also shows structured types mixed which
205 other MooseX::Types libraries.
207 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
212 use MooseX::Types::Structured qw(Dict Tuple);
213 use MooseX::Types::DateTime qw(DateTime);
214 use MooseX::Types::Moose qw(Int Str Object);
215 use MooseX::Types -declare => [qw(Name Age Person)];
218 as Dict[name=>Str, age=>Int];
221 from Dict[first=>Str, last=>Str, years=>Int],
223 name => "$_->{first} $_->{last}",
226 from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime],
228 name => "$_->{fullname}{first} $_->{fullname}{last}",
229 age => ($_->{dob} - 'DateTime'->now)->years,
232 has person => (is=>'rw', isa=>Person, coerce=>1);
236 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
237 MooseX::Meta::TypeConstraint::Structured->new(
238 name => "MooseX::Types::Structured::Tuple" ,
239 parent => find_type_constraint('ArrayRef'),
240 constraint_generator=> sub {
241 ## Get the constraints and values to check
242 my @type_constraints = @{shift @_};
243 my @values = @{shift @_};
244 ## Perform the checking
245 while(@type_constraints) {
246 my $type_constraint = shift @type_constraints;
248 my $value = shift @values;
249 unless($type_constraint->check($value)) {
256 ## Make sure there are no leftovers.
259 } elsif(@type_constraints) {
268 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
269 MooseX::Meta::TypeConstraint::Structured->new(
270 name => "MooseX::Types::Structured::Dict",
271 parent => find_type_constraint('HashRef'),
272 constraint_generator=> sub {
273 ## Get the constraints and values to check
274 my %type_constraints = @{shift @_};
275 my %values = %{shift @_};
276 ## Perform the checking
277 while(%type_constraints) {
278 my($key, $type_constraint) = each %type_constraints;
279 delete $type_constraints{$key};
280 if(exists $values{$key}) {
281 my $value = $values{$key};
282 delete $values{$key};
283 unless($type_constraint->check($value)) {
290 ## Make sure there are no leftovers.
293 } elsif(%type_constraints) {
304 The following modules or resources may be of interest.
306 L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
307 L<MooseX::Meta::TypeConstraint::Structured>
311 Need to clarify deep coercions, need to clarify subtypes of subtypes.
315 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
317 =head1 COPYRIGHT & LICENSE
319 This program is free software; you can redistribute it and/or modify
320 it under the same terms as Perl itself.