Commit | Line | Data |
d24da8ec |
1 | package MooseX::Types::Structured; |
2 | |
98336987 |
3 | use 5.008; |
c116e19a |
4 | |
6c2f284c |
5 | use Moose::Util::TypeConstraints; |
a30fa891 |
6 | use MooseX::Meta::TypeConstraint::Structured; |
e327145a |
7 | use MooseX::Types -declare => [qw(Dict Tuple Optional)]; |
c116e19a |
8 | use Sub::Exporter -setup => { exports => [ qw(Dict Tuple Optional slurpy) ] }; |
011bacc6 |
9 | |
d54624ea |
10 | our $VERSION = '0.08'; |
d24da8ec |
11 | our $AUTHORITY = 'cpan:JJNAPIORK'; |
12 | |
13 | =head1 NAME |
14 | |
af1d00c9 |
15 | MooseX::Types::Structured - Structured Type Constraints for Moose |
d24da8ec |
16 | |
17 | =head1 SYNOPSIS |
18 | |
af1d00c9 |
19 | The following is example usage for this module. |
6c2f284c |
20 | |
07a8693b |
21 | package Person; |
6c2f284c |
22 | |
af1d00c9 |
23 | use Moose; |
07a8693b |
24 | use MooseX::Types::Moose qw(Str Int HashRef); |
25 | use MooseX::Types::Structured qw(Dict Tuple Optional); |
190a34eb |
26 | |
27 | ## A name has a first and last part, but middle names are not required |
28 | has name => ( |
29 | isa=>Dict[ |
07a8693b |
30 | first => Str, |
31 | last => Str, |
32 | middle => Optional[Str], |
190a34eb |
33 | ], |
34 | ); |
07a8693b |
35 | |
36 | ## description is a string field followed by a HashRef of tagged data. |
37 | has description => ( |
38 | isa=>Tuple[ |
39 | Str, |
40 | Optional[HashRef], |
41 | ], |
42 | ); |
af1d00c9 |
43 | |
6c2f284c |
44 | Then you can instantiate this class with something like: |
45 | |
07a8693b |
46 | my $john = Person->new( |
190a34eb |
47 | name => { |
07a8693b |
48 | first => 'John', |
49 | middle => 'James' |
50 | last => 'Napiorkowski', |
190a34eb |
51 | }, |
07a8693b |
52 | description => [ |
53 | 'A cool guy who loves Perl and Moose.', { |
54 | married_to => 'Vanessa Li', |
55 | born_in => 'USA', |
56 | }; |
57 | ] |
190a34eb |
58 | ); |
22727dd5 |
59 | |
60 | Or with: |
61 | |
07a8693b |
62 | my $vanessa = Person->new( |
d87e8b74 |
63 | name => { |
07a8693b |
64 | first => 'Vanessa', |
65 | last => 'Li' |
d87e8b74 |
66 | }, |
07a8693b |
67 | description => ['A great student!'], |
d87e8b74 |
68 | ); |
d24da8ec |
69 | |
d87e8b74 |
70 | But all of these would cause a constraint error for the 'name' attribute: |
6c2f284c |
71 | |
07a8693b |
72 | ## Value for 'name' not a HashRef |
73 | Person->new( name => 'John' ); |
74 | |
75 | ## Value for 'name' has incorrect hash key and missing required keys |
76 | Person->new( name => { |
77 | first_name => 'John' |
78 | }); |
79 | |
80 | ## Also incorrect keys |
81 | Person->new( name => { |
82 | first_name => 'John', |
83 | age => 39, |
84 | }); |
85 | |
86 | ## key 'middle' incorrect type, should be a Str not a ArrayRef |
87 | Person->new( name => { |
88 | first => 'Vanessa', |
89 | middle => [1,2], |
90 | last => 'Li', |
91 | }); |
92 | |
93 | And these would cause a constraint error for the 'description' attribute: |
94 | |
95 | ## Should be an ArrayRef |
96 | Person->new( description => 'Hello I am a String' ); |
190a34eb |
97 | |
07a8693b |
98 | ## First element must be a string not a HashRef. |
99 | Person->new (description => [{ |
100 | tag1 => 'value1', |
101 | tag2 => 'value2' |
102 | }]); |
103 | |
6c2f284c |
104 | Please see the test cases for more examples. |
d24da8ec |
105 | |
106 | =head1 DESCRIPTION |
107 | |
22727dd5 |
108 | A structured type constraint is a standard container L<Moose> type constraint, |
07a8693b |
109 | such as an ArrayRef or HashRef, which has been enhanced to allow you to |
110 | explicitly name all the allowed type constraints inside the structure. The |
af1d00c9 |
111 | generalized form is: |
112 | |
07a8693b |
113 | TypeConstraint[@TypeParameters or %TypeParameters] |
af1d00c9 |
114 | |
c6fece89 |
115 | Where 'TypeParameters' is an array reference or hash references of |
116 | L<Moose::Meta::TypeConstraint> objects. |
af1d00c9 |
117 | |
22727dd5 |
118 | This type library enables structured type constraints. It is built on top of the |
59deb858 |
119 | L<MooseX::Types> library system, so you should review the documentation for that |
120 | if you are not familiar with it. |
121 | |
5632ada1 |
122 | =head2 Comparing Parameterized types to Structured types |
59deb858 |
123 | |
22727dd5 |
124 | Parameterized constraints are built into core Moose and you are probably already |
07a8693b |
125 | familar with the type constraints 'HashRef' and 'ArrayRef'. Structured types |
126 | have similar functionality, so their syntax is likewise similar. For example, |
22727dd5 |
127 | you could define a parameterized constraint like: |
6c2f284c |
128 | |
d87e8b74 |
129 | subtype ArrayOfInts, |
130 | as Arrayref[Int]; |
6c2f284c |
131 | |
c6fece89 |
132 | which would constrain a value to something like [1,2,3,...] and so on. On the |
22727dd5 |
133 | other hand, a structured type constraint explicitly names all it's allowed |
134 | 'internal' type parameter constraints. For the example: |
6c2f284c |
135 | |
af1d00c9 |
136 | subtype StringFollowedByInt, |
137 | as Tuple[Str,Int]; |
6c2f284c |
138 | |
c6fece89 |
139 | would constrain it's value to things like ['hello', 111] but ['hello', 'world'] |
22727dd5 |
140 | would fail, as well as ['hello', 111, 'world'] and so on. Here's another |
141 | example: |
142 | |
143 | subtype StringIntOptionalHashRef, |
144 | as Tuple[ |
145 | Str, Int, |
146 | Optional[HashRef] |
147 | ]; |
148 | |
149 | This defines a type constraint that validates values like: |
150 | |
07a8693b |
151 | ['Hello', 100, {key1 => 'value1', key2 => 'value2'}]; |
22727dd5 |
152 | ['World', 200]; |
153 | |
154 | Notice that the last type constraint in the structure is optional. This is |
155 | enabled via the helper Optional type constraint, which is a variation of the |
07a8693b |
156 | core Moose type constraint 'Maybe'. The main difference is that Optional type |
c6fece89 |
157 | constraints are required to validate if they exist, while 'Maybe' permits |
158 | undefined values. So the following example would not validate: |
22727dd5 |
159 | |
160 | StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]); |
161 | |
162 | Please note the subtle difference between undefined and null. If you wish to |
07a8693b |
163 | allow both null and undefined, you should use the core Moose 'Maybe' type |
164 | constraint instead: |
22727dd5 |
165 | |
c6fece89 |
166 | use MooseX::Types -declare [qw(StringIntMaybeHashRef)]; |
22727dd5 |
167 | use MooseX::Types::Moose qw(Maybe); |
168 | use MooseX::Types::Structured qw(Tuple); |
169 | |
c6fece89 |
170 | subtype StringIntMaybeHashRef, |
22727dd5 |
171 | as Tuple[ |
172 | Str, Int, Maybe[HashRef] |
173 | ]; |
174 | |
175 | This would validate the following: |
176 | |
07a8693b |
177 | ['Hello', 100, {key1 => 'value1', key2 => 'value2'}]; |
22727dd5 |
178 | ['World', 200, undef]; |
179 | ['World', 200]; |
d87e8b74 |
180 | |
c6fece89 |
181 | Structured constraints are not limited to arrays. You can define a structure |
182 | against a HashRef with 'Dict' as in this example: |
d87e8b74 |
183 | |
184 | subtype FirstNameLastName, |
07a8693b |
185 | as Dict[ |
186 | firstname => Str, |
187 | lastname => Str, |
188 | ]; |
d87e8b74 |
189 | |
07a8693b |
190 | This would constrain a HashRef to something like: |
d87e8b74 |
191 | |
07a8693b |
192 | {firstname => 'Christopher', lastname= > 'Parsons'}; |
d87e8b74 |
193 | |
194 | but all the following would fail validation: |
195 | |
07a8693b |
196 | ## Incorrect keys |
197 | {first => 'Christopher', last => 'Parsons'}; |
198 | |
199 | ## Too many keys |
200 | {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'}; |
201 | |
202 | ## Not a HashRef |
203 | ['Christopher', 'Christopher']; |
6c2f284c |
204 | |
205 | These structures can be as simple or elaborate as you wish. You can even |
206 | combine various structured, parameterized and simple constraints all together: |
207 | |
c6fece89 |
208 | subtype Crazy, |
af1d00c9 |
209 | as Tuple[ |
210 | Int, |
211 | Dict[name=>Str, age=>Int], |
212 | ArrayRef[Int] |
213 | ]; |
6c2f284c |
214 | |
af1d00c9 |
215 | Which would match "[1, {name=>'John', age=>25},[10,11,12]]". Please notice how |
59deb858 |
216 | the type parameters can be visually arranged to your liking and to improve the |
217 | clarity of your meaning. You don't need to run then altogether onto a single |
218 | line. |
219 | |
220 | =head2 Alternatives |
6c2f284c |
221 | |
222 | You should exercise some care as to whether or not your complex structured |
223 | constraints would be better off contained by a real object as in the following |
224 | example: |
225 | |
af1d00c9 |
226 | package MyApp::MyStruct; |
227 | use Moose; |
228 | |
07a8693b |
229 | ## lazy way to make a bunch of attributes |
22727dd5 |
230 | has $_ for qw(full_name age_in_years); |
af1d00c9 |
231 | |
232 | package MyApp::MyClass; |
233 | use Moose; |
234 | |
07a8693b |
235 | has person => (isa => 'MyApp::MyStruct'); |
af1d00c9 |
236 | |
237 | my $instance = MyApp::MyClass->new( |
07a8693b |
238 | person=>MyApp::MyStruct->new( |
239 | full_name => 'John', |
c6fece89 |
240 | age_in_years => 39, |
07a8693b |
241 | ), |
af1d00c9 |
242 | ); |
6c2f284c |
243 | |
244 | This method may take some additional time to setup but will give you more |
245 | flexibility. However, structured constraints are highly compatible with this |
246 | method, granting some interesting possibilities for coercion. Try: |
247 | |
07a8693b |
248 | package MyApp::MyClass; |
249 | |
250 | use Moose; |
22727dd5 |
251 | use MyApp::MyStruct; |
07a8693b |
252 | |
253 | ## It's recommended your type declarations live in a separate class in order |
254 | ## to promote reusability and clarity. Inlined here for brevity. |
255 | |
22727dd5 |
256 | use MooseX::Types::DateTime qw(DateTime); |
257 | use MooseX::Types -declare [qw(MyStruct)]; |
258 | use MooseX::Types::Moose qw(Str Int); |
259 | use MooseX::Types::Structured qw(Dict); |
260 | |
261 | ## Use class_type to create an ISA type constraint if your object doesn't |
262 | ## inherit from Moose::Object. |
263 | class_type 'MyApp::MyStruct'; |
264 | |
265 | ## Just a shorter version really. |
266 | subtype MyStruct, |
af1d00c9 |
267 | as 'MyApp::MyStruct'; |
268 | |
22727dd5 |
269 | ## Add the coercions. |
270 | coerce MyStruct, |
271 | from Dict[ |
272 | full_name=>Str, |
273 | age_in_years=>Int |
274 | ], via { |
275 | MyApp::MyStruct->new(%$_); |
276 | }, |
277 | from Dict[ |
278 | lastname=>Str, |
279 | firstname=>Str, |
280 | dob=>DateTime |
281 | ], via { |
282 | my $name = $_->{firstname} .' '. $_->{lastname}; |
af1d00c9 |
283 | my $age = DateTime->now - $_->{dob}; |
07a8693b |
284 | |
285 | MyApp::MyStruct->new( |
286 | full_name=>$name, |
287 | age_in_years=>$age->years, |
288 | ); |
af1d00c9 |
289 | }; |
07a8693b |
290 | |
291 | has person => (isa=>MyStruct); |
292 | |
293 | This would allow you to instantiate with something like: |
294 | |
295 | my $obj = MyApp::MyClass->new( person => { |
296 | full_name=>'John Napiorkowski', |
297 | age_in_years=>39, |
298 | }); |
299 | |
300 | Or even: |
301 | |
302 | my $obj = MyApp::MyClass->new( person => { |
303 | lastname=>'John', |
304 | firstname=>'Napiorkowski', |
305 | dob=>DateTime->new(year=>1969), |
306 | }); |
22727dd5 |
307 | |
308 | If you are not familiar with how coercions work, check out the L<Moose> cookbook |
309 | entry L<Moose::Cookbook::Recipe5> for an explanation. The section L</Coercions> |
310 | has additional examples and discussion. |
311 | |
312 | =head2 Subtyping a Structured type constraint |
16aea7bf |
313 | |
07a8693b |
314 | You need to exercise some care when you try to subtype a structured type as in |
315 | this example: |
d24da8ec |
316 | |
af1d00c9 |
317 | subtype Person, |
07a8693b |
318 | as Dict[name => Str]; |
a4a88fef |
319 | |
af1d00c9 |
320 | subtype FriendlyPerson, |
07a8693b |
321 | as Person[ |
322 | name => Str, |
323 | total_friends => Int, |
324 | ]; |
a4a88fef |
325 | |
16aea7bf |
326 | This will actually work BUT you have to take care that the subtype has a |
a4a88fef |
327 | structure that does not contradict the structure of it's parent. For now the |
59deb858 |
328 | above works, but I will clarify the syntax for this at a future point, so |
22727dd5 |
329 | it's recommended to avoid (should not really be needed so much anyway). For |
59deb858 |
330 | now this is supported in an EXPERIMENTAL way. Your thoughts, test cases and |
07a8693b |
331 | patches are welcomed for discussion. If you find a good use for this, please |
332 | let me know. |
16aea7bf |
333 | |
334 | =head2 Coercions |
335 | |
336 | Coercions currently work for 'one level' deep. That is you can do: |
337 | |
af1d00c9 |
338 | subtype Person, |
07a8693b |
339 | as Dict[ |
340 | name => Str, |
341 | age => Int |
342 | ]; |
af1d00c9 |
343 | |
16aea7bf |
344 | subtype Fullname, |
07a8693b |
345 | as Dict[ |
346 | first => Str, |
347 | last => Str |
348 | ]; |
af1d00c9 |
349 | |
350 | coerce Person, |
d87e8b74 |
351 | ## Coerce an object of a particular class |
07a8693b |
352 | from BlessedPersonObject, via { |
353 | +{ |
354 | name=>$_->name, |
355 | age=>$_->age, |
356 | }; |
357 | }, |
358 | |
d87e8b74 |
359 | ## Coerce from [$name, $age] |
07a8693b |
360 | from ArrayRef, via { |
361 | +{ |
362 | name=>$_->[0], |
363 | age=>$_->[1], |
364 | }, |
365 | }, |
d87e8b74 |
366 | ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject} |
07a8693b |
367 | from Dict[fullname=>Fullname, dob=>DateTime], via { |
af1d00c9 |
368 | my $age = $_->dob - DateTime->now; |
07a8693b |
369 | my $firstn = $_->{fullname}->{first}; |
370 | my $lastn = $_->{fullname}->{last} |
af1d00c9 |
371 | +{ |
07a8693b |
372 | name => $_->{fullname}->{first} .' '. , |
373 | age =>$age->years |
af1d00c9 |
374 | } |
16aea7bf |
375 | }; |
376 | |
377 | And that should just work as expected. However, if there are any 'inner' |
378 | coercions, such as a coercion on 'Fullname' or on 'DateTime', that coercion |
379 | won't currently get activated. |
380 | |
22727dd5 |
381 | Please see the test '07-coerce.t' for a more detailed example. Discussion on |
382 | extending coercions to support this welcome on the Moose development channel or |
383 | mailing list. |
16aea7bf |
384 | |
c6fece89 |
385 | =head2 Recursion |
386 | |
387 | Newer versions of L<MooseX::Types> support recursive type constraints. That is |
388 | you can include a type constraint as a contained type constraint of itself. For |
389 | example: |
390 | |
391 | subtype Person, |
392 | as Dict[ |
393 | name=>Str, |
394 | friends=>Optional[ |
395 | ArrayRef[Person] |
396 | ], |
397 | ]; |
398 | |
399 | This would declare a Person subtype that contains a name and an optional |
400 | ArrayRef of Persons who are friends as in: |
401 | |
402 | { |
403 | name => 'Mike', |
404 | friends => [ |
405 | { name => 'John' }, |
406 | { name => 'Vincent' }, |
407 | { |
408 | name => 'Tracey', |
409 | friends => [ |
410 | { name => 'Stephenie' }, |
411 | { name => 'Ilya' }, |
412 | ], |
413 | }, |
414 | ], |
415 | }; |
416 | |
417 | Please take care to make sure the recursion node is either Optional, or declare |
418 | a Union with an non recursive option such as: |
419 | |
420 | subtype Value |
421 | as Tuple[ |
422 | Str, |
423 | Str|Tuple, |
424 | ]; |
425 | |
426 | Which validates: |
427 | |
428 | [ |
429 | 'Hello', [ |
430 | 'World', [ |
431 | 'Is', [ |
432 | 'Getting', |
433 | 'Old', |
434 | ], |
435 | ], |
436 | ], |
437 | ]; |
438 | |
439 | Otherwise you will define a subtype thatis impossible to validate since it is |
440 | infinitely recursive. For more information about defining recursive types, |
441 | please see the documentation in L<MooseX::Types> and the test cases. |
442 | |
16aea7bf |
443 | =head1 TYPE CONSTRAINTS |
444 | |
445 | This type library defines the following constraints. |
446 | |
447 | =head2 Tuple[@constraints] |
448 | |
07a8693b |
449 | This defines an ArrayRef based constraint which allows you to validate a specific |
450 | list of contained constraints. For example: |
16aea7bf |
451 | |
af1d00c9 |
452 | Tuple[Int,Str]; ## Validates [1,'hello'] |
c6fece89 |
453 | Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2] |
16aea7bf |
454 | |
22727dd5 |
455 | =head2 Dict[%constraints] |
16aea7bf |
456 | |
07a8693b |
457 | This defines a HashRef based constraint which allowed you to validate a specific |
16aea7bf |
458 | hashref. For example: |
459 | |
af1d00c9 |
460 | Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39} |
d24da8ec |
461 | |
22727dd5 |
462 | =head2 Optional[$constraint] |
190a34eb |
463 | |
464 | This is primarily a helper constraint for Dict and Tuple type constraints. What |
465 | this allows if for you to assert that a given type constraint is allowed to be |
466 | null (but NOT undefined). If the value is null, then the type constraint passes |
467 | but if the value is defined it must validate against the type constraint. This |
468 | makes it easy to make a Dict where one or more of the keys doesn't have to exist |
469 | or a tuple where some of the values are not required. For example: |
470 | |
471 | subtype Name() => as Dict[ |
472 | first=>Str, |
473 | last=>Str, |
474 | middle=>Optional[Str], |
475 | ]; |
476 | |
477 | Creates a constraint that validates against a hashref with the keys 'first' and |
478 | 'last' being strings and required while an optional key 'middle' is must be a |
479 | string if it appears but doesn't have to appear. So in this case both the |
480 | following are valid: |
481 | |
482 | {first=>'John', middle=>'James', last=>'Napiorkowski'} |
483 | {first=>'Vanessa', last=>'Li'} |
52ffe972 |
484 | |
485 | =head1 EXPORTABLE SUBROUTINES |
486 | |
487 | This type library makes available for export the following subroutines |
488 | |
489 | =head2 slurpy |
490 | |
491 | Structured type constraints by their nature are closed; that is validation will |
492 | depend and an exact match between your structure definition and the arguments to |
493 | be checked. Sometimes you might wish for a slightly looser amount of validation. |
494 | For example, you may wish to validate the first 3 elements of an array reference |
495 | and allow for an arbitrary number of additional elements. At first thought you |
496 | might think you could do it this way: |
497 | |
498 | # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...] |
499 | subtype AllowTailingArgs, |
500 | as Tuple[ |
501 | Int, |
502 | Str, |
503 | Object, |
504 | ArrayRef[Int], |
505 | ]; |
506 | |
507 | However what this will actually validate are structures like this: |
508 | |
509 | [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef |
510 | |
511 | In order to allow structured validation of, "and then some", arguments, you can |
512 | use the </slurpy> method against a type constraint. For example: |
513 | |
514 | use MooseX::Types::Structured qw(Tuple slurpy); |
515 | |
516 | subtype AllowTailingArgs, |
517 | as Tuple[ |
518 | Int, |
519 | Str, |
520 | Object, |
521 | slurpy ArrayRef[Int], |
522 | ]; |
523 | |
524 | This will now work as expected, validating ArrayRef structures such as: |
525 | |
526 | [1,"hello", $obj, 2,3,4,5,6,...] |
190a34eb |
527 | |
52ffe972 |
528 | A few caveats apply. First, the slurpy type constraint must be the last one in |
529 | the list of type constraint parameters. Second, the parent type of the slurpy |
530 | type constraint must match that of the containing type constraint. That means |
531 | that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including |
532 | another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of |
533 | HashRef, also including other Dict constraints). |
534 | |
535 | Please note the the technical way this works 'under the hood' is that the |
536 | slurpy keywork transforms the target type constraint into a coderef. Please do |
537 | not try to create your own custom coderefs; always use the slurpy method. The |
538 | underlying technology may change in the future but the slurpy keyword will be |
539 | supported. |
540 | |
59deb858 |
541 | =head1 EXAMPLES |
542 | |
543 | Here are some additional example usage for structured types. All examples can |
544 | be found also in the 't/examples.t' test. Your contributions are also welcomed. |
545 | |
546 | =head2 Normalize a HashRef |
547 | |
548 | You need a hashref to conform to a canonical structure but are required accept a |
549 | bunch of different incoming structures. You can normalize using the Dict type |
550 | constraint and coercions. This example also shows structured types mixed which |
551 | other MooseX::Types libraries. |
552 | |
553 | package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize; |
554 | |
555 | use Moose; |
556 | use DateTime; |
557 | |
558 | use MooseX::Types::Structured qw(Dict Tuple); |
559 | use MooseX::Types::DateTime qw(DateTime); |
560 | use MooseX::Types::Moose qw(Int Str Object); |
561 | use MooseX::Types -declare => [qw(Name Age Person)]; |
562 | |
563 | subtype Person, |
c6fece89 |
564 | as Dict[ |
565 | name=>Str, |
566 | age=>Int, |
567 | ]; |
59deb858 |
568 | |
569 | coerce Person, |
c6fece89 |
570 | from Dict[ |
571 | first=>Str, |
572 | last=>Str, |
573 | years=>Int, |
574 | ], via { +{ |
59deb858 |
575 | name => "$_->{first} $_->{last}", |
c6fece89 |
576 | age => $_->{years}, |
59deb858 |
577 | }}, |
c6fece89 |
578 | from Dict[ |
579 | fullname=>Dict[ |
580 | last=>Str, |
581 | first=>Str, |
582 | ], |
583 | dob=>DateTime, |
584 | ], |
07a8693b |
585 | ## DateTime needs to be inside of single quotes here to disambiguate the |
586 | ## class package from the DataTime type constraint imported via the |
587 | ## line "use MooseX::Types::DateTime qw(DateTime);" |
59deb858 |
588 | via { +{ |
589 | name => "$_->{fullname}{first} $_->{fullname}{last}", |
590 | age => ($_->{dob} - 'DateTime'->now)->years, |
591 | }}; |
592 | |
593 | has person => (is=>'rw', isa=>Person, coerce=>1); |
07a8693b |
594 | |
595 | And now you can instantiate with all the following: |
596 | |
597 | __PACKAGE__->new( |
598 | name=>'John Napiorkowski', |
599 | age=>39, |
600 | ); |
601 | |
602 | __PACKAGE__->new( |
603 | first=>'John', |
604 | last=>'Napiorkowski', |
605 | years=>39, |
606 | ); |
607 | |
608 | __PACKAGE__->new( |
609 | fullname => { |
610 | first=>'John', |
611 | last=>'Napiorkowski' |
612 | }, |
613 | dob => 'DateTime'->new( |
614 | year=>1969, |
615 | month=>2, |
616 | day=>13 |
617 | ), |
618 | ); |
619 | |
620 | This technique is a way to support various ways to instantiate your class in a |
621 | clean and declarative way. |
59deb858 |
622 | |
a30fa891 |
623 | =cut |
624 | |
67a8bc04 |
625 | Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( |
626 | MooseX::Meta::TypeConstraint::Structured->new( |
627 | name => "MooseX::Types::Structured::Tuple" , |
628 | parent => find_type_constraint('ArrayRef'), |
e327145a |
629 | constraint_generator=> sub { |
67a8bc04 |
630 | ## Get the constraints and values to check |
e327145a |
631 | my ($type_constraints, $values) = @_; |
07a8693b |
632 | my @type_constraints = defined $type_constraints ? |
ff801143 |
633 | @$type_constraints : (); |
634 | |
635 | my $overflow_handler; |
636 | if(ref $type_constraints[-1] eq 'CODE') { |
637 | $overflow_handler = pop @type_constraints; |
638 | } |
639 | |
e327145a |
640 | my @values = defined $values ? @$values: (); |
67a8bc04 |
641 | ## Perform the checking |
642 | while(@type_constraints) { |
643 | my $type_constraint = shift @type_constraints; |
a30fa891 |
644 | if(@values) { |
67a8bc04 |
645 | my $value = shift @values; |
646 | unless($type_constraint->check($value)) { |
647 | return; |
648 | } |
649 | } else { |
07a8693b |
650 | ## Test if the TC supports null values |
190a34eb |
651 | unless($type_constraint->check()) { |
652 | return; |
653 | } |
a30fa891 |
654 | } |
655 | } |
67a8bc04 |
656 | ## Make sure there are no leftovers. |
657 | if(@values) { |
ff801143 |
658 | if($overflow_handler) { |
52ffe972 |
659 | return $overflow_handler->([@values]); |
ff801143 |
660 | } else { |
661 | return; |
662 | } |
67a8bc04 |
663 | } elsif(@type_constraints) { |
c6fece89 |
664 | warn "I failed due to left over TC"; |
67a8bc04 |
665 | return; |
07a8693b |
666 | } else { |
67a8bc04 |
667 | return 1; |
668 | } |
669 | } |
670 | ) |
671 | ); |
672 | |
673 | Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( |
674 | MooseX::Meta::TypeConstraint::Structured->new( |
675 | name => "MooseX::Types::Structured::Dict", |
676 | parent => find_type_constraint('HashRef'), |
e327145a |
677 | constraint_generator=> sub { |
67a8bc04 |
678 | ## Get the constraints and values to check |
e327145a |
679 | my ($type_constraints, $values) = @_; |
ff801143 |
680 | my @type_constraints = defined $type_constraints ? |
681 | @$type_constraints : (); |
682 | |
683 | my $overflow_handler; |
684 | if(ref $type_constraints[-1] eq 'CODE') { |
685 | $overflow_handler = pop @type_constraints; |
686 | } |
687 | my (%type_constraints) = @type_constraints; |
e327145a |
688 | my %values = defined $values ? %$values: (); |
67a8bc04 |
689 | ## Perform the checking |
690 | while(%type_constraints) { |
691 | my($key, $type_constraint) = each %type_constraints; |
692 | delete $type_constraints{$key}; |
693 | if(exists $values{$key}) { |
694 | my $value = $values{$key}; |
695 | delete $values{$key}; |
696 | unless($type_constraint->check($value)) { |
a30fa891 |
697 | return; |
698 | } |
07a8693b |
699 | } else { |
700 | ## Test to see if the TC supports null values |
190a34eb |
701 | unless($type_constraint->check()) { |
702 | return; |
703 | } |
a30fa891 |
704 | } |
67a8bc04 |
705 | } |
706 | ## Make sure there are no leftovers. |
e327145a |
707 | if(%values) { |
ff801143 |
708 | if($overflow_handler) { |
52ffe972 |
709 | return $overflow_handler->(+{%values}); |
ff801143 |
710 | } else { |
711 | return; |
712 | } |
67a8bc04 |
713 | } elsif(%type_constraints) { |
714 | return; |
07a8693b |
715 | } else { |
67a8bc04 |
716 | return 1; |
717 | } |
718 | }, |
719 | ) |
720 | ); |
d24da8ec |
721 | |
e327145a |
722 | OPTIONAL: { |
723 | my $Optional = Moose::Meta::TypeConstraint::Parameterizable->new( |
724 | name => 'MooseX::Types::Structured::Optional', |
725 | package_defined_in => __PACKAGE__, |
726 | parent => find_type_constraint('Item'), |
727 | constraint => sub { 1 }, |
728 | constraint_generator => sub { |
729 | my ($type_parameter, @args) = @_; |
730 | my $check = $type_parameter->_compiled_type_constraint(); |
731 | return sub { |
07a8693b |
732 | my (@args) = @_; |
733 | ## Does the arg exist? Something exists if it's a 'real' value |
734 | ## or if it is set to undef. |
e327145a |
735 | if(exists($args[0])) { |
736 | ## If it exists, we need to validate it |
737 | $check->($args[0]); |
738 | } else { |
739 | ## But it's is okay if the value doesn't exists |
740 | return 1; |
741 | } |
742 | } |
743 | } |
744 | ); |
745 | |
746 | Moose::Util::TypeConstraints::register_type_constraint($Optional); |
747 | Moose::Util::TypeConstraints::add_parameterizable_type($Optional); |
748 | } |
749 | |
c116e19a |
750 | sub slurpy($) { |
751 | my $tc = shift @_; |
52ffe972 |
752 | return sub { |
753 | $tc->check(shift); |
754 | }; |
c116e19a |
755 | } |
e327145a |
756 | |
d24da8ec |
757 | =head1 SEE ALSO |
758 | |
759 | The following modules or resources may be of interest. |
760 | |
22727dd5 |
761 | L<Moose>, L<MooseX::Types>, L<Moose::Meta::TypeConstraint>, |
a30fa891 |
762 | L<MooseX::Meta::TypeConstraint::Structured> |
d24da8ec |
763 | |
16aea7bf |
764 | =head1 TODO |
765 | |
c6fece89 |
766 | Here's a list of stuff I would be happy to get volunteers helping with: |
767 | |
768 | All POD examples need test cases in t/documentation/*.t |
769 | Want to break out the examples section to a separate cookbook style POD. |
770 | Want more examples and best practice / usage guidance for authors |
771 | Need to clarify deep coercions, |
772 | Need to clarify subtypes of subtypes. |
16aea7bf |
773 | |
d24da8ec |
774 | =head1 AUTHOR |
775 | |
776 | John Napiorkowski, C<< <jjnapiork@cpan.org> >> |
777 | |
778 | =head1 COPYRIGHT & LICENSE |
779 | |
780 | This program is free software; you can redistribute it and/or modify |
781 | it under the same terms as Perl itself. |
782 | |
783 | =cut |
67a8bc04 |
784 | |
785 | 1; |