Fixed a couple doc typos
[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         
9 our $VERSION = '0.01';
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.  You can define a class that has
19 an attribute with a structured type like so:
20
21         package MyApp::MyClass;
22         
23         use Moose;
24         use MooseX::Types::Moose qw(Str Int);
25         use MooseX::Types::Structured qw(Dict Tuple);
26         
27         has name => (isa=>Dict[first_name=>Str, last_name=>Str]);
28         
29 Then you can instantiate this class with something like:
30
31         my $instance = MyApp::MyClass->new(
32                 name=>{first_name=>'John', last_name=>'Napiorkowski'},
33         );
34
35 But all of these would cause an error:
36
37         my $instance = MyApp::MyClass->new(name=>'John');
38         my $instance = MyApp::MyClass->new(name=>{first_name=>'John'});
39         my $instance = MyApp::MyClass->new(name=>{first_name=>'John', age=>39});
40
41 Please see the test cases for more examples.
42
43 =head1 DESCRIPTION
44
45 This type library enables structured type constraints. Basically, this is very
46 similar to parameterized constraints that are built into the core Moose types,
47 except that you are allowed to define the container's entire structure.  For
48 example, you could define a parameterized constraint like so:
49
50         subtype HashOfInts, as Hashref[Int];
51
52 which would constraint a value to something like [1,2,3,...] and so one.  A
53 structured constraint like so:
54
55         subtype StringFollowedByInt, as Tuple[Str,Int];
56         
57 would constrain it's value to something like ['hello', 111];
58
59 These structures can be as simple or elaborate as you wish.  You can even
60 combine various structured, parameterized and simple constraints all together:
61
62         subtype crazy, as Tuple[Int, Dict[name=>Str, age=>Int], ArrayRef[Int]];
63         
64 Which would match "[1, {name=>'John', age=>25},[10,11,12]]".
65
66 You should exercise some care as to whether or not your complex structured
67 constraints would be better off contained by a real object as in the following
68 example:
69
70         {
71                 package MyApp::MyStruct;
72                 use Moose;
73                 
74                         has $_ for qw(name age);
75                 
76                 package MyApp::MyClass;
77                 use Moose;
78                 
79                         has person => (isa=>'MyApp::MyStruct');         
80         }
81
82         my $instance = MyApp::MyClass
83                 ->new( person=>MyApp::MyStruct->new(name=>'John', age=>39) );
84         
85 This method may take some additional time to setup but will give you more
86 flexibility.  However, structured constraints are highly compatible with this
87 method, granting some interesting possibilities for coercion.  Try:
88
89         subtype 'MyStruct',
90          as 'MyApp::MyStruct';
91          
92         coerce 'MyStruct',
93          from (Dict[name=>Str, age=>Int]),
94          via {
95                 MyApp::MyStruct->new(%$_);
96          },
97          from (Dict[last_name=>Str, first_name=>Str, dob=>DateTime]),
98          via {
99                 my $name = $_->{first_name} .' '. $_->{last_name};
100                 my $age = DateTime->now - $_->{dob};
101                 MyApp::MyStruct->new(
102                         name=>$name,
103                         age=>$age->years );
104          };
105          
106 You also need to exercise some care when you try to subtype a structured type
107 as in this example:
108
109         subtype Person,
110          as Dict[name=>Str, age=>iIt];
111          
112         subtype FriendlyPerson,
113          as Person[name=>Str, age=>Int, totalFriends=>Int];
114          
115 This will actually work BUT you have to take care the the subtype has a
116 structure that does not contradict the structure of it's parent.  For now the
117 above works, but I will probably clarify how this works at a future point, so
118 it's recommended to avoid (should not realy be needed so much anyway).  For
119 now this is supported in an EXPERIMENTAL way.
120
121 =cut
122
123 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
124         MooseX::Meta::TypeConstraint::Structured->new(
125                 name => "MooseX::Types::Structured::Tuple" ,
126                 parent => find_type_constraint('ArrayRef'),
127                 constraint_generator=> sub {
128                         ## Get the constraints and values to check
129                         my @type_constraints = @{shift @_};            
130                         my @values = @{shift @_};
131                         ## Perform the checking
132                         while(@type_constraints) {
133                                 my $type_constraint = shift @type_constraints;
134                                 if(@values) {
135                                         my $value = shift @values;
136                                         unless($type_constraint->check($value)) {
137                                                 return;
138                                         }                               
139                                 } else {
140                                         return;
141                                 }
142                         }
143                         ## Make sure there are no leftovers.
144                         if(@values) {
145                                 return;
146                         } elsif(@type_constraints) {
147                                 return;
148                         }else {
149                                 return 1;
150                         }
151                 }
152         )
153 );
154         
155 Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
156         MooseX::Meta::TypeConstraint::Structured->new(
157                 name => "MooseX::Types::Structured::Dict",
158                 parent => find_type_constraint('HashRef'),
159                 constraint_generator=> sub {
160                         ## Get the constraints and values to check
161                         my %type_constraints = @{shift @_};            
162                         my %values = %{shift @_};
163                         ## Perform the checking
164                         while(%type_constraints) {
165                                 my($key, $type_constraint) = each %type_constraints;
166                                 delete $type_constraints{$key};
167                                 if(exists $values{$key}) {
168                                         my $value = $values{$key};
169                                         delete $values{$key};
170                                         unless($type_constraint->check($value)) {
171                                                 return;
172                                         }
173                                 } else {
174                                         return;
175                                 }
176                         }
177                         ## Make sure there are no leftovers.
178                         if(%values) {
179                                 return;
180                         } elsif(%type_constraints) {
181                                 return;
182                         }else {
183                                 return 1;
184                         }
185                 },
186         )
187 );
188
189 =head1 SEE ALSO
190
191 The following modules or resources may be of interest.
192
193 L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
194 L<MooseX::Meta::TypeConstraint::Structured>
195
196 =head1 AUTHOR
197
198 John Napiorkowski, C<< <jjnapiork@cpan.org> >>
199
200 =head1 COPYRIGHT & LICENSE
201
202 This program is free software; you can redistribute it and/or modify
203 it under the same terms as Perl itself.
204
205 =cut
206         
207 1;