adding some stuff to autobuild the README files
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Types / Structured.pm
CommitLineData
d24da8ec 1package MooseX::Types::Structured;
2
6c2f284c 3use Moose;
4use Moose::Util::TypeConstraints;
a30fa891 5use MooseX::Meta::TypeConstraint::Structured;
6use MooseX::Types -declare => [qw(Dict Tuple)];
011bacc6 7
6c2f284c 8
d24da8ec 9our $VERSION = '0.01';
10our $AUTHORITY = 'cpan:JJNAPIORK';
11
12=head1 NAME
13
14MooseX::Types::Structured; Structured Type Constraints for Moose
15
16=head1 SYNOPSIS
17
6c2f284c 18The following is example usage for this module. You can define a class that has
19an 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
29Then you can instantiate this class with something like:
30
31 my $instance = MyApp::MyClass->new(
32 name=>{first_name=>'John', last_name=>'Napiorkowski'},
33 );
d24da8ec 34
6c2f284c 35But 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
41Please see the test cases for more examples.
d24da8ec 42
43=head1 DESCRIPTION
44
6c2f284c 45This type library enables structured type constraints. Basically, this is very
46similar to parameterized constraints that are built into the core Moose types,
47except that you are allowed to define the container's entire structure. For
48example, you could define a parameterized constraint like so:
49
50 subtype HashOfInts, as Hashref[Int];
51
52which would constraint a value to something like [1,2,3,...] and so one. A
53structured constraint like so:
54
55 subtype StringFollowedByInt, as Tuple[Str,Int];
56
57would constrain it's value to something like ['hello', 111];
58
59These structures can be as simple or elaborate as you wish. You can even
60combine various structured, parameterized and simple constraints all together:
61
62 subtype crazy, as Tuple[Int, Dict[name=>Str, age=>Int], ArrayRef[Int]];
63
64Which would match "[1, {name=>'John', age=>25},[10,11,12]]".
65
66You should exercise some care as to whether or not your complex structured
67constraints would be better off contained by a real object as in the following
68example:
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
85This method may take some additional time to setup but will give you more
86flexibility. However, structured constraints are highly compatible with this
87method, 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 {
f9468aac 99 my $name = $_->{first_name} .' '. $_->{last_name};
100 my $age = DateTime->now - $_->{dob};
6c2f284c 101 MyApp::MyStruct->new(
102 name=>$name,
103 age=>$age->years );
104 };
a4a88fef 105
16aea7bf 106=head2 Subtyping a structured subtype
107
108You need to exercise some care when you try to subtype a structured type
a4a88fef 109as in this example:
d24da8ec 110
a4a88fef 111 subtype Person,
112 as Dict[name=>Str, age=>iIt];
113
114 subtype FriendlyPerson,
115 as Person[name=>Str, age=>Int, totalFriends=>Int];
116
16aea7bf 117This will actually work BUT you have to take care that the subtype has a
a4a88fef 118structure that does not contradict the structure of it's parent. For now the
119above works, but I will probably clarify how this works at a future point, so
120it's recommended to avoid (should not realy be needed so much anyway). For
16aea7bf 121now this is supported in an EXPERIMENTAL way. In the future we will probably
122clarify how to augment existing structured types.
123
124=head2 Coercions
125
126Coercions currently work for 'one level' deep. That is you can do:
127
128 subtype Person,
129 as Dict[name=>Str, age=>Int];
130
131 subtype Fullname,
132 as Dict[first=>Str, last=>Str];
133
134 coerce Person,
135 from BlessedPersonObject,
136 via { +{name=>$_->name, age=>$_->age} },
137 from ArrayRef,
138 via { +{name=>$_->[0], age=>$_->[1] },
139 from Dict[fullname=>Fullname, dob=>DateTime],
140 via {
141 my $age = $_->dob - DateTime->now;
142 +{
143 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
144 age=>$age->years
145 }
146 };
147
148And that should just work as expected. However, if there are any 'inner'
149coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
150won't currently get activated.
151
152Please see the test '07-coerce.t' for a more detailed example.
153
154=head1 TYPE CONSTRAINTS
155
156This type library defines the following constraints.
157
158=head2 Tuple[@constraints]
159
160This defines an arrayref based constraint which allows you to validate a specific
161list of constraints. For example:
162
163 Tuple[Int,Str]; ## Validates [1,'hello']
164 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
165
166=head2 Dict [%constraints]
167
168This defines a hashref based constraint which allowed you to validate a specific
169hashref. For example:
170
171 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
d24da8ec 172
a30fa891 173=cut
174
67a8bc04 175Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
176 MooseX::Meta::TypeConstraint::Structured->new(
177 name => "MooseX::Types::Structured::Tuple" ,
178 parent => find_type_constraint('ArrayRef'),
179 constraint_generator=> sub {
180 ## Get the constraints and values to check
181 my @type_constraints = @{shift @_};
182 my @values = @{shift @_};
183 ## Perform the checking
184 while(@type_constraints) {
185 my $type_constraint = shift @type_constraints;
a30fa891 186 if(@values) {
67a8bc04 187 my $value = shift @values;
188 unless($type_constraint->check($value)) {
189 return;
190 }
191 } else {
a30fa891 192 return;
a30fa891 193 }
194 }
67a8bc04 195 ## Make sure there are no leftovers.
196 if(@values) {
197 return;
198 } elsif(@type_constraints) {
199 return;
200 }else {
201 return 1;
202 }
203 }
204 )
205);
206
207Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
208 MooseX::Meta::TypeConstraint::Structured->new(
209 name => "MooseX::Types::Structured::Dict",
210 parent => find_type_constraint('HashRef'),
211 constraint_generator=> sub {
212 ## Get the constraints and values to check
213 my %type_constraints = @{shift @_};
214 my %values = %{shift @_};
215 ## Perform the checking
216 while(%type_constraints) {
217 my($key, $type_constraint) = each %type_constraints;
218 delete $type_constraints{$key};
219 if(exists $values{$key}) {
220 my $value = $values{$key};
221 delete $values{$key};
222 unless($type_constraint->check($value)) {
a30fa891 223 return;
16aea7bf 224 #if ($type_constraint->has_coercion) {
225 # my $temp = $type_constraint->coerce($value);
226 # use Data::Dump qw/dump/; warn dump $value, $temp;
227 # unless($type_constraint->check($temp)) {
228 # return;
229 # }
230 #} else {
231 # return;
232 #}
a30fa891 233 }
67a8bc04 234 } else {
a30fa891 235 return;
a30fa891 236 }
67a8bc04 237 }
238 ## Make sure there are no leftovers.
239 if(%values) {
240 return;
241 } elsif(%type_constraints) {
242 return;
243 }else {
244 return 1;
245 }
246 },
247 )
248);
d24da8ec 249
250=head1 SEE ALSO
251
252The following modules or resources may be of interest.
253
a30fa891 254L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
255L<MooseX::Meta::TypeConstraint::Structured>
d24da8ec 256
16aea7bf 257=head1 TODO
258
259Need to clarify deep coercions, need to clarify subtypes of subtypes.
260
d24da8ec 261=head1 AUTHOR
262
263John Napiorkowski, C<< <jjnapiork@cpan.org> >>
264
265=head1 COPYRIGHT & LICENSE
266
267This program is free software; you can redistribute it and/or modify
268it under the same terms as Perl itself.
269
270=cut
67a8bc04 271
2721;