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