943bcd63191ac5cdcafa78a0699c2bd7dd26fb8a
[gitmo/MooseX-Types.git] / lib / MooseX / Types / TypeDecorator.pm
1 package MooseX::Types::TypeDecorator;
2
3 #ABSTRACT: Wraps Moose::Meta::TypeConstraint objects with added features
4
5 use strict;
6 use warnings;
7
8 use Carp::Clan qw( ^MooseX::Types );
9 use Moose::Util::TypeConstraints ();
10 use Moose::Meta::TypeConstraint::Union;
11 use Scalar::Util qw(blessed);
12
13 use overload(
14     '0+' => sub {
15             my $self = shift @_;
16             my $tc = $self->{__type_constraint};
17             return 0+$tc;
18      },
19     '""' => sub {
20                 my $self = shift @_;
21                 if(blessed $self) {
22                         return $self->__type_constraint->name;                  
23                 } else {
24                         return "$self";
25                 }
26     },
27     bool => sub { 1 },
28     '|' => sub {
29         
30         ## It's kind of ugly that we need to know about Union Types, but this
31         ## is needed for syntax compatibility.  Maybe someday we'll all just do
32         ## Or[Str,Str,Int]
33
34         my @args = @_[0,1]; ## arg 3 is special,  see the overload docs.
35         my @tc = grep {blessed $_} map {
36             blessed $_ ? $_ :
37             Moose::Util::TypeConstraints::find_or_parse_type_constraint($_)
38               || __PACKAGE__->_throw_error( "$_ is not a type constraint")
39         } @args;
40
41         ( scalar @tc == scalar @args)
42             || __PACKAGE__->_throw_error(
43                           "one of your type constraints is bad.  Passed: ". join(', ', @args) ." Got: ". join(', ', @tc));
44
45         ( scalar @tc >= 2 )
46             || __PACKAGE__->_throw_error("You must pass in at least 2 type names to make a union");
47
48         my $union = Moose::Meta::TypeConstraint::Union->new(type_constraints=>\@tc);
49         return Moose::Util::TypeConstraints::register_type_constraint($union);
50     },
51     fallback => 1,
52     
53 );
54
55 =head1 DESCRIPTION
56
57 This is a decorator object that contains an underlying type constraint.  We use
58 this to control access to the type constraint and to add some features.
59
60 =head1 METHODS
61
62 This class defines the following methods.
63
64 =head2 new
65
66 Old school instantiation
67
68 =cut
69
70 sub new {
71     my $proto = shift;
72     if (ref($proto)) {
73         return $proto->_try_delegate('new', @_);
74     }
75     my $class = $proto;
76     if(my $arg = shift @_) {
77         if(blessed $arg && $arg->isa('Moose::Meta::TypeConstraint')) {
78             return bless {'__type_constraint'=>$arg}, $class;
79         } elsif(
80             blessed $arg &&
81             $arg->isa('MooseX::Types::UndefinedType') 
82           ) {
83             ## stub in case we'll need to handle these types differently
84             return bless {'__type_constraint'=>$arg}, $class;
85         } elsif(blessed $arg) {
86             __PACKAGE__->_throw_error("Argument must be ->isa('Moose::Meta::TypeConstraint') or ->isa('MooseX::Types::UndefinedType'), not ". blessed $arg);
87         } else {
88             __PACKAGE__->_throw_error("Argument cannot be '$arg'");
89         }
90     } else {
91         __PACKAGE__->_throw_error("This method [new] requires a single argument.");        
92     }
93 }
94
95 =head2 __type_constraint ($type_constraint)
96
97 Set/Get the type_constraint.
98
99 =cut
100
101 sub __type_constraint {
102     my $self = shift @_;    
103     if(blessed $self) {
104         if(defined(my $tc = shift @_)) {
105             $self->{__type_constraint} = $tc;
106         }
107         return $self->{__type_constraint};        
108     } else {
109         __PACKAGE__->_throw_error('cannot call __type_constraint as a class method');
110     }
111 }
112
113 =head2 isa
114
115 handle $self->isa since AUTOLOAD can't.
116
117 =cut
118
119 sub isa {
120   my $self = shift;
121   return
122     $self->__type_constraint->isa(@_)
123     || $self->_try_delegate('isa', @_);
124 }
125
126 =head2 can
127
128 handle $self->can since AUTOLOAD can't.
129
130 =cut
131
132 sub can { shift->_try_delegate('can', @_) }
133
134 =head2 _throw_error
135
136 properly delegate error messages
137
138 =cut
139
140 sub _throw_error {
141     shift;
142     require Moose;
143     unshift @_, 'Moose';
144     goto &Moose::throw_error;
145 }
146
147 =head2 DESTROY
148
149 We might need it later
150
151 =cut
152
153 sub DESTROY {
154     return;
155 }
156
157 =head2 AUTOLOAD
158
159 Delegate to the decorator target.
160
161 =cut
162
163 sub AUTOLOAD {
164     my ($self, @args) = @_;
165     my ($method) = (our $AUTOLOAD =~ /([^:]+)$/);
166     
167     ## We delegate with this method in an attempt to support a value of
168     ## __type_constraint which is also AUTOLOADing, in particular the class
169     ## MooseX::Types::UndefinedType which AUTOLOADs during autovivication.
170
171     $self->_try_delegate($method, @args);    
172 }
173
174 sub _try_delegate {
175     my ($self, $method, @args) = @_;
176     my $tc = $self->__type_constraint;
177     my $class;
178     if ($tc->can('is_subtype_of')) { # Union can't
179         my $search_tc = $tc;
180         while (1) {
181             if ($search_tc->isa('Moose::Meta::TypeConstraint::Class')) {
182                 $class = $search_tc->class;
183                 last;
184             }
185             $search_tc = $search_tc->parent;
186             last unless $search_tc->is_subtype_of('Object');
187         }
188     }
189         
190     my $inv = (
191         $class
192             ? (
193                 $method eq 'new' || $class->can($method)
194                     ? $class
195                     : $tc
196               )
197             : $tc
198     );
199     $inv->$method(@args);
200 }
201
202 =head1 LICENSE
203
204 This program is free software; you can redistribute it and/or modify
205 it under the same terms as perl itself.
206
207 =cut
208
209 1;