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