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   327105 use 5.10.0;
  12         41  
  12         477  
5              
6 12     12   58 use strict;
  12         19  
  12         354  
7 12     12   60 use warnings;
  12         17  
  12         380  
8              
9 12     12   6201 use Type::Library -base;
  12         186916  
  12         125  
10 12     12   10786 use Type::Utils -all;
  12         47520  
  12         128  
11              
12 12     12   29918 use Data::Object;
  12         1831  
  12         4573  
13              
14             our $VERSION = '0.05'; # VERSION
15              
16             extends 'Types::Standard';
17             extends 'Types::Common::Numeric';
18             extends 'Types::Common::String';
19              
20             my $registry = __PACKAGE__->meta;
21              
22             sub DECLARE {
23 492     492 0 1240 my ($name, %opts) = @_;
24              
25 492 100       1379 return map +(DECLARE($_, %opts)), @$name if ref $name;
26              
27 360         836 ($opts{name} = $name) =~ s/:://g;
28              
29 360 50 0     787 my @cans = ref($opts{can}) eq 'ARRAY' ? @{$opts{can}} : $opts{can} // ();
  360         828  
30 360 50 0     779 my @isas = ref($opts{isa}) eq 'ARRAY' ? @{$opts{isa}} : $opts{isa} // ();
  360         625  
31 360 50 0     652 my @does = ref($opts{does}) eq 'ARRAY' ? @{$opts{does}} : $opts{does} // ();
  360         571  
32              
33 360         413 my $code = $opts{constraint};
34 360         330 my $text = $opts{inlined};
35              
36             $opts{constraint} = sub {
37 0     0   0 my @args = @_;
38 0 0 0     0 return if @isas and grep(not($args[0]->isa($_)), @isas);
39 0 0 0     0 return if @cans and grep(not($args[0]->can($_)), @cans);
40 0 0 0     0 return if @does and grep(not($args[0]->does($_)), @does);
41 0 0 0     0 return if $code and not $code->(@args);
42 0         0 return 1;
43 360         1567 };
44             $opts{inlined} = sub {
45 364     364   176632 my $blessed = "Scalar::Util::blessed($_[1])";
46 364 50       6413 return join(' && ', map "($_)",
47             join(' && ', map "($blessed and $_[1]->isa('$_'))", @isas),
48             join(' && ', map "($blessed and $_[1]->does('$_'))", @does),
49             join(' && ', map "($blessed and $_[1]->can('$_'))", @cans),
50             $text ? $text : (),
51             );
52 360         1491 };
53              
54 360         472 $opts{bless} = "Type::Tiny";
55 360 50       792 $opts{parent} = "Object" unless $opts{parent};
56 360         434 $opts{coerion} = 1;
57              
58 12     12   65 { no warnings "numeric"; $opts{_caller_level}++ }
  12         19  
  12         17217  
  360         349  
  360         478  
59              
60 360         518 my $coerce = delete $opts{coerce};
61 360         1349 my $type = declare(%opts);
62              
63 360         118214 my $functions = {
64             'Data::Object::Array' => 'data_array',
65             'Data::Object::Code' => 'data_code',
66             'Data::Object::Float' => 'data_float',
67             'Data::Object::Hash' => 'data_hash',
68             'Data::Object::Integer' => 'data_integer',
69             'Data::Object::Number' => 'data_number',
70             'Data::Object::Regexp' => 'data_regexp',
71             'Data::Object::Scalar' => 'data_scalar',
72             'Data::Object::String' => 'data_string',
73             'Data::Object::Undef' => 'data_undef',
74             'Data::Object::Universal' => 'data_universal',
75             };
76              
77 360         600 my ($key) = grep { $functions->{$_} } @isas;
  360         826  
78              
79 360 50       1141 for my $coercive ('ARRAY' eq ref $coerce ? @$coerce : $coerce) {
80 744         3748 my $object = $registry->get_type($coercive);
81 744         7105 my $function = $$functions{$key};
82              
83 744         2845 my $forward = Data::Object->can($function);
84 744     8   3165 coerce $opts{name}, from $coercive, via { $forward->($_) };
  8         2641  
85              
86 744         88669 $object->coercion->i_really_want_to_unfreeze;
87              
88 744         6647 my $reverse = Data::Object->can('deduce_deep');
89 744     0   3280 coerce $coercive, from $opts{name}, via { $reverse->($_) };
  0         0  
90              
91 744         64210 $object->coercion->freeze;
92             }
93              
94 360         5107 return $type;
95             }
96              
97             my $array_constraint_name = 'constraint_generator';
98             my $array_constraint_code = sub {
99             my $param = @_ ? Types::TypeTiny::to_TypeTiny(shift) :
100             return $registry->get_type('ArrayObject');
101              
102             Types::TypeTiny::TypeTiny->check($param)
103             or Types::Standard::_croak(
104             "Parameter to ArrayObject[`a] expected ".
105             "to be a type constraint; got $param"
106             );
107              
108             return sub {
109             my $arrayobj = shift;
110             $param->check($_) || return for @$arrayobj;
111             return !!1;
112             }
113             };
114              
115             my $array_coercion_name = 'coercion_generator';
116             my $array_coercion_code = sub {
117             my ($parent, $child, $param) = @_;
118              
119             return $parent->coercion unless $param->has_coercion;
120              
121             my $coercable_item = $param->coercion->_source_type_union;
122             my $c = "Type::Coercion"->new(type_constraint => $child);
123              
124             $c->add_type_coercions(
125             $registry->get_type('ArrayRef') => sub {
126             my $value = @_ ? $_[0] : $_;
127             my $new = [];
128              
129             for (my $i=0; $i < @$value; $i++) {
130             my $item = $value->[$i];
131             return $value unless $coercable_item->check($item);
132             $new->[$i] = $param->coerce($item);
133             }
134              
135             return $parent->coerce($new);
136             },
137             );
138              
139             return $c;
140             };
141              
142             my $array_explanation_name = 'deep_explanation';
143             my $array_explanation_code = sub {
144             my ($type, $value, $varname) = @_;
145             my $param = $type->parameters->[0];
146              
147             for my $i (0 .. $#$value) {
148             my $item = $value->[$i];
149             next if $param->check($item);
150             my $message = '"%s" constrains each value in the array object with "%s"';
151             my $position = sprintf('%s->[%d]', $varname, $i);
152             my $criteria = $param->validate_explain($item, $position);
153             return [sprintf($message, $type, $param), @{$criteria}]
154             }
155              
156             return;
157             };
158              
159             my @with_array_extras = (
160             $array_constraint_name => $array_constraint_code,
161             $array_coercion_name => $array_coercion_code,
162             $array_explanation_name => $array_explanation_code,
163             );
164              
165             DECLARE ["ArrayObj", "ArrayObject"] => (@with_array_extras,
166             isa => ["Data::Object::Array"],
167             does => ["Data::Object::Role::Array"],
168             can => ["data", "dump"],
169             coerce => ["ArrayRef"],
170             );
171              
172             DECLARE ["CodeObj", "CodeObject"] => (
173             isa => ["Data::Object::Code"],
174             does => ["Data::Object::Role::Code"],
175             can => ["data", "dump"],
176             coerce => ["CodeRef"],
177             );
178              
179             DECLARE ["FloatObj", "FloatObject"] => (
180             isa => ["Data::Object::Float"],
181             does => ["Data::Object::Role::Float"],
182             can => ["data", "dump"],
183             coerce => ["Str", "Num", "LaxNum"],
184             );
185              
186             my $hash_constraint_name = 'constraint_generator';
187             my $hash_constraint_code = sub {
188             my $param = @_ ? Types::TypeTiny::to_TypeTiny(shift) :
189             return $registry->get_type('HashObject');
190              
191             Types::TypeTiny::TypeTiny->check($param)
192             or Types::Standard::_croak(
193             "Parameter to HashObject[`a] expected ".
194             "to be a type constraint; got $param"
195             );
196              
197             return sub {
198             my $hashobj = shift;
199             $param->check($_) || return for values %$hashobj;
200             return !!1;
201             }
202             };
203              
204             my $hash_coercion_name = 'coercion_generator';
205             my $hash_coercion_code = sub {
206             my ($parent, $child, $param) = @_;
207              
208             return $parent->coercion unless $param->has_coercion;
209              
210             my $coercable_item = $param->coercion->_source_type_union;
211             my $c = "Type::Coercion"->new(type_constraint => $child);
212              
213             $c->add_type_coercions(
214             $registry->get_type('HashRef') => sub {
215             my $value = @_ ? $_[0] : $_;
216             my $new = {};
217              
218             for my $key (sort keys %$value) {
219             my $item = $value->{$key};
220             return $value unless $coercable_item->check($item);
221             $new->{$key} = $param->coerce($item);
222             }
223              
224             return $parent->coerce($new);
225             },
226             );
227              
228             return $c;
229             };
230              
231             my $hash_explanation_name = 'deep_explanation';
232             my $hash_explanation_code = sub {
233             my ($type, $value, $varname) = @_;
234             my $param = $type->parameters->[0];
235              
236             for my $key (sort keys %$value) {
237             my $item = $value->{$key};
238             next if $param->check($item);
239             my $message = '"%s" constrains each value in the hash object with "%s"';
240             my $position = sprintf('%s->{%s}', $varname, B::perlstring($key));
241             my $criteria = $param->validate_explain($item, $position);
242             return [sprintf($message, $type, $param), @{$criteria}]
243             }
244              
245             return;
246             };
247              
248             my $hash_overrides_name = 'my_methods';
249             my $hash_overrides_opts = {
250             hashref_allows_key => sub {
251             my ($self, $key) = @_;
252             $registry->get_type('Str')->check($key);
253             },
254             hashref_allows_value => sub {
255             my ($self, $key, $value) = @_;
256              
257             return !!0 unless $self->my_hashref_allows_key($key);
258             return !!1 if $self == $registry->get_type('HashRef');
259              
260             my $href = $self->find_parent(sub {
261             $_->has_parent && $_->parent == $registry->get_type('HashRef')
262             });
263              
264             my $param = $href->type_parameter;
265             $registry->get_type('Str')->check($key) and $param->check($value);
266             },
267             };
268              
269             my @with_hash_extras = (
270             $hash_constraint_name => $hash_constraint_code,
271             $hash_coercion_name => $hash_coercion_code,
272             $hash_explanation_name => $hash_explanation_code,
273             $hash_overrides_name => $hash_overrides_opts,
274             );
275              
276             DECLARE ["HashObj", "HashObject"] => (@with_hash_extras,
277             isa => ["Data::Object::Hash"],
278             does => ["Data::Object::Role::Hash"],
279             can => ["data", "dump"],
280             coerce => ["HashRef"],
281             );
282              
283             DECLARE ["IntObj", "IntObject", "IntegerObj", "IntegerObject"] => (
284             isa => ["Data::Object::Integer"],
285             does => ["Data::Object::Role::Integer"],
286             can => ["data", "dump"],
287             coerce => ["Str", "Num", "LaxNum", "StrictNum", "Int"],
288             );
289              
290             DECLARE ["NumObj", "NumObject", "NumberObj", "NumberObject"] => (
291             isa => ["Data::Object::Number"],
292             does => ["Data::Object::Role::Number"],
293             can => ["data", "dump"],
294             coerce => ["Str", "Num", "LaxNum", "StrictNum"],
295             );
296              
297             DECLARE ["RegexpObj", "RegexpObject"] => (
298             isa => ["Data::Object::Regexp"],
299             does => ["Data::Object::Role::Regexp"],
300             can => ["data", "dump"],
301             coerce => ["RegexpRef"],
302             );
303              
304             DECLARE ["ScalarObj", "ScalarObject"] => (
305             isa => ["Data::Object::Scalar"],
306             does => ["Data::Object::Role::Scalar"],
307             can => ["data", "dump"],
308             coerce => ["ScalarRef"],
309             );
310              
311             DECLARE ["StrObj", "StrObject", "StringObj", "StringObject"] => (
312             isa => ["Data::Object::String"],
313             does => ["Data::Object::Role::String"],
314             can => ["data", "dump"],
315             coerce => ["Str"],
316             );
317              
318             DECLARE ["UndefObj", "UndefObject"] => (
319             isa => ["Data::Object::Undef"],
320             does => ["Data::Object::Role::Undef"],
321             can => ["data", "dump"],
322             coerce => ["Undef"],
323             );
324              
325             DECLARE ["AnyObj", "AnyObject", "UniversalObj", "UniversalObject"] => (
326             isa => ["Data::Object::Universal"],
327             does => ["Data::Object::Role::Universal"],
328             can => ["data", "dump"],
329             coerce => ["Any"],
330             );
331              
332             1;
333              
334             __END__