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