File Coverage

blib/lib/Data/Record/Serialize/Role/Base.pm
Criterion Covered Total %
statement 140 141 99.2
branch 58 64 90.6
condition 7 8 87.5
subroutine 33 35 94.2
pod 6 7 85.7
total 244 255 95.6


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