File Coverage

blib/lib/Data/Object/Library.pm
Criterion Covered Total %
statement 57 64 89.0
branch 8 22 36.3
condition 0 21 0.0
subroutine 10 12 83.3
pod 0 1 0.0
total 75 120 62.5


line stmt bran cond sub pod time code
1             # ABSTRACT: Data::Object Type Library for Perl 5
2             package Data::Object::Library;
3              
4 12     12   410695 use 5.10.0;
  12         46  
  12         562  
5              
6 12     12   65 use strict;
  12         20  
  12         436  
7 12     12   75 use warnings;
  12         21  
  12         449  
8              
9 12     12   7582 use Type::Library -base;
  12         223398  
  12         163  
10 12     12   11663 use Type::Utils -all;
  12         52856  
  12         163  
11              
12 12     12   35140 use Data::Object;
  12         1805  
  12         6737  
13              
14             our $VERSION = '0.04'; # VERSION
15              
16             extends 'Types::Standard';
17             extends 'Types::Common::Numeric';
18             extends 'Types::Common::String';
19              
20             my $registry = __PACKAGE__->meta;
21              
22             my $constraint_name;
23             my $constraint_code;
24              
25             my $coercion_name;
26             my $coercion_code;
27              
28             my $explanation_name;
29             my $explanation_code;
30              
31             sub DECLARE {
32 492     492 0 1472 my ($name, %opts) = @_;
33              
34 492 100       1577 return map +(DECLARE($_, %opts)), @$name if ref $name;
35              
36 360         1016 ($opts{name} = $name) =~ s/:://g;
37              
38 360 50 0     1061 my @cans = ref($opts{can}) eq 'ARRAY' ? @{$opts{can}} : $opts{can} // ();
  360         973  
39 360 50 0     884 my @isas = ref($opts{isa}) eq 'ARRAY' ? @{$opts{isa}} : $opts{isa} // ();
  360         725  
40 360 50 0     855 my @does = ref($opts{does}) eq 'ARRAY' ? @{$opts{does}} : $opts{does} // ();
  360         697  
41              
42 360         488 my $code = $opts{constraint};
43 360         431 my $text = $opts{inlined};
44              
45             $opts{constraint} = sub {
46 0     0   0 my @args = @_;
47 0 0 0     0 return if @isas and grep(not($args[0]->isa($_)), @isas);
48 0 0 0     0 return if @cans and grep(not($args[0]->can($_)), @cans);
49 0 0 0     0 return if @does and grep(not($args[0]->does($_)), @does);
50 0 0 0     0 return if $code and not $code->(@args);
51 0         0 return 1;
52 360         1871 };
53             $opts{inlined} = sub {
54 364     364   208772 my $blessed = "Scalar::Util::blessed($_[1])";
55 364 50       7446 return join(' && ', map "($_)",
56             join(' && ', map "($blessed and $_[1]->isa('$_'))", @isas),
57             join(' && ', map "($blessed and $_[1]->does('$_'))", @does),
58             join(' && ', map "($blessed and $_[1]->can('$_'))", @cans),
59             $text ? $text : (),
60             );
61 360         1832 };
62              
63 360         624 $opts{bless} = "Type::Tiny";
64 360 50       954 $opts{parent} = "Object" unless $opts{parent};
65 360         468 $opts{coerion} = 1;
66              
67 12     12   121 { no warnings "numeric"; $opts{_caller_level}++ }
  12         21  
  12         20667  
  360         396  
  360         587  
68              
69 360         631 my $coerce = delete $opts{coerce};
70 360         1532 my $type = declare(%opts);
71              
72 360         134536 my $functions = {
73             'Data::Object::Array' => 'data_array',
74             'Data::Object::Code' => 'data_code',
75             'Data::Object::Float' => 'data_float',
76             'Data::Object::Hash' => 'data_hash',
77             'Data::Object::Integer' => 'data_integer',
78             'Data::Object::Number' => 'data_number',
79             'Data::Object::Regexp' => 'data_regexp',
80             'Data::Object::Scalar' => 'data_scalar',
81             'Data::Object::String' => 'data_string',
82             'Data::Object::Undef' => 'data_undef',
83             'Data::Object::Universal' => 'data_universal',
84             };
85              
86 360         753 my ($key) = grep { $functions->{$_} } @isas;
  360         986  
87              
88 360 50       1317 for my $coercive ('ARRAY' eq ref $coerce ? @$coerce : $coerce) {
89 744         4239 my $object = $registry->get_type($coercive);
90 744         8305 my $function = $$functions{$key};
91              
92 744         3330 my $forward = Data::Object->can($function);
93 744     8   3890 coerce $opts{name}, from $coercive, via { $forward->($_) };
  8         4866  
94              
95 744         100658 $object->coercion->i_really_want_to_unfreeze;
96              
97 744         7566 my $reverse = Data::Object->can('deduce_deep');
98 744     0   3793 coerce $coercive, from $opts{name}, via { $reverse->($_) };
  0         0  
99              
100 744         70777 $object->coercion->freeze;
101             }
102              
103 360         6153 return $type;
104             }
105              
106             $constraint_name = 'constraint_generator';
107             $constraint_code = sub {
108             my $param = @_ ? Types::TypeTiny::to_TypeTiny(shift) :
109             return $registry->get_type('ArrayObject');
110              
111             Types::TypeTiny::TypeTiny->check($param)
112             or Types::Standard::_croak(
113             "Parameter to ArrayObject[`a] expected ".
114             "to be a type constraint; got $param"
115             );
116              
117             return sub {
118             my $arrayobj = shift;
119             $param->check($_) || return for @$arrayobj;
120             return !!1;
121             }
122             };
123              
124             $coercion_name = 'coercion_generator';
125             $coercion_code = sub {
126             my ($parent, $child, $param) = @_;
127              
128             return $parent->coercion unless $param->has_coercion;
129              
130             my $coercable_item = $param->coercion->_source_type_union;
131             my $c = "Type::Coercion"->new(type_constraint => $child);
132              
133             $c->add_type_coercions(
134             $registry->get_type('ArrayRef') => sub {
135             my $value = @_ ? $_[0] : $_;
136             my $new = [];
137              
138             for (my $i=0; $i < @$value; $i++) {
139             my $item = $value->[$i];
140             return $value unless $coercable_item->check($item);
141             $new->[$i] = $param->coerce($item);
142             }
143              
144             return $parent->coerce($new);
145             },
146             );
147              
148             return $c;
149             };
150              
151             $explanation_name = 'deep_explanation';
152             $explanation_code = sub {
153             my ($type, $value, $varname) = @_;
154             my $param = $type->parameters->[0];
155              
156             for my $i (0 .. $#$value) {
157             my $item = $value->[$i];
158             next if $param->check($item);
159             my $message = '"%s" constrains each value in the array object with "%s"';
160             my $position = sprintf('%s->[%d]', $varname, $i);
161             my $criteria = $param->validate_explain($item, $position);
162             return [sprintf($message, $type, $param), @{$criteria}]
163             }
164              
165             return;
166             };
167              
168             my @with_array_extras = (
169             $constraint_name => $constraint_code,
170             $coercion_name => $coercion_code,
171             $explanation_name => $explanation_code,
172             );
173              
174             DECLARE ["ArrayObj", "ArrayObject"] => (@with_array_extras,
175             isa => ["Data::Object::Array"],
176             does => ["Data::Object::Role::Array"],
177             can => ["data", "dump"],
178             coerce => ["ArrayRef"],
179             );
180              
181             DECLARE ["CodeObj", "CodeObject"] => (
182             isa => ["Data::Object::Code"],
183             does => ["Data::Object::Role::Code"],
184             can => ["data", "dump"],
185             coerce => ["CodeRef"],
186             );
187              
188             DECLARE ["FloatObj", "FloatObject"] => (
189             isa => ["Data::Object::Float"],
190             does => ["Data::Object::Role::Float"],
191             can => ["data", "dump"],
192             coerce => ["Str", "Num", "LaxNum"],
193             );
194              
195             $constraint_name = 'constraint_generator';
196             $constraint_code = sub {
197             my $param = @_ ? Types::TypeTiny::to_TypeTiny(shift) :
198             return $registry->get_type('HashObject');
199              
200             Types::TypeTiny::TypeTiny->check($param)
201             or Types::Standard::_croak(
202             "Parameter to HashObject[`a] expected ".
203             "to be a type constraint; got $param"
204             );
205              
206             return sub {
207             my $hashobj = shift;
208             $param->check($_) || return for values %$hashobj;
209             return !!1;
210             }
211             };
212              
213             $coercion_name = 'coercion_generator';
214             $coercion_code = sub {
215             my ($parent, $child, $param) = @_;
216              
217             return $parent->coercion unless $param->has_coercion;
218              
219             my $coercable_item = $param->coercion->_source_type_union;
220             my $c = "Type::Coercion"->new(type_constraint => $child);
221              
222             $c->add_type_coercions(
223             $registry->get_type('HashRef') => sub {
224             my $value = @_ ? $_[0] : $_;
225             my $new = {};
226              
227             for my $key (sort keys %$value) {
228             my $item = $value->{$key};
229             return $value unless $coercable_item->check($item);
230             $new->{$key} = $param->coerce($item);
231             }
232              
233             return $parent->coerce($new);
234             },
235             );
236              
237             return $c;
238             };
239              
240             $explanation_name = 'deep_explanation';
241             $explanation_code = sub {
242             my ($type, $value, $varname) = @_;
243             my $param = $type->parameters->[0];
244              
245             for my $key (sort keys %$value) {
246             my $item = $value->{$key};
247             next if $param->check($item);
248             my $message = '"%s" constrains each value in the hash object with "%s"';
249             my $position = sprintf('%s->{%s}', $varname, B::perlstring($key));
250             my $criteria = $param->validate_explain($item, $position);
251             return [sprintf($message, $type, $param), @{$criteria}]
252             }
253              
254             return;
255             };
256              
257             my $overrides_name = 'my_methods';
258             my $overrides_opts = {
259             hashref_allows_key => sub {
260             my ($self, $key) = @_;
261             $registry->get_type('Str')->check($key);
262             },
263             hashref_allows_value => sub {
264             my ($self, $key, $value) = @_;
265              
266             return !!0 unless $self->my_hashref_allows_key($key);
267             return !!1 if $self == $registry->get_type('HashRef');
268              
269             my $href = $self->find_parent(sub {
270             $_->has_parent && $_->parent == $registry->get_type('HashRef')
271             });
272              
273             my $param = $href->type_parameter;
274             $registry->get_type('Str')->check($key) and $param->check($value);
275             },
276             };
277              
278             my @with_hash_extras = (
279             $constraint_name => $constraint_code,
280             $coercion_name => $coercion_code,
281             $explanation_name => $explanation_code,
282             $overrides_name => $overrides_opts,
283             );
284              
285             DECLARE ["HashObj", "HashObject"] => (@with_hash_extras,
286             isa => ["Data::Object::Hash"],
287             does => ["Data::Object::Role::Hash"],
288             can => ["data", "dump"],
289             coerce => ["HashRef"],
290             );
291              
292             DECLARE ["IntObj", "IntObject", "IntegerObj", "IntegerObject"] => (
293             isa => ["Data::Object::Integer"],
294             does => ["Data::Object::Role::Integer"],
295             can => ["data", "dump"],
296             coerce => ["Str", "Num", "LaxNum", "StrictNum", "Int"],
297             );
298              
299             DECLARE ["NumObj", "NumObject", "NumberObj", "NumberObject"] => (
300             isa => ["Data::Object::Number"],
301             does => ["Data::Object::Role::Number"],
302             can => ["data", "dump"],
303             coerce => ["Str", "Num", "LaxNum", "StrictNum"],
304             );
305              
306             DECLARE ["RegexpObj", "RegexpObject"] => (
307             isa => ["Data::Object::Regexp"],
308             does => ["Data::Object::Role::Regexp"],
309             can => ["data", "dump"],
310             coerce => ["RegexpRef"],
311             );
312              
313             DECLARE ["ScalarObj", "ScalarObject"] => (
314             isa => ["Data::Object::Scalar"],
315             does => ["Data::Object::Role::Scalar"],
316             can => ["data", "dump"],
317             coerce => ["ScalarRef"],
318             );
319              
320             DECLARE ["StrObj", "StrObject", "StringObj", "StringObject"] => (
321             isa => ["Data::Object::String"],
322             does => ["Data::Object::Role::String"],
323             can => ["data", "dump"],
324             coerce => ["Str"],
325             );
326              
327             DECLARE ["UndefObj", "UndefObject"] => (
328             isa => ["Data::Object::Undef"],
329             does => ["Data::Object::Role::Undef"],
330             can => ["data", "dump"],
331             coerce => ["Undef"],
332             );
333              
334             DECLARE ["AnyObj", "AnyObject", "UniversalObj", "UniversalObject"] => (
335             isa => ["Data::Object::Universal"],
336             does => ["Data::Object::Role::Universal"],
337             can => ["data", "dump"],
338             coerce => ["Any"],
339             );
340              
341             1;
342              
343             __END__