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