more doc tweaks and test cases
[gitmo/MooseX-Dependent.git] / lib / MooseX / Types / Parameterizable.pm
CommitLineData
ca01e833 1package MooseX::Types::Parameterizable;
88f7dcd2 2
3use 5.008;
4
c9ecd506 5our $VERSION = '0.02';
88f7dcd2 6$VERSION = eval $VERSION;
a018b5bb 7
3cfd35fd 8use Moose::Util::TypeConstraints;
ca01e833 9use MooseX::Meta::TypeConstraint::Parameterizable;
88f7dcd2 10use MooseX::Types -declare => [qw(Parameterizable)];
3cfd35fd 11
a018b5bb 12=head1 NAME
13
ca01e833 14MooseX::Types::Parameterizable - Create your own Parameterizable Types.
a018b5bb 15
16=head1 SYNOPSIS
17
1fa27116 18The follow is example usage.
3cfd35fd 19
afdaaf52 20 package Test::MooseX::Types::Parameterizable::Synopsis;
21
1fa27116 22 use Moose;
ca01e833 23 use MooseX::Types::Parameterizable qw(Parameterizable);
afdaaf52 24 use MooseX::Types::Moose qw(Str Int ArrayRef);
1fa27116 25 use MooseX::Types -declare=>[qw(Varchar)];
26
afdaaf52 27 ## Create a type constraint that is a string but parameterizes an integer
28 ## that is used as a maximum length constraint on that string, similar to
c9ecd506 29 ## a SQL Varchar database type.
afdaaf52 30
1fa27116 31 subtype Varchar,
32 as Parameterizable[Str,Int],
33 where {
34 my($string, $int) = @_;
35 $int >= length($string) ? 1:0;
36 },
afdaaf52 37 message { "'$_' is too long" };
38
80b1af4c 39 ## Coerce an ArrayRef to a string via concatenation.
40
afdaaf52 41 coerce Varchar,
42 from ArrayRef,
43 via {
44 my ($arrayref, $int) = @_;
45 join('', @$arrayref);
1fa27116 46 };
47
afdaaf52 48 has 'varchar_five' => (isa=>Varchar[5], is=>'ro', coerce=>1);
49 has 'varchar_ten' => (isa=>Varchar[10], is=>'ro');
1fa27116 50
afdaaf52 51 ## Object created since attributes are valid
1fa27116 52 my $object1 = __PACKAGE__->new(
53 varchar_five => '1234',
54 varchar_ten => '123456789',
55 );
56
afdaaf52 57 ## Dies with an invalid constraint for 'varchar_five'
1fa27116 58 my $object2 = __PACKAGE__->new(
c9ecd506 59 varchar_five => '12345678', ## too long!
1fa27116 60 varchar_ten => '123456789',
61 );
62
afdaaf52 63 ## varchar_five coerces as expected
64 my $object3 = __PACKAGE__->new(
c9ecd506 65 varchar_five => [qw/aa bb/], ## coerces to "aabb"
afdaaf52 66 varchar_ten => '123456789',
67 );
68
1fa27116 69See t/05-pod-examples.t for runnable versions of all POD code
70
a018b5bb 71=head1 DESCRIPTION
72
c9ecd506 73A L<MooseX::Types> library for creating parameterizable types. A parameterizable
74type constraint for all intents and uses is a subclass of a parent type, but
75adds additional type parameters which are available to constraint callbacks
76(such as inside the 'where' clause of a type constraint definition) or in the
77coercions.
5964b3ca 78
c9ecd506 79If you have L<Moose> experience, you probably are familiar with the builtin
80parameterizable type constraints 'ArrayRef' and 'HashRef'. This type constraint
81lets you generate your own versions of parameterized constraints that work
82similarly. See L<Moose::Util::TypeConstraints> for more.
83
84Using this type constraint, you can generate new type constraints that have
85additional runtime advice, such as being able to specify maximum and minimum
86values for an Int (integer) type constraint:
5964b3ca 87
9be9bf16 88 subtype Range,
d1cfb043 89 as Dict[max=>Int, min=>Int],
90 where {
91 my ($range) = @_;
92 return $range->{max} > $range->{min};
93 };
9be9bf16 94
95 subtype RangedInt,
d1cfb043 96 as Parameterizable[Int, Range],
97 where {
98 my ($value, $range) = @_;
99 return ($value >= $range->{min} &&
100 $value <= $range->{max});
101 };
102
9be9bf16 103 RangedInt([{min=>10,max=>100}])->check(50); ## OK
104 RangedInt([{min=>50, max=>75}])->check(99); ## Not OK, 99 exceeds max
c9ecd506 105
106The type parameter must be valid against the type constraint given. If you pass
107an invalid value this throws a hard Moose exception. You'll need to capture it
108in an eval or related exception catching system (see L<TryCatch> or <Try::Tiny>.)
109For example the following would throw a hard error (and not just return false)
6c67366e 110
9be9bf16 111 RangedInt([{min=>99, max=>10}])->check(10); ## Not OK, not a valid Range!
6c67366e 112
113If you can't accept a hard exception here, you'll need to test the constraining
114values first, as in:
115
9be9bf16 116 my $range = {min=>99, max=>10};
117 if(my $err = Range->validate($range)) {
d1cfb043 118 ## Handle #$err
9be9bf16 119 } else {
d1cfb043 120 RangedInt($range)->check(99);
9be9bf16 121 }
122
88f7dcd2 123Please note that for ArrayRef or HashRef parameterizable type constraints, as in the
5964b3ca 124example above, as a convenience we automatically ref the incoming type
125parameters, so that the above could also be written as:
126
9be9bf16 127 RangedInt([min=>10,max=>100])->check(50); ## OK
128 RangedInt([min=>50, max=>75])->check(99); ## Not OK, 99 exceeds max
129 RangedInt([min=>99, max=>10])->check(10); ## Exception, not a valid Range!
5964b3ca 130
131This is the preferred syntax, as it improve readability and adds to the
132conciseness of your type constraint declarations. An exception wil be thrown if
133your type parameters don't match the required reference type.
134
80b1af4c 135Also note that if you 'chain' parameterization results with a method call like:
6c67366e 136
9be9bf16 137 TypeConstraint([$ob])->method;
138
6c67366e 139You need to have the "(...)" around the ArrayRef in the Type Constraint
80b1af4c 140parameters. You can skip the wrapping parenthesis in the most common cases,
141such as when you use the type constraint in the options section of a L<Moose>
142attribute declaration, or when defining type libraries.
6c67366e 143
88f7dcd2 144==head2 Subtyping a Parameterizable type constraints
5964b3ca 145
88f7dcd2 146When subclassing a parameterizable type you must be careful to match either the
5964b3ca 147required type parameter type constraint, or if re-parameterizing, the new
148type constraints are a subtype of the parent. For example:
149
9be9bf16 150 subtype RangedInt,
d1cfb043 151 as Parameterizable[Int, Range],
152 where {
153 my ($value, $range) = @_;
154 return ($value >= $range->{min} &&
155 $value =< $range->{max});
156 };
5964b3ca 157
158Example subtype with additional constraints:
159
9be9bf16 160 subtype PositiveRangedInt,
d1cfb043 161 as RangedInt,
162 where {
163 shift >= 0;
164 };
165
80b1af4c 166In this case you'd now have a parameterizable type constraint called which
167would work like:
168
169 Test::More::ok PositiveRangedInt([{min=>-10, max=>75}])->check(5);
170 Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5);
171
172Of course the above is somewhat counter-intuitive to the reader, since we have
173defined our 'RangedInt' in such as way as to let you declare negative ranges.
174For the moment each type constraint rule is apply without knowledge of any
175other rule, nor can a rule 'inform' existing rules. This is a limitation of
176the current system. However, you could instead do the following:
177
5964b3ca 178
9be9bf16 179 ## Subtype of Int for positive numbers
180 subtype PositiveInt,
d1cfb043 181 as Int,
182 where {
183 my ($value, $range) = @_;
184 return $value >= 0;
185 };
9be9bf16 186
187 ## subtype Range to re-parameterize Range with subtypes
188 subtype PositiveRange,
d1cfb043 189 as Range[max=>PositiveInt, min=>PositiveInt];
9be9bf16 190
191 ## create subtype via reparameterizing
192 subtype PositiveRangedInt,
d1cfb043 193 as RangedInt[PositiveRange];
5964b3ca 194
80b1af4c 195This would constrain values in the same way as the previous type constraint but
196have the bonus that you'd throw a hard exception if you try to use an incorrect
197range:
198
199 Test::More::ok PositiveRangedInt([{min=>10, max=>75}])->check(15); ## OK
200 Test::More::ok !PositiveRangedInt([{min=>-10, max=>75}])->check(-5); ## Dies
201
88f7dcd2 202Notice how re-parameterizing the parameterizable type 'RangedInt' works slightly
6c67366e 203differently from re-parameterizing 'PositiveRange' Although it initially takes
88f7dcd2 204two type constraint values to declare a parameterizable type, should you wish to
5964b3ca 205later re-parameterize it, you only use a subtype of the second type parameter
88f7dcd2 206(the parameterizable type constraint) since the first type constraint sets the parent
207type for the parameterizable type. In other words, given the example above, a type
208constraint of 'RangedInt' would have a parent of 'Int', not 'Parameterizable' and for
5964b3ca 209all intends and uses you could stick it wherever you'd need an Int.
210
9be9bf16 211 subtype NameAge,
d1cfb043 212 as Tuple[Str, Int];
9be9bf16 213
d1cfb043 214 ## re-parameterized subtypes of NameAge containing a Parameterizable Int
9be9bf16 215 subtype NameBetween18and35Age,
d1cfb043 216 as NameAge[
217 Str,
218 PositiveRangedInt[min=>18,max=>35],
219 ];
5964b3ca 220
88f7dcd2 221One caveat is that you can't stick an unparameterized parameterizable type inside a
5964b3ca 222structure, such as L<MooseX::Types::Structured> since that would require the
88f7dcd2 223ability to convert a 'containing' type constraint into a parameterizable type, which
5964b3ca 224is a capacity we current don't have.
9be9bf16 225
3cfd35fd 226=head2 Coercions
a018b5bb 227
88f7dcd2 228Parameterizable types have some limited support for coercions. Several things must
26cf337e 229be kept in mind. The first is that the coercion targets the type constraint
88f7dcd2 230which is being made parameterizable, Not the parameterizable type. So for example if you
231create a Parameterizable type like:
26cf337e 232
9be9bf16 233 subtype RequiredAgeInYears,
234 as Int;
26cf337e 235
9be9bf16 236 subtype PersonOverAge,
237 as Parameterizable[Person, RequiredAgeInYears]
238 where {
d1cfb043 239 my ($person, $required_years_old) = @_;
240 return $person->years_old > $required_years_old;
9be9bf16 241 }
26cf337e 242
243This would validate the following:
9be9bf16 244
245 my $person = Person->new(age=>35);
246 PersonOverAge([18])->check($person);
247
26cf337e 248You can then apply the following coercion
249
9be9bf16 250 coerce PersonOverAge,
251 from Dict[age=>int],
252 via {Person->new(%$_)},
253 from Int,
254 via {Person->new(age=>$_)};
255
26cf337e 256This coercion would then apply to all the following:
257
9be9bf16 258 PersonOverAge([18])->check(30); ## via the Int coercion
259 PersonOverAge([18])->check({age=>50}); ## via the Dict coercion
26cf337e 260
88f7dcd2 261However, you are not allowed to place coercions on parameterizable types that have
26cf337e 262had their constraining value filled, nor subtypes of such. For example:
263
9be9bf16 264 coerce PersonOverAge[18],
265 from DateTime,
266 via {$_->years};
267
26cf337e 268That would generate a hard exception. This is a limitation for now until I can
269devise a smarter way to cache the generated type constraints. However, I doubt
270it will be a significant limitation, since the general use case is supported.
271
272Lastly, the constraining value is available in the coercion in much the same way
273it is available to the constraint.
274
9be9bf16 275 ## Create a type constraint where a Person must be in the set
276 subtype PersonInSet,
d1cfb043 277 as Parameterizable[Person, PersonSet],
278 where {
279 my ($person, $person_set) = @_;
280 $person_set->find($person);
281 }
9be9bf16 282
283 coerce PersonInSet,
d1cfb043 284 from HashRef,
285 via {
286 my ($hashref, $person_set) = @_;
287 return $person_set->create($hash_ref);
288 };
a018b5bb 289
3cfd35fd 290=head2 Recursion
a018b5bb 291
c9ecd506 292 TBD - Need more tests.
a018b5bb 293
3cfd35fd 294=head1 TYPE CONSTRAINTS
a018b5bb 295
3cfd35fd 296This type library defines the following constraints.
a018b5bb 297
88f7dcd2 298=head2 Parameterizable[ParentTypeConstraint, ParameterizableValueTypeConstraint]
a018b5bb 299
5964b3ca 300Create a subtype of ParentTypeConstraint with a dependency on a value that can
88f7dcd2 301pass the ParameterizableValueTypeConstraint. If ParameterizableValueTypeConstraint is empty
5964b3ca 302we default to the 'Any' type constraint (see L<Moose::Util::TypeConstraints>).
9b6d2e22 303
5964b3ca 304This creates a type constraint which must be further parameterized at later time
305before it can be used to ->check or ->validate a value. Attempting to do so
306will cause an exception.
a018b5bb 307
308=cut
309
3cfd35fd 310Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
ca01e833 311 MooseX::Meta::TypeConstraint::Parameterizable->new(
312 name => 'MooseX::Types::Parameterizable::Parameterizable',
a588ee00 313 parent => find_type_constraint('Any'),
d1cfb043 314 constraint => sub {1},
9b6d2e22 315 )
3cfd35fd 316);
9b6d2e22 317
3cfd35fd 318=head1 AUTHOR
a018b5bb 319
3cfd35fd 320John Napiorkowski, C<< <jjnapiork@cpan.org> >>
321
322=head1 COPYRIGHT & LICENSE
a018b5bb 323
324This program is free software; you can redistribute it and/or modify
3cfd35fd 325it under the same terms as Perl itself.
a018b5bb 326
327=cut
9b6d2e22 328
a018b5bb 3291;