changed documentation errors, added a bit more docs
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Types / Structured.pm
CommitLineData
d24da8ec 1package MooseX::Types::Structured;
2
98336987 3use 5.008;
6c2f284c 4use Moose;
5use Moose::Util::TypeConstraints;
a30fa891 6use MooseX::Meta::TypeConstraint::Structured;
7use MooseX::Types -declare => [qw(Dict Tuple)];
011bacc6 8
d87e8b74 9our $VERSION = '0.05';
d24da8ec 10our $AUTHORITY = 'cpan:JJNAPIORK';
11
12=head1 NAME
13
af1d00c9 14MooseX::Types::Structured - Structured Type Constraints for Moose
d24da8ec 15
16=head1 SYNOPSIS
17
af1d00c9 18The following is example usage for this module.
6c2f284c 19
af1d00c9 20 package MyApp::MyClass;
6c2f284c 21
af1d00c9 22 use Moose;
23 use MooseX::Types::Moose qw(Str Int);
24 use MooseX::Types::Structured qw(Dict Tuple);
25
26 has name => (isa=>Dict[first_name=>Str, last_name=>Str]);
27
6c2f284c 28Then you can instantiate this class with something like:
29
af1d00c9 30 my $instance = MyApp::MyClass->new(
d87e8b74 31 name => {
32 first_name=>'John',
33 last_name=>'Napiorkowski',
34 },
35 );
d24da8ec 36
d87e8b74 37But all of these would cause a constraint error for the 'name' attribute:
6c2f284c 38
d87e8b74 39 MyApp::MyClass->new( name=>'John' );
40 MyApp::MyClass->new( name=>{first_name=>'John'} );
41 MyApp::MyClass->new( name=>{first_name=>'John', age=>39} );
6c2f284c 42
43Please see the test cases for more examples.
d24da8ec 44
45=head1 DESCRIPTION
46
af1d00c9 47A structured type constraint is a standard container L</Moose> type constraint,
48such as an arrayref or hashref, which has been enhanced to allow you to
59deb858 49explicitly name all the allow type constraints inside the structure. The
af1d00c9 50generalized form is:
51
52 TypeConstraint[TypeParameters]
53
d87e8b74 54Where 'TypeParameters' is an array or hash of L</Moose::Meta::TypeConstraint>
55type constraints.
af1d00c9 56
59deb858 57This type library enables structured type constraints. It is build on top of the
58L<MooseX::Types> library system, so you should review the documentation for that
59if you are not familiar with it.
60
5632ada1 61=head2 Comparing Parameterized types to Structured types
59deb858 62
63Parameterized constraints are built into the core Moose types 'HashRef' and
64'ArrayRef'. Structured types have similar functionality, so their syntax is
65likewise similar. For example, you could define a parameterized constraint like:
6c2f284c 66
d87e8b74 67 subtype ArrayOfInts,
68 as Arrayref[Int];
6c2f284c 69
af1d00c9 70which would constraint a value to something like [1,2,3,...] and so on. On the
59deb858 71other hand, a structured type constraint explicitly names all it's allowed type
af1d00c9 72parameter constraints. For the example:
6c2f284c 73
af1d00c9 74 subtype StringFollowedByInt,
75 as Tuple[Str,Int];
6c2f284c 76
59deb858 77would constrain it's value to something like ['hello', 111] but ['hello', 'world']
d87e8b74 78would fail, as well as ['hello', 111, 'world'] and so on.
79
80Structured Constraints are not limited to arrays. You can define a structure
81against a hashref with 'Dict' as in this example:
82
83 subtype FirstNameLastName,
84 as Dict[firste=>Str, lastname=>Str];
85
86This would constrain a hashref to something like:
87
88 {firstname=>'Vanessa', lastname=>'Li'};
89
90but all the following would fail validation:
91
92 {first=>'Vanessa', last=>'Li'};
93 {firstname=>'Vanessa', lastname=>'Li', middlename=>'NA'};
94 ['Vanessa', 'Li'];
6c2f284c 95
96These structures can be as simple or elaborate as you wish. You can even
97combine various structured, parameterized and simple constraints all together:
98
af1d00c9 99 subtype crazy,
100 as Tuple[
101 Int,
102 Dict[name=>Str, age=>Int],
103 ArrayRef[Int]
104 ];
6c2f284c 105
af1d00c9 106Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
59deb858 107the type parameters can be visually arranged to your liking and to improve the
108clarity of your meaning. You don't need to run then altogether onto a single
109line.
110
111=head2 Alternatives
6c2f284c 112
113You should exercise some care as to whether or not your complex structured
114constraints would be better off contained by a real object as in the following
115example:
116
af1d00c9 117 package MyApp::MyStruct;
118 use Moose;
119
120 has $_ for qw(name age);
121
122 package MyApp::MyClass;
123 use Moose;
124
125 has person => (isa=>'MyApp::MyStruct');
126
127 my $instance = MyApp::MyClass->new(
128 person=>MyApp::MyStruct->new(name=>'John', age=>39),
129 );
6c2f284c 130
131This method may take some additional time to setup but will give you more
132flexibility. However, structured constraints are highly compatible with this
133method, granting some interesting possibilities for coercion. Try:
134
af1d00c9 135 subtype 'MyStruct',
136 as 'MyApp::MyStruct';
137
138 coerce 'MyStruct',
139 from (Dict[name=>Str, age=>Int]),
59deb858 140 via { MyApp::MyStruct->new(%$_) },
af1d00c9 141 from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
142 via {
143 my $name = $_->{first_name} .' '. $_->{last_name};
144 my $age = DateTime->now - $_->{dob};
59deb858 145 MyApp::MyStruct->new( name=>$name, age=>$age->years );
af1d00c9 146 };
a4a88fef 147
16aea7bf 148=head2 Subtyping a structured subtype
149
150You need to exercise some care when you try to subtype a structured type
a4a88fef 151as in this example:
d24da8ec 152
af1d00c9 153 subtype Person,
d87e8b74 154 as Dict[name=>Str, age=>Int];
a4a88fef 155
af1d00c9 156 subtype FriendlyPerson,
157 as Person[name=>Str, age=>Int, totalFriends=>Int];
a4a88fef 158
16aea7bf 159This will actually work BUT you have to take care that the subtype has a
a4a88fef 160structure that does not contradict the structure of it's parent. For now the
59deb858 161above works, but I will clarify the syntax for this at a future point, so
a4a88fef 162it's recommended to avoid (should not realy be needed so much anyway). For
59deb858 163now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
164patches are welcomed for discussion.
16aea7bf 165
166=head2 Coercions
167
168Coercions currently work for 'one level' deep. That is you can do:
169
af1d00c9 170 subtype Person,
16aea7bf 171 as Dict[name=>Str, age=>Int];
af1d00c9 172
16aea7bf 173 subtype Fullname,
174 as Dict[first=>Str, last=>Str];
af1d00c9 175
176 coerce Person,
d87e8b74 177 ## Coerce an object of a particular class
af1d00c9 178 from BlessedPersonObject,
179 via { +{name=>$_->name, age=>$_->age} },
d87e8b74 180 ## Coerce from [$name, $age]
af1d00c9 181 from ArrayRef,
182 via { +{name=>$_->[0], age=>$_->[1] },
d87e8b74 183 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
16aea7bf 184 from Dict[fullname=>Fullname, dob=>DateTime],
185 via {
af1d00c9 186 my $age = $_->dob - DateTime->now;
187 +{
188 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
189 age=>$age->years
190 }
16aea7bf 191 };
192
193And that should just work as expected. However, if there are any 'inner'
194coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
195won't currently get activated.
196
197Please see the test '07-coerce.t' for a more detailed example.
198
199=head1 TYPE CONSTRAINTS
200
201This type library defines the following constraints.
202
203=head2 Tuple[@constraints]
204
205This defines an arrayref based constraint which allows you to validate a specific
206list of constraints. For example:
207
af1d00c9 208 Tuple[Int,Str]; ## Validates [1,'hello']
209 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
16aea7bf 210
211=head2 Dict [%constraints]
212
213This defines a hashref based constraint which allowed you to validate a specific
214hashref. For example:
215
af1d00c9 216 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
d24da8ec 217
59deb858 218=head1 EXAMPLES
219
220Here are some additional example usage for structured types. All examples can
221be found also in the 't/examples.t' test. Your contributions are also welcomed.
222
223=head2 Normalize a HashRef
224
225You need a hashref to conform to a canonical structure but are required accept a
226bunch of different incoming structures. You can normalize using the Dict type
227constraint and coercions. This example also shows structured types mixed which
228other MooseX::Types libraries.
229
230 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
231
232 use Moose;
233 use DateTime;
234
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)];
239
240 subtype Person,
241 as Dict[name=>Str, age=>Int];
242
243 coerce Person,
244 from Dict[first=>Str, last=>Str, years=>Int],
245 via { +{
246 name => "$_->{first} $_->{last}",
247 age=>$_->{years},
248 }},
249 from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime],
250 via { +{
251 name => "$_->{fullname}{first} $_->{fullname}{last}",
252 age => ($_->{dob} - 'DateTime'->now)->years,
253 }};
254
255 has person => (is=>'rw', isa=>Person, coerce=>1);
256
a30fa891 257=cut
258
67a8bc04 259Moose::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;
a30fa891 270 if(@values) {
67a8bc04 271 my $value = shift @values;
272 unless($type_constraint->check($value)) {
273 return;
274 }
275 } else {
a30fa891 276 return;
a30fa891 277 }
278 }
67a8bc04 279 ## Make sure there are no leftovers.
280 if(@values) {
281 return;
282 } elsif(@type_constraints) {
283 return;
284 }else {
285 return 1;
286 }
287 }
288 )
289);
290
291Moose::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)) {
a30fa891 307 return;
308 }
67a8bc04 309 } else {
a30fa891 310 return;
a30fa891 311 }
67a8bc04 312 }
313 ## Make sure there are no leftovers.
314 if(%values) {
315 return;
316 } elsif(%type_constraints) {
317 return;
318 }else {
319 return 1;
320 }
321 },
322 )
323);
d24da8ec 324
325=head1 SEE ALSO
326
327The following modules or resources may be of interest.
328
a30fa891 329L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
330L<MooseX::Meta::TypeConstraint::Structured>
d24da8ec 331
16aea7bf 332=head1 TODO
333
334Need to clarify deep coercions, need to clarify subtypes of subtypes.
335
d24da8ec 336=head1 AUTHOR
337
338John Napiorkowski, C<< <jjnapiork@cpan.org> >>
339
340=head1 COPYRIGHT & LICENSE
341
342This program is free software; you can redistribute it and/or modify
343it under the same terms as Perl itself.
344
345=cut
67a8bc04 346
3471;