File Coverage

blib/lib/Variable/Strongly/Typed.pm
Criterion Covered Total %
statement 87 121 71.9
branch 6 26 23.0
condition 1 3 33.3
subroutine 25 30 83.3
pod 4 4 100.0
total 123 184 66.8


line stmt bran cond sub pod time code
1             package Variable::Strongly::Typed;
2              
3 7     7   215087 use version; $VERSION = qv('1.1.0');
  7         17011  
  7         45  
4              
5 7     7   588 use warnings;
  7         16  
  7         208  
6 7     7   37 use strict;
  7         18  
  7         193  
7 7     7   37 use Carp;
  7         12  
  7         677  
8              
9             # Tie any 'TYPE' attributes to me
10 7     7   7190 use Attribute::Handlers;
  7         41986  
  7         45  
11              
12             # Our 3 friends for scalars & arrays & hashes
13 7     7   4858 use Variable::Strongly::Typed::Scalar;
  7         18  
  7         351  
14 7     7   4244 use Variable::Strongly::Typed::Array;
  7         16  
  7         191  
15 7     7   10153 use Variable::Strongly::Typed::Hash;
  7         21  
  7         221  
16 7     7   4261 use Variable::Strongly::Typed::Validators;
  7         17  
  7         221  
17 7     7   4373 use Variable::Strongly::Typed::Code;
  7         17  
  7         266  
18              
19             # Can't use Class::Std as it wants the 'ATTR' attribute
20             # as does Attribute::Handlers... Bummer
21 7     7   6294 use Class::Std::Utils;
  7         4613  
  7         38  
22             {
23             # class variables
24             my %actual_object_of; # The actual object ref
25             my %validator_sub_of; # Validate subroutine
26             my %type_of; # Strong Type of this variable
27             my %error_handler_of; # Error handling function to call when
28             # assignment/return goes wrong
29              
30             do {
31 7     7   545 no warnings 'redefine';
  7         13  
  7         618  
32             sub UNIVERSAL::TYPE : ATTR(SCALAR) {
33 3     3 1 6231 my ($package, $symbol, $referent, $attr, $data) = @_;
34            
35 3         28 tie $$referent, 'Variable::Strongly::Typed::Scalar', $data;
36 0         0 return;
37 7     7   35 }
  7         18  
  7         60  
38            
39             sub UNIVERSAL::TYPE : ATTR(ARRAY) {
40 1     1 1 1702 my ($package, $symbol, $referent, $attr, $data) = @_;
41            
42 1         10 tie @$referent, 'Variable::Strongly::Typed::Array', $data;
43 0         0 return;
44 7     7   3015 }
  7         13  
  7         27  
45            
46             sub UNIVERSAL::TYPE : ATTR(HASH) {
47 1     1 1 2456 my ($package, $symbol, $referent, $attr, $data) = @_;
48            
49 1         13 tie %$referent, 'Variable::Strongly::Typed::Hash', $data;
50 0         0 return;
51 7     7   7207 }
  7         28  
  7         30  
52              
53             sub UNIVERSAL::TYPE : ATTR(CODE) {
54 2     2 1 8308 my ($package, $symbol, $referent, $attr, $data) = @_;
55              
56 2         12 Variable::Strongly::Typed::Code->new($symbol, $referent, $data);
57 1         3 return;
58            
59 7     7   2213 }
  7         16  
  7         30  
60             };
61              
62             # Set up our class variables
63             sub _init {
64 7     7   20 my($self, $object, $type) = @_;
65 7         66 my $ident = ident $self;
66              
67 7         14 my $error_handler;
68              
69 7 50       38 if (ref $type eq 'ARRAY') {
70 7         19 ($type, $error_handler) = @$type;
71 7 100       1633 croak("Error handler must be a CODE ref!")
72             if (ref $error_handler ne 'CODE');
73             }
74              
75 1         3 $actual_object_of {$ident} = $object;
76 1         4 $validator_sub_of {$ident} = _make_validator_sub($type);
77 1         3 $type_of {$ident} = $type;
78 1         3 $error_handler_of {$ident} = $error_handler;
79              
80 1         3 return $self;
81             }
82              
83             # Get actual object ref
84             sub _get_object {
85 7     7   17 my($self) = @_;
86 7         39 return $actual_object_of{ident $self};
87             }
88              
89             sub _get_type {
90 1     1   3 my($self) = @_;
91 1         12 return $type_of{ident $self};
92             }
93              
94             sub _get_valid_sub {
95 0     0   0 my($self) = @_;
96 0         0 return $validator_sub_of{ident $self};
97             }
98              
99             sub _get_error_handler {
100 0     0   0 my($self) = @_;
101 0         0 return $error_handler_of{ident $self};
102             }
103              
104             sub _error {
105 0     0   0 my($self, $val) = @_;
106 0         0 my $eh = $self->_get_error_handler;
107              
108 0 0       0 if ($eh) {
109 0         0 $eh->($val);
110             } else {
111 0         0 croak($self->_make_error_message($val));
112             }
113             }
114              
115             # Check if a value is okay...
116             # IF this returns w/o croaking then all is okay...
117             sub _check_values {
118 0     0   0 my ($self, @values) = @_;
119              
120 0         0 my($valid_sub) = $self->_get_valid_sub;
121              
122             # if valid or failed sub says it's okay then ok
123             # otherwise undef
124             # Note this works slightly differently then Tie::Constrained
125             # as we have no notion of an 'original' or 'default' value
126             # 'failed' sub will croak if not valid...
127 0         0 foreach my $val (@values) {
128              
129             # This'll croak (by default) if a value is not valid
130 0 0       0 $valid_sub->($val) || $self->_error($val);
131             }
132              
133             # if we got this far all must be good
134 0         0 return \@values;
135             }
136              
137             sub _make_error_message {
138 0     0   0 my($self, $val) = @_;
139              
140             # 'num', 'string', 'IO::File' or CODE ref if user defined
141 0         0 my $valid_type = $self->_get_type;
142              
143             # Scalar, Array, or Hash
144 0         0 my ($variable_type) = $self =~ /:: ([^:=]+) =/xms;
145 0         0 $variable_type = lc $variable_type;
146              
147             # String-ify value
148 0 0       0 if (ref $val) {
149 0         0 $val = (ref $val) . ' reference';
150             } else {
151 0         0 $val = "'$val'";
152             }
153              
154 0 0       0 my $ww = $variable_type eq 'code' ? 'return' : 'assign';
155 0 0       0 my $from_or_to = $variable_type eq 'code' ? 'from' : 'to';
156 0 0       0 $variable_type = 'function or method' if $variable_type eq 'code';
157              
158 0         0 my $msg;
159 0 0       0 if (ref $valid_type eq 'CODE') {
160 0         0 $msg = "Cannot $ww $val $from_or_to a user-validated"
161             . " $variable_type variable!";
162             } else {
163 0 0       0 $msg = "Cannot $ww $val $from_or_to a"
164             . ($variable_type eq 'array' ? 'n' : '')
165             . " $variable_type of type $valid_type!";
166             }
167              
168 0         0 return $msg;
169             }
170              
171             sub _make_validator_sub {
172 1     1   3 my($val) = @_;
173              
174             # User supplied??
175 1 50 33     11 return $val if ($val && (ref($val) eq 'CODE'));
176              
177 1         2 my $condition;
178              
179             # Stuff like 'int', 'string', 'float' ... 'primitive' types
180 1         4 $condition = $Variable::Strongly::Typed::Validators::conditions{$val};
181              
182 1 50       3 unless ($condition) {
183             # It's either SCALAR, ARRAY, HASH the exact object type
184             # or an isa... A reference type basically
185 0         0 $condition =
186             'ref && ((ref eq ' . "'$val'" . ')'
187             . ' || (ref ne "SCALAR" && ref ne "ARRAY"'
188             . ' && ref ne "HASH" && $_->isa(' . "'$val'" . ')))';
189             }
190              
191             # Concoct validate sub
192 1         136 my $sub =
193             eval 'sub { local $_ = shift;' . ' !$_ || (' . $condition . ') }';
194              
195 1 50       4 if ($@) {
196 0         0 croak("Unable to create a Strongly Typed $val!!");
197             }
198              
199 1         4 return $sub;
200             }
201              
202             # Clean Up, Clean Up, Everybody's Doing Some, Clean Up
203             # -Walter (26 months)
204             sub DESTROY {
205 6     6   953 my($self) = @_;
206 6         25 my $ident = ident $self;
207              
208 6         47 my $obj = $self->_get_object;
209 6         13 undef $obj;
210 6         18 delete $actual_object_of{$ident};
211 6         11 delete $validator_sub_of{$ident};
212 6         1971 delete $type_of {$ident};
213             }
214              
215             }
216              
217             1; # Magic true value required at end of module
218             __END__