1 package MooseX::Types::Structured;
5 use Moose::Util::TypeConstraints;
6 use MooseX::Meta::TypeConstraint::Structured;
7 use MooseX::Types -declare => [qw(Dict Tuple Optional)];
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 Optional);
26 ## A name has a first and last part, but middle names are not required
31 middle=>Optional[Str],
35 Then you can instantiate this class with something like:
37 my $john = MyApp::MyClass->new(
45 my $vanessa = MyApp::MyClass->new(
52 But all of these would cause a constraint error for the 'name' attribute:
54 MyApp::MyClass->new( name=>'John' );
55 MyApp::MyClass->new( name=>{first_name=>'John'} );
56 MyApp::MyClass->new( name=>{first_name=>'John', age=>39} );
57 MyApp::MyClass->new( name=>{first=>'Vanessa', middle=>[1,2], last=>'Li'} );
59 Please see the test cases for more examples.
63 A structured type constraint is a standard container L</Moose> type constraint,
64 such as an arrayref or hashref, which has been enhanced to allow you to
65 explicitly name all the allow type constraints inside the structure. The
68 TypeConstraint[TypeParameters]
70 Where 'TypeParameters' is an array or hash of L</Moose::Meta::TypeConstraint>
73 This type library enables structured type constraints. It is build 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 the core Moose types 'HashRef' and
80 'ArrayRef'. Structured types have similar functionality, so their syntax is
81 likewise similar. For example, you could define a parameterized constraint like:
86 which would constraint a value to something like [1,2,3,...] and so on. On the
87 other hand, a structured type constraint explicitly names all it's allowed type
88 parameter constraints. For the example:
90 subtype StringFollowedByInt,
93 would constrain it's value to something like ['hello', 111] but ['hello', 'world']
94 would fail, as well as ['hello', 111, 'world'] and so on.
96 Structured Constraints are not limited to arrays. You can define a structure
97 against a hashref with 'Dict' as in this example:
99 subtype FirstNameLastName,
100 as Dict[firste=>Str, lastname=>Str];
102 This would constrain a hashref to something like:
104 {firstname=>'Vanessa', lastname=>'Li'};
106 but all the following would fail validation:
108 {first=>'Vanessa', last=>'Li'};
109 {firstname=>'Vanessa', lastname=>'Li', middlename=>'NA'};
112 These structures can be as simple or elaborate as you wish. You can even
113 combine various structured, parameterized and simple constraints all together:
118 Dict[name=>Str, age=>Int],
122 Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
123 the type parameters can be visually arranged to your liking and to improve the
124 clarity of your meaning. You don't need to run then altogether onto a single
129 You should exercise some care as to whether or not your complex structured
130 constraints would be better off contained by a real object as in the following
133 package MyApp::MyStruct;
136 has $_ for qw(name age);
138 package MyApp::MyClass;
141 has person => (isa=>'MyApp::MyStruct');
143 my $instance = MyApp::MyClass->new(
144 person=>MyApp::MyStruct->new(name=>'John', age=>39),
147 This method may take some additional time to setup but will give you more
148 flexibility. However, structured constraints are highly compatible with this
149 method, granting some interesting possibilities for coercion. Try:
152 as 'MyApp::MyStruct';
155 from (Dict[name=>Str, age=>Int]),
156 via { MyApp::MyStruct->new(%$_) },
157 from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
159 my $name = $_->{first_name} .' '. $_->{last_name};
160 my $age = DateTime->now - $_->{dob};
161 MyApp::MyStruct->new( name=>$name, age=>$age->years );
164 =head2 Subtyping a structured subtype
166 You need to exercise some care when you try to subtype a structured type
170 as Dict[name=>Str, age=>Int];
172 subtype FriendlyPerson,
173 as Person[name=>Str, age=>Int, totalFriends=>Int];
175 This will actually work BUT you have to take care that the subtype has a
176 structure that does not contradict the structure of it's parent. For now the
177 above works, but I will clarify the syntax for this at a future point, so
178 it's recommended to avoid (should not realy be needed so much anyway). For
179 now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
180 patches are welcomed for discussion.
184 Coercions currently work for 'one level' deep. That is you can do:
187 as Dict[name=>Str, age=>Int];
190 as Dict[first=>Str, last=>Str];
193 ## Coerce an object of a particular class
194 from BlessedPersonObject,
195 via { +{name=>$_->name, age=>$_->age} },
196 ## Coerce from [$name, $age]
198 via { +{name=>$_->[0], age=>$_->[1] },
199 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
200 from Dict[fullname=>Fullname, dob=>DateTime],
202 my $age = $_->dob - DateTime->now;
204 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
209 And that should just work as expected. However, if there are any 'inner'
210 coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
211 won't currently get activated.
213 Please see the test '07-coerce.t' for a more detailed example.
215 =head1 TYPE CONSTRAINTS
217 This type library defines the following constraints.
219 =head2 Tuple[@constraints]
221 This defines an arrayref based constraint which allows you to validate a specific
222 list of constraints. For example:
224 Tuple[Int,Str]; ## Validates [1,'hello']
225 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
227 =head2 Dict [%constraints]
229 This defines a hashref based constraint which allowed you to validate a specific
230 hashref. For example:
232 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
234 =head2 Optional [$constraint]
236 This is primarily a helper constraint for Dict and Tuple type constraints. What
237 this allows if for you to assert that a given type constraint is allowed to be
238 null (but NOT undefined). If the value is null, then the type constraint passes
239 but if the value is defined it must validate against the type constraint. This
240 makes it easy to make a Dict where one or more of the keys doesn't have to exist
241 or a tuple where some of the values are not required. For example:
243 subtype Name() => as Dict[
246 middle=>Optional[Str],
249 Creates a constraint that validates against a hashref with the keys 'first' and
250 'last' being strings and required while an optional key 'middle' is must be a
251 string if it appears but doesn't have to appear. So in this case both the
254 {first=>'John', middle=>'James', last=>'Napiorkowski'}
255 {first=>'Vanessa', last=>'Li'}
259 Here are some additional example usage for structured types. All examples can
260 be found also in the 't/examples.t' test. Your contributions are also welcomed.
262 =head2 Normalize a HashRef
264 You need a hashref to conform to a canonical structure but are required accept a
265 bunch of different incoming structures. You can normalize using the Dict type
266 constraint and coercions. This example also shows structured types mixed which
267 other MooseX::Types libraries.
269 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
274 use MooseX::Types::Structured qw(Dict Tuple);
275 use MooseX::Types::DateTime qw(DateTime);
276 use MooseX::Types::Moose qw(Int Str Object);
277 use MooseX::Types -declare => [qw(Name Age Person)];
280 as Dict[name=>Str, age=>Int];
283 from Dict[first=>Str, last=>Str, years=>Int],
285 name => "$_->{first} $_->{last}",
288 from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime],
290 name => "$_->{fullname}{first} $_->{fullname}{last}",
291 age => ($_->{dob} - 'DateTime'->now)->years,
294 has person => (is=>'rw', isa=>Person, coerce=>1);
298 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
299 MooseX::Meta::TypeConstraint::Structured->new(
300 name => "MooseX::Types::Structured::Tuple" ,
301 parent => find_type_constraint('ArrayRef'),
302 constraint_generator=> sub {
303 ## Get the constraints and values to check
304 my ($type_constraints, $values) = @_;
305 my @type_constraints = defined $type_constraints ? @$type_constraints: ();
306 my @values = defined $values ? @$values: ();
307 ## Perform the checking
308 while(@type_constraints) {
309 my $type_constraint = shift @type_constraints;
311 my $value = shift @values;
312 unless($type_constraint->check($value)) {
316 unless($type_constraint->check()) {
321 ## Make sure there are no leftovers.
324 } elsif(@type_constraints) {
333 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
334 MooseX::Meta::TypeConstraint::Structured->new(
335 name => "MooseX::Types::Structured::Dict",
336 parent => find_type_constraint('HashRef'),
337 constraint_generator=> sub {
338 ## Get the constraints and values to check
339 my ($type_constraints, $values) = @_;
340 my %type_constraints = defined $type_constraints ? @$type_constraints: ();
341 my %values = defined $values ? %$values: ();
342 ## Perform the checking
343 while(%type_constraints) {
344 my($key, $type_constraint) = each %type_constraints;
345 delete $type_constraints{$key};
346 if(exists $values{$key}) {
347 my $value = $values{$key};
348 delete $values{$key};
349 unless($type_constraint->check($value)) {
353 unless($type_constraint->check()) {
358 ## Make sure there are no leftovers.
361 } elsif(%type_constraints) {
371 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
372 name => 'MooseX::Types::Structured::Optional',
373 package_defined_in => __PACKAGE__,
374 parent => find_type_constraint('Item'),
375 constraint => sub { 1 },
376 constraint_generator => sub {
377 my ($type_parameter, @args) = @_;
378 my $check = $type_parameter->_compiled_type_constraint();
381 if(exists($args[0])) {
382 ## If it exists, we need to validate it
385 ## But it's is okay if the value doesn't exists
392 Moose::Util::TypeConstraints::register_type_constraint($Optional);
393 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
399 The following modules or resources may be of interest.
401 L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
402 L<MooseX::Meta::TypeConstraint::Structured>
406 Need to clarify deep coercions, need to clarify subtypes of subtypes.
410 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
412 =head1 COPYRIGHT & LICENSE
414 This program is free software; you can redistribute it and/or modify
415 it under the same terms as Perl itself.