Avoid modification of non-creatable array values.
[gitmo/MooseX-Types-Structured.git] / lib / MooseX / Types / Structured.pm
index f440d5d..e336cd3 100644 (file)
@@ -4,10 +4,13 @@ use 5.008;
 
 use Moose::Util::TypeConstraints;
 use MooseX::Meta::TypeConstraint::Structured;
+use MooseX::Types::Structured::OverflowHandler;
 use MooseX::Types -declare => [qw(Dict Tuple Optional)];
 use Sub::Exporter -setup => { exports => [ qw(Dict Tuple Optional slurpy) ] };
+use Devel::PartialDump;
+use Scalar::Util qw(blessed);
 
-our $VERSION = '0.07';
+our $VERSION = '0.13';
 our $AUTHORITY = 'cpan:JJNAPIORK';
 
 =head1 NAME
@@ -481,7 +484,80 @@ 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 on 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 </slurpy> 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 ERROR MESSAGES
+
+Error reporting has been improved to return more useful debugging messages. Now
+I will stringify the incoming check value with L<Devel::PartialDump> so that you
+can see the actual structure that is tripping up validation.  Also, I report the
+'internal' validation error, so that if a particular element inside the
+Structured Type is failing validation, you will see that.  There's a limit to
+how deep this internal reporting goes, but you shouldn't see any of the "failed
+with ARRAY(XXXXXX)" that we got with earlier versions of this module.
+
+This support is continuing to expand, so it's best to use these messages for
+debugging purposes and not for creating messages that 'escape into the wild'
+such as error messages sent to the user.
+
+Please see the test '12-error.t' for a more lengthy example.  Your thoughts and
+preferable tests or code patches very welcome!
+
 =head1 EXAMPLES
 
 Here are some additional example usage for structured types.  All examples can
@@ -539,26 +615,32 @@ other MooseX::Types libraries.
 And now you can instantiate with all the following:
 
     __PACKAGE__->new(
-        name=>'John Napiorkowski',
-        age=>39,
+        person=>{
+            name=>'John Napiorkowski',
+            age=>39,            
+        },
     );
         
     __PACKAGE__->new(
-        first=>'John',
-        last=>'Napiorkowski',
-        years=>39,
+        person=>{
+            first=>'John',
+            last=>'Napiorkowski',
+            years=>39,
+        },
     );
     
     __PACKAGE__->new(
-        fullname => {
-            first=>'John',
-            last=>'Napiorkowski'
+        person=>{
+            fullname => {
+                first=>'John',
+                last=>'Napiorkowski'
+            },
+            dob => 'DateTime'->new(
+                year=>1969,
+                month=>2,
+                day=>13
+            ),            
         },
-        dob => 'DateTime'->new(
-            year=>1969,
-            month=>2,
-            day=>13
-        ),
     );
     
 This technique is a way to support various ways to instantiate your class in a
@@ -577,7 +659,8 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
              @$type_constraints : ();
             
             my $overflow_handler;
-            if(ref $type_constraints[-1] eq 'CODE') {
+            if($type_constraints[-1] && blessed $type_constraints[-1]
+              && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
                 $overflow_handler = pop @type_constraints;
             }
             
@@ -588,11 +671,15 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
                                if(@values) {
                                        my $value = shift @values;
                                        unless($type_constraint->check($value)) {
+                        $_[2]->{message} = $type_constraint->get_message($value)
+                         if ref $_[2];
                                                return;
                                        }                               
                                } else {
                     ## Test if the TC supports null values
                                        unless($type_constraint->check()) {
+                        $_[2]->{message} = $type_constraint->get_message('NULL')
+                         if ref $_[2];
                                                return;
                                        }
                                }
@@ -600,12 +687,16 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
                        ## Make sure there are no leftovers.
                        if(@values) {
                 if($overflow_handler) {
-                    return $overflow_handler->(@values);
+                    return $overflow_handler->check([@values], $_[2]);
                 } else {
+                    $_[2]->{message} = "More values than Type Constraints!"
+                     if ref $_[2];
                     return;
                 }
                        } elsif(@type_constraints) {
-                warn "I failed due to left over TC";
+                $_[2]->{message} =
+                 "Not enough values for all defined type constraints.  Remaining: ". join(', ',@type_constraints)
+                 if ref $_[2];
                                return;
                        } else {
                                return 1;
@@ -625,7 +716,8 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
              @$type_constraints : ();
             
             my $overflow_handler;
-            if(ref $type_constraints[-1] eq 'CODE') {
+            if($type_constraints[-1] && blessed $type_constraints[-1]
+              && $type_constraints[-1]->isa('MooseX::Types::Structured::OverflowHandler')) {
                 $overflow_handler = pop @type_constraints;
             } 
             my (%type_constraints) = @type_constraints;
@@ -638,11 +730,15 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
                                        my $value = $values{$key};
                                        delete $values{$key};
                                        unless($type_constraint->check($value)) {
+                        $_[2]->{message} = $type_constraint->get_message($value)
+                         if ref $_[2];
                                                return;
                                        }
                                } else {
                     ## Test to see if the TC supports null values
                                        unless($type_constraint->check()) {
+                        $_[2]->{message} = $type_constraint->get_message('NULL')
+                         if ref $_[2];
                                                return;
                                        }
                                }
@@ -650,11 +746,16 @@ Moose::Util::TypeConstraints::get_type_constraint_registry->add_type_constraint(
                        ## Make sure there are no leftovers.
                        if(%values) { 
                 if($overflow_handler) {
-                    return $overflow_handler->(%values);
+                    return $overflow_handler->check(+{%values});
                 } else {
+                    $_[2]->{message} = "More values than Type Constraints!"
+                     if ref $_[2];
                     return;
                 }
                        } elsif(%type_constraints) {
+                $_[2]->{message} =
+                 "Not enough values for all defined type constraints.  Remaining: ". join(', ',values %values)
+                 if ref $_[2];
                                return;
                        } else {
                                return 1;
@@ -691,26 +792,11 @@ OPTIONAL: {
     Moose::Util::TypeConstraints::add_parameterizable_type($Optional);
 }
 
-sub slurpy($) {
-       my $tc = shift @_;
-       ## we don't want to force the TC to be a Moose::Meta::TypeConstraint, we
-       ## just want to make sure it provides the minimum needed bits to function.
-       if($tc and ref $tc and $tc->can('check') and $tc->can('is_subtype_of') ) {
-               return sub {
-                       if($tc->is_subtype_of('HashRef')) {
-                               return $tc->check(+{@_});
-                       } elsif($tc->is_subtype_of('ArrayRef')) {
-                               return $tc->check([@_]);
-                       } else {
-                               return;
-                       }
-               };              
-       } else {
-               ## For now just pass it all to check and cross our fingers
-               return sub {
-                       return $tc->check(@_);
-               };      
-       }
+sub slurpy ($) {
+       my ($tc) = @_;
+       return MooseX::Types::Structured::OverflowHandler->new(
+        type_constraint => $tc,
+    );
 }
 
 =head1 SEE ALSO