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