actually create the optional TC, and a bunch fo changes to make sure the api test...
[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;
e327145a 7use MooseX::Types -declare => [qw(Dict Tuple Optional)];
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'),
e327145a 263 constraint_generator=> sub {
67a8bc04 264 ## Get the constraints and values to check
e327145a 265 my ($type_constraints, $values) = @_;
266 my @type_constraints = defined $type_constraints ? @$type_constraints: ();
267 my @values = defined $values ? @$values: ();
67a8bc04 268 ## Perform the checking
269 while(@type_constraints) {
270 my $type_constraint = shift @type_constraints;
a30fa891 271 if(@values) {
67a8bc04 272 my $value = shift @values;
273 unless($type_constraint->check($value)) {
274 return;
275 }
276 } else {
a30fa891 277 return;
a30fa891 278 }
279 }
67a8bc04 280 ## Make sure there are no leftovers.
281 if(@values) {
282 return;
283 } elsif(@type_constraints) {
284 return;
285 }else {
286 return 1;
287 }
288 }
289 )
290);
291
292Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
293 MooseX::Meta::TypeConstraint::Structured->new(
294 name => "MooseX::Types::Structured::Dict",
295 parent => find_type_constraint('HashRef'),
e327145a 296 constraint_generator=> sub {
67a8bc04 297 ## Get the constraints and values to check
e327145a 298 my ($type_constraints, $values) = @_;
299 my %type_constraints = defined $type_constraints ? @$type_constraints: ();
300 my %values = defined $values ? %$values: ();
67a8bc04 301 ## Perform the checking
302 while(%type_constraints) {
303 my($key, $type_constraint) = each %type_constraints;
304 delete $type_constraints{$key};
305 if(exists $values{$key}) {
306 my $value = $values{$key};
307 delete $values{$key};
308 unless($type_constraint->check($value)) {
a30fa891 309 return;
310 }
e327145a 311 } else {
a30fa891 312 return;
a30fa891 313 }
67a8bc04 314 }
315 ## Make sure there are no leftovers.
e327145a 316 if(%values) {
67a8bc04 317 return;
318 } elsif(%type_constraints) {
319 return;
320 }else {
321 return 1;
322 }
323 },
324 )
325);
d24da8ec 326
e327145a 327OPTIONAL: {
328 my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new(
329 name => 'MooseX::Types::Structured::Optional',
330 package_defined_in => __PACKAGE__,
331 parent => find_type_constraint('Item'),
332 constraint => sub { 1 },
333 constraint_generator => sub {
334 my ($type_parameter, @args) = @_;
335 my $check = $type_parameter->_compiled_type_constraint();
336 return sub {
337 my (@args) = @_;
338 if(exists($args[0])) {
339 ## If it exists, we need to validate it
340 $check->($args[0]);
341 } else {
342 ## But it's is okay if the value doesn't exists
343 return 1;
344 }
345 }
346 }
347 );
348
349 Moose::Util::TypeConstraints::register_type_constraint($Optional);
350 Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
351}
352
353
d24da8ec 354=head1 SEE ALSO
355
356The following modules or resources may be of interest.
357
a30fa891 358L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
359L<MooseX::Meta::TypeConstraint::Structured>
d24da8ec 360
16aea7bf 361=head1 TODO
362
363Need to clarify deep coercions, need to clarify subtypes of subtypes.
364
d24da8ec 365=head1 AUTHOR
366
367John Napiorkowski, C<< <jjnapiork@cpan.org> >>
368
369=head1 COPYRIGHT & LICENSE
370
371This program is free software; you can redistribute it and/or modify
372it under the same terms as Perl itself.
373
374=cut
67a8bc04 375
3761;