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