1MooseX::Types::StructurUesde(r3pCmo)ntributed Perl DocumMeonotsaetXi:o:nTypes::Structured(3pm)
2
3
4
6 MooseX::Types::Structured - Structured Type Constraints for Moose
7
9 version 0.36
10
12 The following is example usage for this module.
13
14 package Person;
15
16 use Moose;
17 use MooseX::Types::Moose qw(Str Int HashRef);
18 use MooseX::Types::Structured qw(Dict Tuple Optional);
19
20 ## A name has a first and last part, but middle names are not required
21 has name => (
22 isa=>Dict[
23 first => Str,
24 last => Str,
25 middle => Optional[Str],
26 ],
27 );
28
29 ## description is a string field followed by a HashRef of tagged data.
30 has description => (
31 isa=>Tuple[
32 Str,
33 Optional[HashRef],
34 ],
35 );
36
37 ## Remainder of your class attributes and methods
38
39 Then you can instantiate this class with something like:
40
41 my $john = Person->new(
42 name => {
43 first => 'John',
44 middle => 'James'
45 last => 'Napiorkowski',
46 },
47 description => [
48 'A cool guy who loves Perl and Moose.', {
49 married_to => 'Vanessa Li',
50 born_in => 'USA',
51 };
52 ]
53 );
54
55 Or with:
56
57 my $vanessa = Person->new(
58 name => {
59 first => 'Vanessa',
60 last => 'Li'
61 },
62 description => ['A great student!'],
63 );
64
65 But all of these would cause a constraint error for the "name"
66 attribute:
67
68 ## Value for 'name' not a HashRef
69 Person->new( name => 'John' );
70
71 ## Value for 'name' has incorrect hash key and missing required keys
72 Person->new( name => {
73 first_name => 'John'
74 });
75
76 ## Also incorrect keys
77 Person->new( name => {
78 first_name => 'John',
79 age => 39,
80 });
81
82 ## key 'middle' incorrect type, should be a Str not a ArrayRef
83 Person->new( name => {
84 first => 'Vanessa',
85 middle => [1,2],
86 last => 'Li',
87 });
88
89 And these would cause a constraint error for the "description"
90 attribute:
91
92 ## Should be an ArrayRef
93 Person->new( description => 'Hello I am a String' );
94
95 ## First element must be a string not a HashRef.
96 Person->new (description => [{
97 tag1 => 'value1',
98 tag2 => 'value2'
99 }]);
100
101 Please see the test cases for more examples.
102
104 A structured type constraint is a standard container Moose type
105 constraint, such as an "ArrayRef" or "HashRef", which has been enhanced
106 to allow you to explicitly name all the allowed type constraints inside
107 the structure. The generalized form is:
108
109 TypeConstraint[@TypeParameters or %TypeParameters]
110
111 Where "TypeParameters" is an array reference or hash references of
112 Moose::Meta::TypeConstraint objects.
113
114 This type library enables structured type constraints. It is built on
115 top of the MooseX::Types library system, so you should review the
116 documentation for that if you are not familiar with it.
117
118 Comparing Parameterized types to Structured types
119 Parameterized constraints are built into core Moose and you are
120 probably already familiar with the type constraints "HashRef" and
121 "ArrayRef". Structured types have similar functionality, so their
122 syntax is likewise similar. For example, you could define a
123 parameterized constraint like:
124
125 subtype ArrayOfInts,
126 as ArrayRef[Int];
127
128 which would constrain a value to something like [1,2,3,...] and so on.
129 On the other hand, a structured type constraint explicitly names all
130 it's allowed 'internal' type parameter constraints. For the example:
131
132 subtype StringFollowedByInt,
133 as Tuple[Str,Int];
134
135 would constrain its value to things like "['hello', 111]" but
136 "['hello', 'world']" would fail, as well as "['hello', 111, 'world']"
137 and so on. Here's another example:
138
139 package MyApp::Types;
140
141 use MooseX::Types -declare [qw(StringIntOptionalHashRef)];
142 use MooseX::Types::Moose qw(Str Int);
143 use MooseX::Types::Structured qw(Tuple Optional);
144
145 subtype StringIntOptionalHashRef,
146 as Tuple[
147 Str, Int,
148 Optional[HashRef]
149 ];
150
151 This defines a type constraint that validates values like:
152
153 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
154 ['World', 200];
155
156 Notice that the last type constraint in the structure is optional.
157 This is enabled via the helper "Optional" type constraint, which is a
158 variation of the core Moose type constraint "Maybe". The main
159 difference is that "Optional" type constraints are required to validate
160 if they exist, while "Maybe" permits undefined values. So the
161 following example would not validate:
162
163 StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]);
164
165 Please note the subtle difference between undefined and null. If you
166 wish to allow both null and undefined, you should use the core Moose
167 "Maybe" type constraint instead:
168
169 package MyApp::Types;
170
171 use MooseX::Types -declare [qw(StringIntMaybeHashRef)];
172 use MooseX::Types::Moose qw(Str Int Maybe);
173 use MooseX::Types::Structured qw(Tuple);
174
175 subtype StringIntMaybeHashRef,
176 as Tuple[
177 Str, Int, Maybe[HashRef]
178 ];
179
180 This would validate the following:
181
182 ['Hello', 100, {key1 => 'value1', key2 => 'value2'}];
183 ['World', 200, undef];
184 ['World', 200];
185
186 Structured constraints are not limited to arrays. You can define a
187 structure against a "HashRef" with the "Dict" type constraint as in
188 this example:
189
190 subtype FirstNameLastName,
191 as Dict[
192 firstname => Str,
193 lastname => Str,
194 ];
195
196 This would constrain a "HashRef" that validates something like:
197
198 {firstname => 'Christopher', lastname => 'Parsons'};
199
200 but all the following would fail validation:
201
202 ## Incorrect keys
203 {first => 'Christopher', last => 'Parsons'};
204
205 ## Too many keys
206 {firstname => 'Christopher', lastname => 'Parsons', middlename => 'Allen'};
207
208 ## Not a HashRef
209 ['Christopher', 'Parsons'];
210
211 These structures can be as simple or elaborate as you wish. You can
212 even combine various structured, parameterized and simple constraints
213 all together:
214
215 subtype Crazy,
216 as Tuple[
217 Int,
218 Dict[name=>Str, age=>Int],
219 ArrayRef[Int]
220 ];
221
222 Which would match:
223
224 [1, {name=>'John', age=>25},[10,11,12]];
225
226 Please notice how the type parameters can be visually arranged to your
227 liking and to improve the clarity of your meaning. You don't need to
228 run then altogether onto a single line. Additionally, since the "Dict"
229 type constraint defines a hash constraint, the key order is not
230 meaningful. For example:
231
232 subtype AnyKeyOrder,
233 as Dict[
234 key1=>Int,
235 key2=>Str,
236 key3=>Int,
237 ];
238
239 Would validate both:
240
241 {key1 => 1, key2 => "Hi!", key3 => 2};
242 {key2 => "Hi!", key1 => 100, key3 => 300};
243
244 As you would expect, since underneath it's just a plain old Perl hash
245 at work.
246
247 Alternatives
248 You should exercise some care as to whether or not your complex
249 structured constraints would be better off contained by a real object
250 as in the following example:
251
252 package MyApp::MyStruct;
253 use Moose;
254
255 ## lazy way to make a bunch of attributes
256 has $_ for qw(full_name age_in_years);
257
258 package MyApp::MyClass;
259 use Moose;
260
261 has person => (isa => 'MyApp::MyStruct');
262
263 my $instance = MyApp::MyClass->new(
264 person=>MyApp::MyStruct->new(
265 full_name => 'John',
266 age_in_years => 39,
267 ),
268 );
269
270 This method may take some additional time to set up but will give you
271 more flexibility. However, structured constraints are highly
272 compatible with this method, granting some interesting possibilities
273 for coercion. Try:
274
275 package MyApp::MyClass;
276
277 use Moose;
278 use MyApp::MyStruct;
279
280 ## It's recommended your type declarations live in a separate class in order
281 ## to promote reusability and clarity. Inlined here for brevity.
282
283 use MooseX::Types::DateTime qw(DateTime);
284 use MooseX::Types -declare [qw(MyStruct)];
285 use MooseX::Types::Moose qw(Str Int);
286 use MooseX::Types::Structured qw(Dict);
287
288 ## Use class_type to create an ISA type constraint if your object doesn't
289 ## inherit from Moose::Object.
290 class_type 'MyApp::MyStruct';
291
292 ## Just a shorter version really.
293 subtype MyStruct,
294 as 'MyApp::MyStruct';
295
296 ## Add the coercions.
297 coerce MyStruct,
298 from Dict[
299 full_name=>Str,
300 age_in_years=>Int
301 ], via {
302 MyApp::MyStruct->new(%$_);
303 },
304 from Dict[
305 lastname=>Str,
306 firstname=>Str,
307 dob=>DateTime
308 ], via {
309 my $name = $_->{firstname} .' '. $_->{lastname};
310 my $age = DateTime->now - $_->{dob};
311
312 MyApp::MyStruct->new(
313 full_name=>$name,
314 age_in_years=>$age->years,
315 );
316 };
317
318 has person => (isa=>MyStruct);
319
320 This would allow you to instantiate with something like:
321
322 my $obj = MyApp::MyClass->new( person => {
323 full_name=>'John Napiorkowski',
324 age_in_years=>39,
325 });
326
327 Or even:
328
329 my $obj = MyApp::MyClass->new( person => {
330 lastname=>'John',
331 firstname=>'Napiorkowski',
332 dob=>DateTime->new(year=>1969),
333 });
334
335 If you are not familiar with how coercions work, check out the Moose
336 cookbook entry Moose::Cookbook::Recipe5 for an explanation. The
337 section "Coercions" has additional examples and discussion.
338
339 Subtyping a Structured type constraint
340 You need to exercise some care when you try to subtype a structured
341 type as in this example:
342
343 subtype Person,
344 as Dict[name => Str];
345
346 subtype FriendlyPerson,
347 as Person[
348 name => Str,
349 total_friends => Int,
350 ];
351
352 This will actually work BUT you have to take care that the subtype has
353 a structure that does not contradict the structure of it's parent. For
354 now the above works, but I will clarify the syntax for this at a future
355 point, so it's recommended to avoid (should not really be needed so
356 much anyway). For now this is supported in an EXPERIMENTAL way. Your
357 thoughts, test cases and patches are welcomed for discussion. If you
358 find a good use for this, please let me know.
359
360 Coercions
361 Coercions currently work for 'one level' deep. That is you can do:
362
363 subtype Person,
364 as Dict[
365 name => Str,
366 age => Int
367 ];
368
369 subtype Fullname,
370 as Dict[
371 first => Str,
372 last => Str
373 ];
374
375 coerce Person,
376 ## Coerce an object of a particular class
377 from BlessedPersonObject, via {
378 +{
379 name=>$_->name,
380 age=>$_->age,
381 };
382 },
383
384 ## Coerce from [$name, $age]
385 from ArrayRef, via {
386 +{
387 name=>$_->[0],
388 age=>$_->[1],
389 },
390 },
391 ## Coerce from {fullname=>{first=>...,last=>...}, dob=>$DateTimeObject}
392 from Dict[fullname=>Fullname, dob=>DateTime], via {
393 my $age = $_->dob - DateTime->now;
394 my $firstn = $_->{fullname}->{first};
395 my $lastn = $_->{fullname}->{last}
396 +{
397 name => $_->{fullname}->{first} .' '. ,
398 age =>$age->years
399 }
400 };
401
402 And that should just work as expected. However, if there are any
403 'inner' coercions, such as a coercion on "Fullname" or on "DateTime",
404 that coercion won't currently get activated.
405
406 Please see the test 07-coerce.t for a more detailed example.
407 Discussion on extending coercions to support this welcome on the Moose
408 development channel or mailing list.
409
410 Recursion
411 Newer versions of MooseX::Types support recursive type constraints.
412 That is you can include a type constraint as a contained type
413 constraint of itself. For example:
414
415 subtype Person,
416 as Dict[
417 name=>Str,
418 friends=>Optional[
419 ArrayRef[Person]
420 ],
421 ];
422
423 This would declare a "Person" subtype that contains a name and an
424 optional "ArrayRef" of "Person"s who are friends as in:
425
426 {
427 name => 'Mike',
428 friends => [
429 { name => 'John' },
430 { name => 'Vincent' },
431 {
432 name => 'Tracey',
433 friends => [
434 { name => 'Stephenie' },
435 { name => 'Ilya' },
436 ],
437 },
438 ],
439 };
440
441 Please take care to make sure the recursion node is either "Optional",
442 or declare a union with an non-recursive option such as:
443
444 subtype Value
445 as Tuple[
446 Str,
447 Str|Tuple,
448 ];
449
450 Which validates:
451
452 [
453 'Hello', [
454 'World', [
455 'Is', [
456 'Getting',
457 'Old',
458 ],
459 ],
460 ],
461 ];
462
463 Otherwise you will define a subtype that is impossible to validate
464 since it is infinitely recursive. For more information about defining
465 recursive types, please see the documentation in MooseX::Types and the
466 test cases.
467
469 This type library defines the following constraints.
470
471 Tuple[@constraints]
472 This defines an ArrayRef based constraint which allows you to validate
473 a specific list of contained constraints. For example:
474
475 Tuple[Int,Str]; ## Validates [1,'hello']
476 Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2]
477
478 The Values of @constraints should ideally be MooseX::Types declared
479 type constraints. We do support 'old style' Moose string based
480 constraints to a limited degree but these string type constraints are
481 considered deprecated. There will be limited support for bugs
482 resulting from mixing string and MooseX::Types in your structures. If
483 you encounter such a bug and really need it fixed, we will required a
484 detailed test case at the minimum.
485
486 Dict[%constraints]
487 This defines a HashRef based constraint which allowed you to validate a
488 specific hashref. For example:
489
490 Dict[name=>Str, age=>Int]; ## Validates {name=>'John', age=>39}
491
492 The keys in %constraints follow the same rules as @constraints in the
493 above section.
494
495 Map[ $key_constraint, $value_constraint ]
496 This defines a "HashRef"-based constraint in which both the keys and
497 values are required to meet certain constraints. For example, to map
498 hostnames to IP addresses, you might say:
499
500 Map[ HostName, IPAddress ]
501
502 The type constraint would only be met if every key was a valid
503 "HostName" and every value was a valid "IPAddress".
504
505 Optional[$constraint]
506 This is primarily a helper constraint for "Dict" and "Tuple" type
507 constraints. What this allows is for you to assert that a given type
508 constraint is allowed to be null (but NOT undefined). If the value is
509 null, then the type constraint passes but if the value is defined it
510 must validate against the type constraint. This makes it easy to make
511 a Dict where one or more of the keys doesn't have to exist or a tuple
512 where some of the values are not required. For example:
513
514 subtype Name() => as Dict[
515 first=>Str,
516 last=>Str,
517 middle=>Optional[Str],
518 ];
519
520 ...creates a constraint that validates against a hashref with the keys
521 'first' and 'last' being strings and required while an optional key
522 'middle' is must be a string if it appears but doesn't have to appear.
523 So in this case both the following are valid:
524
525 {first=>'John', middle=>'James', last=>'Napiorkowski'}
526 {first=>'Vanessa', last=>'Li'}
527
528 If you use the "Maybe" type constraint instead, your values will also
529 validate against "undef", which may be incorrect for you.
530
532 This type library makes available for export the following subroutines
533
534 slurpy
535 Structured type constraints by their nature are closed; that is
536 validation will depend on an exact match between your structure
537 definition and the arguments to be checked. Sometimes you might wish
538 for a slightly looser amount of validation. For example, you may wish
539 to validate the first 3 elements of an array reference and allow for an
540 arbitrary number of additional elements. At first thought you might
541 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,
557 you can use the "slurpy" method against a type constraint. For
558 example:
559
560 use MooseX::Types::Structured qw(Tuple slurpy);
561
562 subtype AllowTailingArgs,
563 as Tuple[
564 Int,
565 Str,
566 Object,
567 slurpy ArrayRef[Int],
568 ];
569
570 This will now work as expected, validating ArrayRef structures such as:
571
572 [1,"hello", $obj, 2,3,4,5,6,...]
573
574 A few caveats apply. First, the slurpy type constraint must be the
575 last one in the list of type constraint parameters. Second, the parent
576 type of the slurpy type constraint must match that of the containing
577 type constraint. That means that a "Tuple" can allow a slurpy
578 "ArrayRef" (or children of "ArrayRef"s, including another "Tuple") and
579 a "Dict" can allow a slurpy "HashRef" (or children/subtypes of HashRef,
580 also including other "Dict" constraints).
581
582 Please note the technical way this works 'under the hood' is that the
583 slurpy keyword transforms the target type constraint into a coderef.
584 Please do not try to create your own custom coderefs; always use the
585 slurpy method. The underlying technology may change in the future but
586 the slurpy keyword will be supported.
587
589 Error reporting has been improved to return more useful debugging
590 messages. Now I will stringify the incoming check value with
591 Devel::PartialDump so that you can see the actual structure that is
592 tripping up validation. Also, I report the 'internal' validation
593 error, so that if a particular element inside the Structured Type is
594 failing validation, you will see that. There's a limit to how deep
595 this internal reporting goes, but you shouldn't see any of the "failed
596 with ARRAY(XXXXXX)" that we got with earlier versions of this module.
597
598 This support is continuing to expand, so it's best to use these
599 messages for debugging purposes and not for creating messages that
600 'escape into the wild' such as error messages sent to the user.
601
602 Please see the test '12-error.t' for a more lengthy example. Your
603 thoughts and preferable tests or code patches very welcome!
604
606 Here are some additional example usage for structured types. All
607 examples can be found also in the 't/examples.t' test. Your
608 contributions are also welcomed.
609
610 Normalize a HashRef
611 You need a hashref to conform to a canonical structure but are required
612 accept a bunch of different incoming structures. You can normalize
613 using the "Dict" type constraint and coercions. This example also
614 shows structured types mixed which other MooseX::Types libraries.
615
616 package Test::MooseX::Meta::TypeConstraint::Structured::Examples::Normalize;
617
618 use Moose;
619 use DateTime;
620
621 use MooseX::Types::Structured qw(Dict Tuple);
622 use MooseX::Types::DateTime qw(DateTime);
623 use MooseX::Types::Moose qw(Int Str Object);
624 use MooseX::Types -declare => [qw(Name Age Person)];
625
626 subtype Person,
627 as Dict[
628 name=>Str,
629 age=>Int,
630 ];
631
632 coerce Person,
633 from Dict[
634 first=>Str,
635 last=>Str,
636 years=>Int,
637 ], via { +{
638 name => "$_->{first} $_->{last}",
639 age => $_->{years},
640 }},
641 from Dict[
642 fullname=>Dict[
643 last=>Str,
644 first=>Str,
645 ],
646 dob=>DateTime,
647 ],
648 ## DateTime needs to be inside of single quotes here to disambiguate the
649 ## class package from the DataTime type constraint imported via the
650 ## line "use MooseX::Types::DateTime qw(DateTime);"
651 via { +{
652 name => "$_->{fullname}{first} $_->{fullname}{last}",
653 age => ($_->{dob} - 'DateTime'->now)->years,
654 }};
655
656 has person => (is=>'rw', isa=>Person, coerce=>1);
657
658 And now you can instantiate with all the following:
659
660 __PACKAGE__->new(
661 person=>{
662 name=>'John Napiorkowski',
663 age=>39,
664 },
665 );
666
667 __PACKAGE__->new(
668 person=>{
669 first=>'John',
670 last=>'Napiorkowski',
671 years=>39,
672 },
673 );
674
675 __PACKAGE__->new(
676 person=>{
677 fullname => {
678 first=>'John',
679 last=>'Napiorkowski'
680 },
681 dob => 'DateTime'->new(
682 year=>1969,
683 month=>2,
684 day=>13
685 ),
686 },
687 );
688
689 This technique is a way to support various ways to instantiate your
690 class in a clean and declarative way.
691
693 The following modules or resources may be of interest.
694
695 Moose, MooseX::Types, Moose::Meta::TypeConstraint,
696 MooseX::Meta::TypeConstraint::Structured
697
699 Bugs may be submitted through the RT bug tracker
700 <https://rt.cpan.org/Public/Dist/Display.html?Name=MooseX-Types-
701 Structured> (or bug-MooseX-Types-Structured@rt.cpan.org <mailto:bug-
702 MooseX-Types-Structured@rt.cpan.org>).
703
704 There is also a mailing list available for users of this distribution,
705 at <http://lists.perl.org/list/moose.html>.
706
707 There is also an irc channel available for users of this distribution,
708 at "#moose" on "irc.perl.org" <irc://irc.perl.org/#moose>.
709
711 • John Napiorkowski <jjnapiork@cpan.org>
712
713 • Florian Ragwitz <rafl@debian.org>
714
715 • יובל קוג'מן (Yuval Kogman) <nothingmuch@woobling.org>
716
717 • Tomas (t0m) Doran <bobtfish@bobtfish.net>
718
719 • Robert Sedlacek <rs@474.at>
720
722 • Karen Etheridge <ether@cpan.org>
723
724 • Ricardo Signes <rjbs@cpan.org>
725
726 • Dave Rolsky <autarch@urth.org>
727
728 • Ansgar Burchardt <ansgar@43-1.org>
729
730 • Stevan Little <stevan.little@iinteractive.com>
731
732 • arcanez <justin.d.hunter@gmail.com>
733
734 • Jesse Luehrs <doy@tozt.net>
735
736 • D. Ilmari Mannsåker <ilmari@cpan.org>
737
739 This software is copyright (c) 2008 by John Napiorkowski.
740
741 This is free software; you can redistribute it and/or modify it under
742 the same terms as the Perl 5 programming language system itself.
743
744
745
746perl v5.38.0 2023-07-21 MooseX::Types::Structured(3pm)