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(
44 my $vanessa = MyApp::MyClass->new(
51 But all of these would cause a constraint error for the 'name' attribute:
53 MyApp::MyClass->new( name=>'John' );
54 MyApp::MyClass->new( name=>{first_name=>'John'} );
55 MyApp::MyClass->new( name=>{first_name=>'John', age=>39} );
56 MyApp::MyClass->new( name=>{first=>'Vanessa', middle=>[1,2], last=>'Li'} );
58 Please see the test cases for more examples.
62 A structured type constraint is a standard container L</Moose> type constraint,
63 such as an arrayref or hashref, which has been enhanced to allow you to
64 explicitly name all the allow type constraints inside the structure. The
67 TypeConstraint[TypeParameters]
69 Where 'TypeParameters' is an array or hash of L</Moose::Meta::TypeConstraint>
72 This type library enables structured type constraints. It is build on top of the
73 L<MooseX::Types> library system, so you should review the documentation for that
74 if you are not familiar with it.
76 =head2 Comparing Parameterized types to Structured types
78 Parameterized constraints are built into the core Moose types 'HashRef' and
79 'ArrayRef'. Structured types have similar functionality, so their syntax is
80 likewise similar. For example, you could define a parameterized constraint like:
85 which would constraint a value to something like [1,2,3,...] and so on. On the
86 other hand, a structured type constraint explicitly names all it's allowed type
87 parameter constraints. For the example:
89 subtype StringFollowedByInt,
92 would constrain it's value to something like ['hello', 111] but ['hello', 'world']
93 would fail, as well as ['hello', 111, 'world'] and so on.
95 Structured Constraints are not limited to arrays. You can define a structure
96 against a hashref with 'Dict' as in this example:
98 subtype FirstNameLastName,
99 as Dict[firste=>Str, lastname=>Str];
101 This would constrain a hashref to something like:
103 {firstname=>'Vanessa', lastname=>'Li'};
105 but all the following would fail validation:
107 {first=>'Vanessa', last=>'Li'};
108 {firstname=>'Vanessa', lastname=>'Li', middlename=>'NA'};
111 These structures can be as simple or elaborate as you wish. You can even
112 combine various structured, parameterized and simple constraints all together:
117 Dict[name=>Str, age=>Int],
121 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
122 the type parameters can be visually arranged to your liking and to improve the
123 clarity of your meaning. You don't need to run then altogether onto a single
128 You should exercise some care as to whether or not your complex structured
129 constraints would be better off contained by a real object as in the following
132 package MyApp::MyStruct;
135 has $_ for qw(name age);
137 package MyApp::MyClass;
140 has person => (isa=>'MyApp::MyStruct');
142 my $instance = MyApp::MyClass->new(
143 person=>MyApp::MyStruct->new(name=>'John', age=>39),
146 This method may take some additional time to setup but will give you more
147 flexibility. However, structured constraints are highly compatible with this
148 method, granting some interesting possibilities for coercion. Try:
151 as 'MyApp::MyStruct';
154 from (Dict[name=>Str, age=>Int]),
155 via { MyApp::MyStruct->new(%$_) },
156 from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
158 my $name = $_->{first_name} .' '. $_->{last_name};
159 my $age = DateTime->now - $_->{dob};
160 MyApp::MyStruct->new( name=>$name, age=>$age->years );
163 =head2 Subtyping a structured subtype
165 You need to exercise some care when you try to subtype a structured type
169 as Dict[name=>Str, age=>Int];
171 subtype FriendlyPerson,
172 as Person[name=>Str, age=>Int, totalFriends=>Int];
174 This will actually work BUT you have to take care that the subtype has a
175 structure that does not contradict the structure of it's parent. For now the
176 above works, but I will clarify the syntax for this at a future point, so
177 it's recommended to avoid (should not realy be needed so much anyway). For
178 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
179 patches are welcomed for discussion.
183 Coercions currently work for 'one level' deep. That is you can do:
186 as Dict[name=>Str, age=>Int];
189 as Dict[first=>Str, last=>Str];
192 ## Coerce an object of a particular class
193 from BlessedPersonObject,
194 via { +{name=>$_->name, age=>$_->age} },
195 ## Coerce from [$name, $age]
197 via { +{name=>$_->[0], age=>$_->[1] },
198 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
199 from Dict[fullname=>Fullname, dob=>DateTime],
201 my $age = $_->dob - DateTime->now;
203 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
208 And that should just work as expected. However, if there are any 'inner'
209 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
210 won't currently get activated.
212 Please see the test '07-coerce.t' for a more detailed example.
214 =head1 TYPE CONSTRAINTS
216 This type library defines the following constraints.
218 =head2 Tuple[@constraints]
220 This defines an arrayref based constraint which allows you to validate a specific
221 list of constraints. For example:
223 Tuple[Int,Str]; ## Validates [1,'hello']
224 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
226 =head2 Dict [%constraints]
228 This defines a hashref based constraint which allowed you to validate a specific
229 hashref. For example:
231 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
233 =head2 Optional [$constraint]
235 This is primarily a helper constraint for Dict and Tuple type constraints. What
236 this allows if for you to assert that a given type constraint is allowed to be
237 null (but NOT undefined). If the value is null, then the type constraint passes
238 but if the value is defined it must validate against the type constraint. This
239 makes it easy to make a Dict where one or more of the keys doesn't have to exist
240 or a tuple where some of the values are not required. For example:
242 subtype Name() => as Dict[
245 middle=>Optional[Str],
248 Creates a constraint that validates against a hashref with the keys 'first' and
249 'last' being strings and required while an optional key 'middle' is must be a
250 string if it appears but doesn't have to appear. So in this case both the
253 {first=>'John', middle=>'James', last=>'Napiorkowski'}
254 {first=>'Vanessa', last=>'Li'}
258 Here are some additional example usage for structured types. All examples can
259 be found also in the 't/examples.t' test. Your contributions are also welcomed.
261 =head2 Normalize a HashRef
263 You need a hashref to conform to a canonical structure but are required accept a
264 bunch of different incoming structures. You can normalize using the Dict type
265 constraint and coercions. This example also shows structured types mixed which
266 other MooseX::Types libraries.
268 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
273 use MooseX::Types::Structured qw(Dict Tuple);
274 use MooseX::Types::DateTime qw(DateTime);
275 use MooseX::Types::Moose qw(Int Str Object);
276 use MooseX::Types -declare => [qw(Name Age Person)];
279 as Dict[name=>Str, age=>Int];
282 from Dict[first=>Str, last=>Str, years=>Int],
284 name => "$_->{first} $_->{last}",
287 from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime],
289 name => "$_->{fullname}{first} $_->{fullname}{last}",
290 age => ($_->{dob} - 'DateTime'->now)->years,
293 has person => (is=>'rw', isa=>Person, coerce=>1);
297 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
298 MooseX::Meta::TypeConstraint::Structured->new(
299 name => "MooseX::Types::Structured::Tuple" ,
300 parent => find_type_constraint('ArrayRef'),
301 constraint_generator=> sub {
302 ## Get the constraints and values to check
303 my ($type_constraints, $values) = @_;
304 my @type_constraints = defined $type_constraints ? @$type_constraints: ();
305 my @values = defined $values ? @$values: ();
306 ## Perform the checking
307 while(@type_constraints) {
308 my $type_constraint = shift @type_constraints;
310 my $value = shift @values;
311 unless($type_constraint->check($value)) {
315 unless($type_constraint->check()) {
320 ## Make sure there are no leftovers.
323 } elsif(@type_constraints) {
332 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
333 MooseX::Meta::TypeConstraint::Structured->new(
334 name => "MooseX::Types::Structured::Dict",
335 parent => find_type_constraint('HashRef'),
336 constraint_generator=> sub {
337 ## Get the constraints and values to check
338 my ($type_constraints, $values) = @_;
339 my %type_constraints = defined $type_constraints ? @$type_constraints: ();
340 my %values = defined $values ? %$values: ();
341 ## Perform the checking
342 while(%type_constraints) {
343 my($key, $type_constraint) = each %type_constraints;
344 delete $type_constraints{$key};
345 if(exists $values{$key}) {
346 my $value = $values{$key};
347 delete $values{$key};
348 unless($type_constraint->check($value)) {
352 unless($type_constraint->check()) {
357 ## Make sure there are no leftovers.
360 } elsif(%type_constraints) {
370 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
371 name => 'MooseX::Types::Structured::Optional',
372 package_defined_in => __PACKAGE__,
373 parent => find_type_constraint('Item'),
374 constraint => sub { 1 },
375 constraint_generator => sub {
376 my ($type_parameter, @args) = @_;
377 my $check = $type_parameter->_compiled_type_constraint();
380 if(exists($args[0])) {
381 ## If it exists, we need to validate it
384 ## But it's is okay if the value doesn't exists
391 Moose::Util::TypeConstraints::register_type_constraint($Optional);
392 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
398 The following modules or resources may be of interest.
400 L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
401 L<MooseX::Meta::TypeConstraint::Structured>
405 Need to clarify deep coercions, need to clarify subtypes of subtypes.
409 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
411 =head1 COPYRIGHT & LICENSE
413 This program is free software; you can redistribute it and/or modify
414 it under the same terms as Perl itself.