X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FMooseX%2FTypes%2FStructured.pm;h=4124c39d26d728ffaaf7688f141574cbe89fd623;hb=d54624ea83d2cf02feefb3dc4e50a318ee433eb7;hp=4d97757fdef0a97795082526796d62b6c2380be2;hpb=07a8693bdff0e8fda9b09de4c828de1e955cb342;p=gitmo%2FMooseX-Types-Structured.git diff --git a/lib/MooseX/Types/Structured.pm b/lib/MooseX/Types/Structured.pm index 4d97757..4124c39 100644 --- a/lib/MooseX/Types/Structured.pm +++ b/lib/MooseX/Types/Structured.pm @@ -1,11 +1,13 @@ package MooseX::Types::Structured; use 5.008; + use Moose::Util::TypeConstraints; use MooseX::Meta::TypeConstraint::Structured; use MooseX::Types -declare => [qw(Dict Tuple Optional)]; +use Sub::Exporter -setup => { exports => [ qw(Dict Tuple Optional slurpy) ] }; -our $VERSION = '0.06'; +our $VERSION = '0.08'; our $AUTHORITY = 'cpan:JJNAPIORK'; =head1 NAME @@ -110,7 +112,8 @@ generalized form is: TypeConstraint[@TypeParameters or %TypeParameters] -Where 'TypeParameters' is an array or hash of L. +Where 'TypeParameters' is an array reference or hash references of +L objects. This type library enables structured type constraints. It is built on top of the L library system, so you should review the documentation for that @@ -126,14 +129,14 @@ you could define a parameterized constraint like: subtype ArrayOfInts, as Arrayref[Int]; -which would constraint a value to something like [1,2,3,...] and so on. On the +which would constrain a value to something like [1,2,3,...] and so on. On the other hand, a structured type constraint explicitly names all it's allowed 'internal' type parameter constraints. For the example: subtype StringFollowedByInt, as Tuple[Str,Int]; -would constrain it's value to something like ['hello', 111] but ['hello', 'world'] +would constrain it's value to things like ['hello', 111] but ['hello', 'world'] would fail, as well as ['hello', 111, 'world'] and so on. Here's another example: @@ -151,8 +154,8 @@ This defines a type constraint that validates values like: Notice that the last type constraint in the structure is optional. This is enabled via the helper Optional type constraint, which is a variation of the core Moose type constraint 'Maybe'. The main difference is that Optional type -constraints are required to validate if they exist, while Maybe permits undefined -values. So the following example would not validate: +constraints are required to validate if they exist, while 'Maybe' permits +undefined values. So the following example would not validate: StringIntOptionalHashRef->validate(['Hello Undefined', 1000, undef]); @@ -160,11 +163,11 @@ Please note the subtle difference between undefined and null. If you wish to allow both null and undefined, you should use the core Moose 'Maybe' type constraint instead: - use MooseX::Types -declare [qw(StringIntOptionalHashRef)]; + use MooseX::Types -declare [qw(StringIntMaybeHashRef)]; use MooseX::Types::Moose qw(Maybe); use MooseX::Types::Structured qw(Tuple); - subtype StringIntOptionalHashRef, + subtype StringIntMaybeHashRef, as Tuple[ Str, Int, Maybe[HashRef] ]; @@ -175,8 +178,8 @@ This would validate the following: ['World', 200, undef]; ['World', 200]; -Structured Constraints are not limited to arrays. You can define a structure -against a hashref with 'Dict' as in this example: +Structured constraints are not limited to arrays. You can define a structure +against a HashRef with 'Dict' as in this example: subtype FirstNameLastName, as Dict[ @@ -202,7 +205,7 @@ but all the following would fail validation: These structures can be as simple or elaborate as you wish. You can even combine various structured, parameterized and simple constraints all together: - subtype crazy, + subtype Crazy, as Tuple[ Int, Dict[name=>Str, age=>Int], @@ -234,7 +237,7 @@ example: my $instance = MyApp::MyClass->new( person=>MyApp::MyStruct->new( full_name => 'John', - age_in_years => 39 + age_in_years => 39, ), ); @@ -379,6 +382,64 @@ Please see the test '07-coerce.t' for a more detailed example. Discussion on extending coercions to support this welcome on the Moose development channel or mailing list. +=head2 Recursion + +Newer versions of L support recursive type constraints. That is +you can include a type constraint as a contained type constraint of itself. For +example: + + subtype Person, + as Dict[ + name=>Str, + friends=>Optional[ + ArrayRef[Person] + ], + ]; + +This would declare a Person subtype that contains a name and an optional +ArrayRef of Persons who are friends as in: + + { + name => 'Mike', + friends => [ + { name => 'John' }, + { name => 'Vincent' }, + { + name => 'Tracey', + friends => [ + { name => 'Stephenie' }, + { name => 'Ilya' }, + ], + }, + ], + }; + +Please take care to make sure the recursion node is either Optional, or declare +a Union with an non recursive option such as: + + subtype Value + as Tuple[ + Str, + Str|Tuple, + ]; + +Which validates: + + [ + 'Hello', [ + 'World', [ + 'Is', [ + 'Getting', + 'Old', + ], + ], + ], + ]; + +Otherwise you will define a subtype thatis impossible to validate since it is +infinitely recursive. For more information about defining recursive types, +please see the documentation in L and the test cases. + =head1 TYPE CONSTRAINTS This type library defines the following constraints. @@ -389,7 +450,7 @@ This defines an ArrayRef based constraint which allows you to validate a specifi list of contained constraints. For example: Tuple[Int,Str]; ## Validates [1,'hello'] - Tuple[Str|Object, Int]; ##Validates ['hello', 1] or [$object, 2] + Tuple[Str|Object, Int]; ## Validates ['hello', 1] or [$object, 2] =head2 Dict[%constraints] @@ -420,7 +481,63 @@ following are valid: {first=>'John', middle=>'James', last=>'Napiorkowski'} {first=>'Vanessa', last=>'Li'} + +=head1 EXPORTABLE SUBROUTINES + +This type library makes available for export the following subroutines + +=head2 slurpy + +Structured type constraints by their nature are closed; that is validation will +depend and an exact match between your structure definition and the arguments to +be checked. Sometimes you might wish for a slightly looser amount of validation. +For example, you may wish to validate the first 3 elements of an array reference +and allow for an arbitrary number of additional elements. At first thought you +might think you could do it this way: + + # I want to validate stuff like: [1,"hello", $obj, 2,3,4,5,6,...] + subtype AllowTailingArgs, + as Tuple[ + Int, + Str, + Object, + ArrayRef[Int], + ]; + +However what this will actually validate are structures like this: + + [10,"Hello", $obj, [11,12,13,...] ]; # Notice element 4 is an ArrayRef + +In order to allow structured validation of, "and then some", arguments, you can +use the method against a type constraint. For example: + + use MooseX::Types::Structured qw(Tuple slurpy); + subtype AllowTailingArgs, + as Tuple[ + Int, + Str, + Object, + slurpy ArrayRef[Int], + ]; + +This will now work as expected, validating ArrayRef structures such as: + + [1,"hello", $obj, 2,3,4,5,6,...] + +A few caveats apply. First, the slurpy type constraint must be the last one in +the list of type constraint parameters. Second, the parent type of the slurpy +type constraint must match that of the containing type constraint. That means +that a Tuple can allow a slurpy ArrayRef (or children of ArrayRefs, including +another Tuple) and a Dict can allow a slurpy HashRef (or children/subtypes of +HashRef, also including other Dict constraints). + +Please note the the technical way this works 'under the hood' is that the +slurpy keywork transforms the target type constraint into a coderef. Please do +not try to create your own custom coderefs; always use the slurpy method. The +underlying technology may change in the future but the slurpy keyword will be +supported. + =head1 EXAMPLES Here are some additional example usage for structured types. All examples can @@ -444,15 +561,27 @@ other MooseX::Types libraries. use MooseX::Types -declare => [qw(Name Age Person)]; subtype Person, - as Dict[name=>Str, age=>Int]; + as Dict[ + name=>Str, + age=>Int, + ]; coerce Person, - from Dict[first=>Str, last=>Str, years=>Int], - via { +{ + from Dict[ + first=>Str, + last=>Str, + years=>Int, + ], via { +{ name => "$_->{first} $_->{last}", - age=>$_->{years}, + age => $_->{years}, }}, - from Dict[fullname=>Dict[last=>Str, first=>Str], dob=>DateTime], + from Dict[ + fullname=>Dict[ + last=>Str, + first=>Str, + ], + dob=>DateTime, + ], ## DateTime needs to be inside of single quotes here to disambiguate the ## class package from the DataTime type constraint imported via the ## line "use MooseX::Types::DateTime qw(DateTime);" @@ -501,7 +630,13 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( ## Get the constraints and values to check my ($type_constraints, $values) = @_; my @type_constraints = defined $type_constraints ? - @$type_constraints : (); + @$type_constraints : (); + + my $overflow_handler; + if(ref $type_constraints[-1] eq 'CODE') { + $overflow_handler = pop @type_constraints; + } + my @values = defined $values ? @$values: (); ## Perform the checking while(@type_constraints) { @@ -520,8 +655,13 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( } ## Make sure there are no leftovers. if(@values) { - return; + if($overflow_handler) { + return $overflow_handler->([@values]); + } else { + return; + } } elsif(@type_constraints) { + warn "I failed due to left over TC"; return; } else { return 1; @@ -537,8 +677,14 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( constraint_generator=> sub { ## Get the constraints and values to check my ($type_constraints, $values) = @_; - my %type_constraints = defined $type_constraints ? - @$type_constraints : (); + my @type_constraints = defined $type_constraints ? + @$type_constraints : (); + + my $overflow_handler; + if(ref $type_constraints[-1] eq 'CODE') { + $overflow_handler = pop @type_constraints; + } + my (%type_constraints) = @type_constraints; my %values = defined $values ? %$values: (); ## Perform the checking while(%type_constraints) { @@ -559,7 +705,11 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint( } ## Make sure there are no leftovers. if(%values) { - return; + if($overflow_handler) { + return $overflow_handler->(+{%values}); + } else { + return; + } } elsif(%type_constraints) { return; } else { @@ -597,6 +747,12 @@ OPTIONAL: { Moose::Util::TypeConstraints::add_parameterizable_type($Optional); } +sub slurpy($) { + my $tc = shift @_; + return sub { + $tc->check(shift); + }; +} =head1 SEE ALSO @@ -607,8 +763,13 @@ L =head1 TODO -Need to clarify deep coercions, need to clarify subtypes of subtypes. Would -like more and better examples and probably some best practices guidence. +Here's a list of stuff I would be happy to get volunteers helping with: + +All POD examples need test cases in t/documentation/*.t +Want to break out the examples section to a separate cookbook style POD. +Want more examples and best practice / usage guidance for authors +Need to clarify deep coercions, +Need to clarify subtypes of subtypes. =head1 AUTHOR