foo
[gitmo/Moose.git] / lib / Moose / Meta / TypeConstraint.pm
index 4c1205c..e4dc9bb 100644 (file)
@@ -5,14 +5,19 @@ use strict;
 use warnings;
 use metaclass;
 
-use Sub::Name 'subname';
-use Carp      'confess';
+use Sub::Name    'subname';
+use Carp         'confess';
+use Scalar::Util 'blessed';
 
-our $VERSION = '0.01';
+our $VERSION = '0.04';
 
 __PACKAGE__->meta->add_attribute('name'       => (reader => 'name'      ));
 __PACKAGE__->meta->add_attribute('parent'     => (reader => 'parent'    ));
 __PACKAGE__->meta->add_attribute('constraint' => (reader => 'constraint'));
+__PACKAGE__->meta->add_attribute('message'   => (
+    accessor  => 'message',
+    predicate => 'has_message'
+));
 __PACKAGE__->meta->add_attribute('coercion'   => (
     accessor  => 'coercion',
     predicate => 'has_coercion'
@@ -30,7 +35,7 @@ sub new {
     return $self;
 }
 
-sub compile_type_constraint () {
+sub compile_type_constraint {
     my $self  = shift;
     my $check = $self->constraint;
     (defined $check)
@@ -42,7 +47,7 @@ sub compile_type_constraint () {
                $self->_compiled_type_constraint(subname $self->name => sub {                   
                        local $_ = $_[0];
                        return undef unless defined $parent->($_[0]) && $check->($_[0]);
-                       $_[0];
+                       1;
                });        
     }
     else {
@@ -50,13 +55,130 @@ sub compile_type_constraint () {
        $self->_compiled_type_constraint(subname $self->name => sub { 
                local $_ = $_[0];
                return undef unless $check->($_[0]);
-               $_[0];
+               1;
        });
     }
 }
 
 sub check { $_[0]->_compiled_type_constraint->($_[1]) }
 
+sub validate { 
+    my ($self, $value) = @_;
+    if ($self->_compiled_type_constraint->($value)) {
+        return undef;
+    }
+    else {
+        if ($self->has_message) {
+            local $_ = $value;
+            return $self->message->($value);
+        }
+        else {
+            return "Validation failed for '" . $self->name . "' failed";
+        }
+    }
+}
+
+sub is_a_type_of {
+    my ($self, $type_name) = @_;
+    ($self->name eq $type_name || $self->is_subtype_of($type_name));
+}
+
+sub is_subtype_of {
+    my ($self, $type_name) = @_;
+    my $current = $self;
+    while (my $parent = $current->parent) {
+        return 1 if $parent->name eq $type_name;
+        $current = $parent;
+    }
+    return 0;
+}
+
+sub union {
+    my ($class, @type_constraints) = @_;
+    (scalar @type_constraints >= 2)
+        || confess "You must pass in at least 2 Moose::Meta::TypeConstraint instances to make a union";    
+    (blessed($_) && $_->isa('Moose::Meta::TypeConstraint'))
+        || confess "You must pass in only Moose::Meta::TypeConstraint instances to make unions"
+            foreach @type_constraints;
+    return Moose::Meta::TypeConstraint::Union->new(
+        type_constraints => \@type_constraints
+    );
+}
+
+package Moose::Meta::TypeConstraint::Union;
+
+use strict;
+use warnings;
+use metaclass;
+
+our $VERSION = '0.01';
+
+__PACKAGE__->meta->add_attribute('type_constraints' => (
+    accessor  => 'type_constraints',
+    default   => sub { [] }
+));
+
+sub new { 
+    my $class = shift;
+    my $self  = $class->meta->new_object(@_);
+    return $self;
+}
+
+sub name { join ' | ' => map { $_->name } @{$_[0]->type_constraints} }
+
+# NOTE:
+# this should probably never be used
+# but we include it here for completeness
+sub constraint    { 
+    my $self = shift;
+    sub { $self->check($_[0]) }; 
+}
+
+# conform to the TypeConstraint API
+sub parent        { undef  }
+sub coercion      { undef  }
+sub has_coercion  { 0      }
+sub message       { undef  }
+sub has_message   { 0      }
+
+sub check {
+    my $self  = shift;
+    my $value = shift;
+    foreach my $type (@{$self->type_constraints}) {
+        return 1 if $type->check($value);
+    }
+    return undef;
+}
+
+sub validate {
+    my $self  = shift;
+    my $value = shift;
+    my $message;
+    foreach my $type (@{$self->type_constraints}) {
+        my $err = $type->validate($value);
+        return unless defined $err;
+        $message .= ($message ? ' and ' : '') . $err
+            if defined $err;
+    }
+    return ($message . ' in (' . $self->name . ')') ;    
+}
+
+sub is_a_type_of {
+    my ($self, $type_name) = @_;
+    foreach my $type (@{$self->type_constraints}) {
+        return 1 if $type->is_a_type_of($type_name);
+    }
+    return 0;    
+}
+
+sub is_subtype_of {
+    my ($self, $type_name) = @_;
+    foreach my $type (@{$self->type_constraints}) {
+        return 1 if $type->is_subtype_of($type_name);
+    }
+    return 0;
+}
+
 1;
 
 __END__
@@ -65,12 +187,19 @@ __END__
 
 =head1 NAME
 
-Moose::Meta::TypeConstraint - The Moose Type Constraint metaobject
-
-=head1 SYNOPSIS
+Moose::Meta::TypeConstraint - The Moose Type Constraint metaclass
 
 =head1 DESCRIPTION
 
+For the most part, the only time you will ever encounter an 
+instance of this class is if you are doing some serious deep 
+introspection. This API should not be considered final, but 
+it is B<highly unlikely> that this will matter to a regular 
+Moose user.
+
+If you wish to use features at this depth, please come to the 
+#moose IRC channel on irc.perl.org and we can talk :)
+
 =head1 METHODS
 
 =over 4
@@ -79,19 +208,46 @@ Moose::Meta::TypeConstraint - The Moose Type Constraint metaobject
 
 =item B<new>
 
+=item B<is_a_type_of ($type_name)>
+
+This checks the current type name, and if it does not match, 
+checks if it is a subtype of it.
+
+=item B<is_subtype_of ($type_name)>
+
+=item B<compile_type_constraint>
+
+=item B<check ($value)>
+
+This method will return a true (C<1>) if the C<$value> passes the 
+constraint, and false (C<0>) otherwise.
+
+=item B<validate ($value)>
+
+This method is similar to C<check>, but it deals with the error 
+message. If the C<$value> passes the constraint, C<undef> will be 
+returned. If the C<$value> does B<not> pass the constraint, then 
+the C<message> will be used to construct a custom error message.  
+
 =item B<name>
 
 =item B<parent>
 
-=item B<check>
-
 =item B<constraint>
 
+=item B<has_message>
+
+=item B<message>
+
 =item B<has_coercion>
 
 =item B<coercion>
 
-=item B<compile_type_constraint>
+=back
+
+=over 4
+
+=item B<union (@type_constraints)>
 
 =back