added soe doc fixes and a new examples section, also changed the way pause should...
[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
59deb858 8our $VERSION = '0.03';
d24da8ec 9our $AUTHORITY = 'cpan:JJNAPIORK';
10
11=head1 NAME
12
af1d00c9 13MooseX::Types::Structured - Structured Type Constraints for Moose
d24da8ec 14
15=head1 SYNOPSIS
16
af1d00c9 17The following is example usage for this module.
6c2f284c 18
af1d00c9 19 package MyApp::MyClass;
6c2f284c 20
af1d00c9 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
6c2f284c 27Then you can instantiate this class with something like:
28
af1d00c9 29 my $instance = MyApp::MyClass->new(
6c2f284c 30 name=>{first_name=>'John', last_name=>'Napiorkowski'},
31 );
d24da8ec 32
6c2f284c 33But all of these would cause an error:
34
af1d00c9 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});
6c2f284c 38
39Please see the test cases for more examples.
d24da8ec 40
41=head1 DESCRIPTION
42
af1d00c9 43A structured type constraint is a standard container L</Moose> type constraint,
44such as an arrayref or hashref, which has been enhanced to allow you to
59deb858 45explicitly name all the allow type constraints inside the structure. The
af1d00c9 46generalized form is:
47
48 TypeConstraint[TypeParameters]
49
50Where TypeParameters is a list of type constraints.
51
59deb858 52This type library enables structured type constraints. It is build on top of the
53L<MooseX::Types> library system, so you should review the documentation for that
54if you are not familiar with it.
55
56=head Comparing Parameterized types to Structured types
57
58Parameterized constraints are built into the core Moose types 'HashRef' and
59'ArrayRef'. Structured types have similar functionality, so their syntax is
60likewise similar. For example, you could define a parameterized constraint like:
6c2f284c 61
af1d00c9 62 subtype HashOfInts,
63 as Hashref[Int];
6c2f284c 64
af1d00c9 65which would constraint a value to something like [1,2,3,...] and so on. On the
59deb858 66other hand, a structured type constraint explicitly names all it's allowed type
af1d00c9 67parameter constraints. For the example:
6c2f284c 68
af1d00c9 69 subtype StringFollowedByInt,
70 as Tuple[Str,Int];
6c2f284c 71
59deb858 72would constrain it's value to something like ['hello', 111] but ['hello', 'world']
73would fail, as well as ['hello', 111, 'world']
6c2f284c 74
75These structures can be as simple or elaborate as you wish. You can even
76combine various structured, parameterized and simple constraints all together:
77
af1d00c9 78 subtype crazy,
79 as Tuple[
80 Int,
81 Dict[name=>Str, age=>Int],
82 ArrayRef[Int]
83 ];
6c2f284c 84
af1d00c9 85Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how
59deb858 86the type parameters can be visually arranged to your liking and to improve the
87clarity of your meaning. You don't need to run then altogether onto a single
88line.
89
90=head2 Alternatives
6c2f284c 91
92You should exercise some care as to whether or not your complex structured
93constraints would be better off contained by a real object as in the following
94example:
95
af1d00c9 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 );
6c2f284c 109
110This method may take some additional time to setup but will give you more
111flexibility. However, structured constraints are highly compatible with this
112method, granting some interesting possibilities for coercion. Try:
113
af1d00c9 114 subtype 'MyStruct',
115 as 'MyApp::MyStruct';
116
117 coerce 'MyStruct',
118 from (Dict[name=>Str, age=>Int]),
59deb858 119 via { MyApp::MyStruct->new(%$_) },
af1d00c9 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};
59deb858 124 MyApp::MyStruct->new( name=>$name, age=>$age->years );
af1d00c9 125 };
a4a88fef 126
16aea7bf 127=head2 Subtyping a structured subtype
128
129You need to exercise some care when you try to subtype a structured type
a4a88fef 130as in this example:
d24da8ec 131
af1d00c9 132 subtype Person,
133 as Dict[name=>Str, age=>iIt];
a4a88fef 134
af1d00c9 135 subtype FriendlyPerson,
136 as Person[name=>Str, age=>Int, totalFriends=>Int];
a4a88fef 137
16aea7bf 138This will actually work BUT you have to take care that the subtype has a
a4a88fef 139structure that does not contradict the structure of it's parent. For now the
59deb858 140above works, but I will clarify the syntax for this at a future point, so
a4a88fef 141it's recommended to avoid (should not realy be needed so much anyway). For
59deb858 142now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and
143patches are welcomed for discussion.
16aea7bf 144
145=head2 Coercions
146
147Coercions currently work for 'one level' deep. That is you can do:
148
af1d00c9 149 subtype Person,
16aea7bf 150 as Dict[name=>Str, age=>Int];
af1d00c9 151
16aea7bf 152 subtype Fullname,
153 as Dict[first=>Str, last=>Str];
af1d00c9 154
155 coerce Person,
156 from BlessedPersonObject,
157 via { +{name=>$_->name, age=>$_->age} },
158 from ArrayRef,
159 via { +{name=>$_->[0], age=>$_->[1] },
16aea7bf 160 from Dict[fullname=>Fullname, dob=>DateTime],
161 via {
af1d00c9 162 my $age = $_->dob - DateTime->now;
163 +{
164 name=> $_->{fullname}->{first} .' '. $_->{fullname}->{last},
165 age=>$age->years
166 }
16aea7bf 167 };
168
169And that should just work as expected. However, if there are any 'inner'
170coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion
171won't currently get activated.
172
173Please see the test '07-coerce.t' for a more detailed example.
174
175=head1 TYPE CONSTRAINTS
176
177This type library defines the following constraints.
178
179=head2 Tuple[@constraints]
180
181This defines an arrayref based constraint which allows you to validate a specific
182list of constraints. For example:
183
af1d00c9 184 Tuple[Int,Str]; ## Validates [1,'hello']
185 Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2]
16aea7bf 186
187=head2 Dict [%constraints]
188
189This defines a hashref based constraint which allowed you to validate a specific
190hashref. For example:
191
af1d00c9 192 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
d24da8ec 193
59deb858 194=head1 EXAMPLES
195
196Here are some additional example usage for structured types. All examples can
197be found also in the 't/examples.t' test. Your contributions are also welcomed.
198
199=head2 Normalize a HashRef
200
201You need a hashref to conform to a canonical structure but are required accept a
202bunch of different incoming structures. You can normalize using the Dict type
203constraint and coercions. This example also shows structured types mixed which
204other 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
a30fa891 233=cut
234
67a8bc04 235Moose::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;
a30fa891 246 if(@values) {
67a8bc04 247 my $value = shift @values;
248 unless($type_constraint->check($value)) {
249 return;
250 }
251 } else {
a30fa891 252 return;
a30fa891 253 }
254 }
67a8bc04 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
267Moose::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)) {
a30fa891 283 return;
284 }
67a8bc04 285 } else {
a30fa891 286 return;
a30fa891 287 }
67a8bc04 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);
d24da8ec 300
301=head1 SEE ALSO
302
303The following modules or resources may be of interest.
304
a30fa891 305L<Moose>, L<MooseX::TypeLibrary>, L<Moose::Meta::TypeConstraint>,
306L<MooseX::Meta::TypeConstraint::Structured>
d24da8ec 307
16aea7bf 308=head1 TODO
309
310Need to clarify deep coercions, need to clarify subtypes of subtypes.
311
d24da8ec 312=head1 AUTHOR
313
314John Napiorkowski, C<< <jjnapiork@cpan.org> >>
315
316=head1 COPYRIGHT & LICENSE
317
318This program is free software; you can redistribute it and/or modify
319it under the same terms as Perl itself.
320
321=cut
67a8bc04 322
3231;