File Coverage

blib/lib/Data/Record/Serialize/Role/Base.pm
Criterion Covered Total %
statement 142 143 99.3
branch 57 64 89.0
condition 7 8 87.5
subroutine 34 36 94.4
pod 6 7 85.7
total 246 258 95.3


line stmt bran cond sub pod time code
1             package Data::Record::Serialize::Role::Base;
2              
3             # ABSTRACT: Base Role for Data::Record::Serialize
4              
5 17     17   137022 use v5.12;
  17         78  
6 17     17   135 use Moo::Role;
  17         50  
  17         114  
7              
8             our $VERSION = '1.05';
9              
10 17     17   9025 use Data::Record::Serialize::Error { errors => [ 'fields', 'types' ] }, -all;
  17         54  
  17         397  
11              
12 17     17   11810 use Data::Record::Serialize::Util -all;
  17         49  
  17         168  
13              
14 17     17   35702 use Types::Standard qw[ ArrayRef CodeRef CycleTuple HashRef Enum Str Bool is_HashRef Maybe ];
  17         1765230  
  17         248  
15 17     17   73627 use Data::Record::Serialize::Types qw( SerializeType );
  17         949  
  17         170  
16              
17 17     17   17884 use Ref::Util qw( is_coderef is_arrayref );
  17         10621  
  17         1468  
18 17     17   163 use List::Util 1.33 qw( any );
  17         436  
  17         1189  
19              
20 17     17   166 use POSIX ();
  17         57  
  17         375  
21              
22 17     17   125 use namespace::clean;
  17         94  
  17         169  
23              
24              
25              
26              
27              
28              
29              
30              
31              
32              
33              
34              
35              
36              
37              
38              
39             has types => (
40             is => 'rwp',
41             isa => ( HashRef [SerializeType] | CycleTuple [ Str, SerializeType ] )
42             , # need parens for perl <= 5.12.5
43             predicate => 1,
44             trigger => sub {
45             $_[0]->clear_type_index;
46             $_[0]->clear_output_types;
47             },
48             );
49              
50              
51              
52              
53              
54              
55              
56              
57              
58              
59              
60              
61              
62             has default_type => (
63             is => 'ro',
64             isa => SerializeType,
65             predicate => 1,
66             );
67              
68              
69              
70              
71              
72              
73              
74              
75              
76              
77              
78             has fields => (
79             is => 'rwp',
80             isa => ( ArrayRef [Str] | Enum ['all'] ), # need parens for perl <= 5.12.5
81             predicate => 1,
82             clearer => 1,
83             trigger => sub {
84             $_[0]->_clear_fieldh;
85             $_[0]->clear_output_types;
86             $_[0]->clear_output_fields;
87             },
88             );
89              
90              
91             # for quick lookup of field names
92             has _fieldh => (
93             is => 'lazy',
94             init_arg => undef,
95             clearer => 1,
96             builder => sub {
97 52     52   566 my $self = shift;
98 52         123 my %fieldh;
99 52         130 @fieldh{ @{ $self->fields } } = ();
  52         222  
100 52         282 return \%fieldh;
101             },
102             );
103              
104              
105              
106              
107              
108              
109              
110              
111              
112              
113              
114             has output_fields => (
115             is => 'lazy',
116             trigger => 1,
117             clearer => 1,
118             builder => sub {
119 18     18   20987 my $self = shift;
120 18   66     36 [ map { $self->rename_fields->{$_} // $_ } @{ $self->fields } ];
  42         367  
  18         60  
121             },
122             init_arg => undef,
123             );
124              
125             # something for other roles to wrap.
126       0     sub _trigger_output_fields { }
127              
128             has _run_setup => (
129             is => 'rwp',
130             isa => Bool,
131             init_args => undef,
132             default => 1,
133             );
134              
135              
136             # have we initialized types? can't simply use $self->has_types, as
137             # the caller may have provided some.
138             has _have_initialized_types => (
139             is => 'rwp',
140             init_arg => undef,
141             isa => Bool,
142             default => 0,
143             );
144              
145             has _boolify => (
146             is => 'lazy',
147             isa => Bool,
148             init_arg => undef,
149 48 100   48   1365 builder => sub { $_[0]->_can_bool || $_[0]->_convert_boolean_to_int },
150             );
151              
152             has _convert_boolean_to_int => (
153             is => 'rwp',
154             default => 0,
155             );
156              
157             has _can_bool => (
158             is => 'lazy',
159             isa => Bool,
160             init_arg => undef,
161 48     48   1832 builder => sub { !!$_[0]->can( 'to_bool' ) },
162             );
163              
164              
165              
166              
167              
168              
169              
170              
171              
172              
173              
174              
175              
176              
177              
178              
179              
180             sub _build_field_list_with_type {
181 32     32   367 my ( $self, $list_spec, $type, $error_label ) = @_;
182              
183 32         520 my $fieldh = $self->_fieldh;
184 32         251 my $list = do {
185 32 100       145 if ( is_coderef( $list_spec ) ) {
    100          
186 3         41 ( ArrayRef [Str] )->assert_return( $list_spec->( $self ) );
187             }
188             elsif ( is_arrayref( $list_spec ) ) {
189 10         38 [@$list_spec];
190             }
191             else {
192             # want all of the fields. actually just want the ones that will be output,
193             # otherwise the check below will fail.
194 19 100       134 [ grep { exists $fieldh->{$_} } $list_spec ? @{ $self->type_index->[$type] } : () ];
  24         254  
  16         349  
195             }
196             };
197              
198             # this check is to help catch typos by users
199 32         149 my @not_field = grep { !exists $fieldh->{$_} } @{$list};
  35         107  
  32         105  
200 32 100       166 error( 'fields', "unknown $error_label fields: " . join( ', ', @not_field ) )
201             if @not_field;
202              
203 26         448 return $list;
204             }
205              
206              
207              
208              
209              
210              
211              
212              
213              
214              
215              
216              
217              
218              
219              
220              
221              
222              
223              
224              
225              
226              
227              
228              
229              
230              
231              
232              
233              
234              
235              
236              
237              
238              
239              
240             has [ 'nullify', 'numify', 'stringify' ] => (
241             is => 'rw',
242             isa => ( ArrayRef [Str] | CodeRef | Bool ), # need parens for perl <= 5.12.5
243             predicate => 1,
244             trigger => 1,
245             );
246              
247 11     11   28204 sub _trigger_nullify { $_[0]->_clear_nullified }
248 7     7   30433 sub _trigger_numify { $_[0]->_clear_numified }
249 7     7   3975 sub _trigger_stringify { $_[0]->_clear_stringified }
250              
251              
252              
253              
254              
255              
256              
257              
258              
259              
260              
261              
262              
263              
264              
265              
266              
267              
268             sub nullified {
269 10     10 1 4105 my $self = shift;
270 10 100       62 return [ $self->has_fields ? @{ $self->_nullified } : () ];
  8         163  
271             }
272              
273              
274              
275              
276              
277              
278              
279              
280              
281              
282              
283              
284              
285              
286              
287              
288              
289              
290              
291             sub numified {
292 10     10 1 3794 my $self = shift;
293 10 100       50 return [ $self->has_fields ? @{ $self->_numified } : () ];
  8         161  
294             }
295              
296              
297              
298              
299              
300              
301              
302              
303              
304              
305              
306              
307              
308              
309              
310              
311              
312              
313             sub stringified {
314 10     10 1 4468 my $self = shift;
315 10 100       53 return [ $self->has_fields ? @{ $self->_stringified } : () ];
  8         170  
316             }
317              
318              
319             has [ '_nullified', '_numified', '_stringified' ] => (
320             is => 'lazy',
321             isa => ArrayRef [Str],
322             clearer => 1,
323             predicate => 1,
324             init_arg => undef,
325             builder => 1,
326             );
327              
328             sub _build__nullified {
329 54     54   586 my $self = shift;
330 54 100       1143 return $self->has_nullify
331             ? $self->_build_field_list_with_type( $self->nullify, ANY, 'nullify' )
332             : [];
333             }
334              
335             sub _build__numified {
336 53     53   669 my $self = shift;
337 53 100       995 return $self->has_numify
338             ? $self->_build_field_list_with_type( $self->numify, NUMBER, 'numify' )
339             : [];
340             }
341              
342             sub _build__stringified {
343 52     52   591 my $self = shift;
344 52 100       1010 return $self->has_stringify
345             ? $self->_build_field_list_with_type( $self->stringify, STRING, 'stringify' )
346             : [];
347             }
348              
349              
350              
351              
352              
353              
354              
355              
356              
357              
358 1     1 1 254 sub string_fields { $_[0]->type_index->[STRING] }
359              
360              
361              
362              
363              
364              
365              
366              
367              
368              
369 40     40 1 1302 sub numeric_fields { $_[0]->type_index->[NUMBER] }
370              
371              
372              
373              
374              
375              
376              
377              
378              
379              
380 128     128 1 2554 sub boolean_fields { $_[0]->type_index->[BOOLEAN] }
381              
382              
383              
384              
385              
386              
387              
388              
389              
390              
391              
392              
393              
394              
395              
396              
397              
398              
399              
400              
401              
402              
403              
404              
405              
406              
407              
408              
409              
410              
411              
412              
413              
414              
415              
416             has type_index => (
417             is => 'lazy',
418             init_arg => undef,
419             clearer => 1,
420             builder => sub {
421 56     56   611 my $self = shift;
422 56 50       262 error( 'types', 'no types for fields are available' )
423             unless $self->has_types;
424 56         326 index_types( $self->types );
425             },
426             );
427              
428              
429              
430              
431              
432              
433              
434              
435              
436              
437              
438             has output_types => (
439             is => 'lazy',
440             init_arg => undef,
441             clearer => 1,
442             trigger => 1,
443             );
444              
445             sub _build_output_types {
446 55     55   4849 my $self = shift;
447 55         145 my %types;
448              
449             return
450 55 50       274 unless $self->has_types;
451              
452 55         130 my @int_fields = grep { defined $self->types->{$_} } @{ $self->fields };
  131         485  
  55         225  
453 55         140 @types{@int_fields} = @{ $self->types }{@int_fields};
  55         251  
454              
455 55 50       202 unless ( $self->_encoder_has_type( BOOLEAN ) ) {
456 55         130 $types{$_} = T_INTEGER for @{ $self->boolean_fields };
  55         201  
457 55         295 $self->_set__convert_boolean_to_int( 1 );
458             }
459              
460 55 100       338 unless ( $self->_encoder_has_type( INTEGER ) ) {
461 38         134 $types{$_} = T_NUMBER for @{ $self->numeric_fields };
  38         165  
462             }
463              
464 55 100       673 if ( my $map_types = $self->_map_types ) {
465 19         134 for my $field ( keys %types ) {
466 46         81 my $type = $types{$field};
467 46 50       113 next unless exists $map_types->{$type};
468 46         98 $types{$field} = $map_types->{$type};
469             }
470             }
471              
472 55         233 for my $key ( keys %types ) {
473 132 100       562 my $rename = $self->rename_fields->{$key}
474             or next;
475              
476 2         10 $types{$rename} = delete $types{$key};
477             }
478              
479 55         277 \%types;
480             }
481              
482             # something for other roles to wrap.
483       0     sub _trigger_output_types { }
484              
485              
486             sub _encoder_has_type {
487 110     110   262 my ( $self, $type ) = @_;
488 110   100 92   529 any { is_type( $_, $type ) } keys %{ $self->_map_types // {} };
  92         512  
  110         380  
489             }
490              
491              
492              
493              
494              
495              
496              
497              
498             has format_fields => (
499             is => 'ro',
500             isa => HashRef [ Str | CodeRef ],
501             );
502              
503              
504              
505              
506              
507              
508              
509             has format_types => (
510             is => 'ro',
511             isa => HashRef [ Str | CodeRef ],
512             );
513              
514              
515              
516              
517              
518              
519              
520              
521             has rename_fields => (
522             is => 'ro',
523             isa => HashRef [Str],
524             coerce => sub {
525             return $_[0] unless is_HashRef( $_[0] );
526              
527             # remove renames which do nothing
528             my %rename = %{ $_[0] };
529             delete @rename{ grep { $rename{$_} eq $_ } keys %rename };
530             return \%rename;
531             },
532             default => sub { {} },
533             trigger => sub {
534             $_[0]->clear_output_types;
535             },
536             );
537              
538              
539              
540              
541              
542              
543              
544              
545              
546             has format => (
547             is => 'ro',
548             isa => Bool,
549             default => 1,
550             );
551              
552             has _format => (
553             is => 'rwp',
554             lazy => 1,
555             default => sub {
556             my $self = shift;
557              
558             if ( $self->format ) {
559             my %format;
560              
561             # first consider types; they'll be overridden by per field
562             # formats in the next step.
563             if ( $self->format_types && $self->types ) {
564              
565             for my $field ( @{ $self->fields } ) {
566              
567             my $type = $self->types->{$field}
568             or next;
569              
570             my $format = $self->format_types->{$type}
571             or next;
572              
573             $format{$field} = $format;
574             }
575             }
576              
577             if ( $self->format_fields ) {
578             for my $field ( @{ $self->fields } ) {
579             my $format = $self->format_fields->{$field}
580             or next;
581              
582             $format{$field} = $format;
583             }
584             }
585              
586             return \%format
587             if keys %format;
588             }
589              
590             return;
591             },
592             init_arg => undef,
593             );
594              
595              
596              
597              
598              
599              
600             sub BUILD {
601 59     59 0 1191 my $self = shift;
602              
603             # if types is passed, set fields if it's not set.
604             # convert types to hash if it's an array
605 59 100       356 if ( $self->has_types ) {
606 19         67 my $types = $self->types;
607              
608 19 100       129 if ( 'HASH' eq ref $types ) {
    50          
609 18 100       97 $self->_set_fields( [ keys %{$types} ] )
  9         173  
610             unless $self->has_fields;
611             }
612             elsif ( 'ARRAY' eq ref $types ) {
613 1         5 $self->_set_types( { @{$types} } );
  1         21  
614              
615 1 50       21 if ( !$self->has_fields ) {
616 1         2 my @fields;
617             # pull off "keys"
618 1         11 push @fields, ( shift @$types, shift @$types )[0] while @$types;
619 1         19 $self->_set_fields( \@fields );
620             }
621             }
622             else {
623 0         0 error( '::attribute::value', 'internal error' );
624             }
625             }
626              
627 59 100       355 if ( $self->has_fields ) {
628              
629 29 100       141 if ( ref $self->fields ) {
630             # in this specific case everything can be done before the first
631             # record is read. this is kind of overkill, but at least one
632             # test depended upon being able to determine types prior
633             # to sending the first record, so need to do this here rather
634             # than in Default::setup
635 24 100       115 $self->_set_types_from_default
636             if $self->has_default_type;
637             }
638              
639             # if fields eq 'all', clear out the attribute so that it will get
640             # filled in when the first record is sent.
641             else {
642 5         99 $self->clear_fields;
643             }
644             }
645              
646 59         512 return;
647             }
648              
649             sub _set_types_from_record {
650 46     46   150 my ( $self, $data ) = @_;
651              
652 46 50       209 return if $self->_have_initialized_types;
653              
654 46 100       191 my $types = $self->has_types ? $self->types : {};
655              
656 46         99 for my $field ( grep !defined $types->{$_}, @{ $self->fields } ) {
  46         301  
657 93         201 my $value = $data->{$field};
658 93 100       394 my $def = Scalar::Util::looks_like_number( $value ) ? T_NUMBER : T_STRING;
659              
660 93 100 100     910 $def = T_INTEGER
661             if $def eq T_NUMBER
662             && POSIX::floor( $value ) == POSIX::ceil( $value );
663              
664 93         282 $types->{$field} = $def;
665             }
666              
667 46         1049 $self->_set_types( $types );
668 46         1037 $self->_set__have_initialized_types( 1 );
669             }
670              
671             sub _set_types_from_default {
672 9     9   22 my $self = shift;
673              
674 9 100       38 return if $self->_have_initialized_types;
675              
676 7 100       27 my $types = $self->has_types ? $self->types : {};
677              
678 7         16 $types->{$_} = $self->default_type for grep { !defined $types->{$_} } @{ $self->fields };
  14         65  
  7         27  
679              
680 7         148 $self->_set_types( $types );
681 7         162 $self->_set__have_initialized_types( 1 );
682             }
683              
684              
685             1;
686              
687             #
688             # This file is part of Data-Record-Serialize
689             #
690             # This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
691             #
692             # This is free software, licensed under:
693             #
694             # The GNU General Public License, Version 3, June 2007
695             #
696              
697             __END__
698              
699             =pod
700              
701             =for :stopwords Diab Jerius Smithsonian Astrophysical Observatory
702              
703             =head1 NAME
704              
705             Data::Record::Serialize::Role::Base - Base Role for Data::Record::Serialize
706              
707             =head1 VERSION
708              
709             version 1.05
710              
711             =head1 DESCRIPTION
712              
713             C<Data::Record::Serialize::Role::Base> is the base role for
714             L<Data::Record::Serialize>. It serves the place of a base class, except
715             as a role there is no overhead during method lookup
716              
717             =head1 OBJECT ATTRIBUTES
718              
719             =head2 C<types>
720              
721             If no types are available, returns C<undef>; see also L</has_types>.
722              
723             Otherwise, returns a hashref whose keys are the input field names and
724             whose values are the types (C<N>, C<I>, C<S>, C<B>). If types are
725             deduced from the data, this mapping is finalized (and thus accurate)
726             only after the first record has been sent.
727              
728             =head2 C<default_type> I<type>
729              
730             The value passed to the constructor (if any).
731              
732             =head2 C<fields>
733              
734             The names of the input fields that will be output.
735              
736             =head2 nullify
737              
738             The value passed to the constructor (if any).
739              
740             =head2 numify
741              
742             $bool = $s->numify;
743              
744             The value passed to the constructor (if any).
745             See the discussion for the L<< numify|Data::Record::Serialize/numify >> constructor option.
746              
747             =head2 stringify
748              
749             $bool = $s->stringify;
750              
751             The value passed to the constructor (if any).
752             See the discussion for the L<< stringify|Data::Record::Serialize/stringify >> constructor option.
753              
754             =head2 C<format_fields>
755              
756             The value passed to the constructor (if any).
757              
758             =head2 C<format_types>
759              
760             The value passed to the constructor (if any).
761              
762             =head2 C<rename_fields>
763              
764             The value passed to the constructor (if any).
765              
766             =head2 C<format>
767              
768             If true, format the output fields using the formats specified in the
769             C<format_fields> and/or C<format_types> options. The default is false.
770              
771             =head1 METHODS
772              
773             =head2 has_types
774              
775             returns true if L</types> has been set.
776              
777             =head2 has_default_type
778              
779             returns true if L</default_type> has been set.
780              
781             =head2 has_fields
782              
783             returns true if L</fields> has been set.
784              
785             =head2 B<output_fields>
786              
787             $array_ref = $s->output_fields;
788              
789             The names of the output fields, in order of requested output. This takes into account
790             fields which have been renamed.
791              
792             =head2 has_nullify
793              
794             returns true if L</nullify> has been set.
795              
796             =head2 has_numify
797              
798             returns true if L</numify> has been set.
799              
800             =head2 has_stringify
801              
802             returns true if L</stringify> has been set.
803              
804             =head2 nullified
805              
806             $fields = $obj->nullified;
807              
808             Returns a arrayref of fields which are checked for empty values (see L</nullify>).
809              
810             This will return an empty array if the list is not yet available (for
811             example, if fields names are determined from the first output record
812             and none has been sent).
813              
814             If the list of fields is available, calling B<nullified> may result in
815             verification of the list of nullified fields against the list of
816             actual fields. A disparity will result in an exception of class
817             C<Data::Record::Serialize::Error::Role::Base::fields>.
818              
819             =head2 numified
820              
821             $fields = $obj->numified;
822              
823             Returns an arrayref of fields which are converted to numbers.
824              
825             This will return an empty array if the list is not yet available (for
826             example, if fields names are determined from the first output record
827             and none has been sent).
828              
829             If the list of fields is available, calling B<numified> may result in
830             verification of the list of numified fields against the list of
831             actual fields. A disparity will result in an exception of class
832             C<Data::Record::Serialize::Error::Role::Base::fields>.
833              
834             =head2 stringified
835              
836             $fields = $obj->stringified;
837              
838             Returns an arrayref of fields which are converted to strings.
839              
840             This will return an empty array if the list is not yet available (for
841             example, if fields names are determined from the first output record
842             and none has been sent).
843              
844             If the list of fields is available, calling B<stringified> may result in
845             verification of the list of stringified fields against the list of
846             actual fields. A disparity will result in an exception of class
847             C<Data::Record::Serialize::Error::Role::Base::fields>.
848              
849             =head2 B<string_fields>
850              
851             $array_ref = $s->string_fields;
852              
853             The input field names for those fields deemed to be strings
854              
855             =head2 B<numeric_fields>
856              
857             $array_ref = $s->numeric_fields;
858              
859             The input field names for those fields deemed to be numeric (either N or I).
860              
861             =head2 B<boolean_fields>
862              
863             $array_ref = $s->boolean_fields;
864              
865             The input field names for those fields deemed to be boolean.
866              
867             =head2 B<type_index>
868              
869             $arrayref = $s->type_index;
870              
871             An array, with indices representing field type or category. The values are
872             an array of field names. This is finalized (and thus accurate) only after the first record is written.
873              
874             I<Don't edit this!>.
875              
876             The indices are available via L<Data::Record::Serialize::Util> and are:
877              
878             =over
879              
880             =item INTEGER
881              
882             =item FLOAT
883              
884             =item NUMBER
885              
886             C<FLOAT> and C<INTEGER>
887              
888             =item STRING
889              
890             =item NOT_STRING
891              
892             everything that's not C<STRING>
893              
894             =item BOOLEAN
895              
896             =back
897              
898             =head2 B<output_types>
899              
900             $hash_ref = $s->output_types;
901              
902             The fully resolved mapping between output field name and output field type. If the
903             encoder has specified a type map, the output types are the result of
904             that mapping. This is only valid after the first record has been sent.
905              
906             =head1 INTERNALS
907              
908             =begin internals
909              
910             =sub _build_field_list_with_type
911              
912             $list = $s->_build_field_list_with_type( $list_spec, $type, $error_label );
913              
914             Given a specification for a list (see the nullify, stringify, and
915             numify attributes) and the field type (e.g. STRING, NUMERIC, BOOLEAN,
916             ANY ) if the specification is boolean, return a list.
917              
918             =end internals
919              
920             =for Pod::Coverage BUILD
921              
922             =head1 SUPPORT
923              
924             =head2 Bugs
925              
926             Please report any bugs or feature requests to bug-data-record-serialize@rt.cpan.org or through the web interface at: L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Record-Serialize>
927              
928             =head2 Source
929              
930             Source is available at
931              
932             https://gitlab.com/djerius/data-record-serialize
933              
934             and may be cloned from
935              
936             https://gitlab.com/djerius/data-record-serialize.git
937              
938             =head1 SEE ALSO
939              
940             Please see those modules/websites for more information related to this module.
941              
942             =over 4
943              
944             =item *
945              
946             L<Data::Record::Serialize|Data::Record::Serialize>
947              
948             =back
949              
950             =head1 AUTHOR
951              
952             Diab Jerius <djerius@cpan.org>
953              
954             =head1 COPYRIGHT AND LICENSE
955              
956             This software is Copyright (c) 2017 by Smithsonian Astrophysical Observatory.
957              
958             This is free software, licensed under:
959              
960             The GNU General Public License, Version 3, June 2007
961              
962             =cut