File Coverage

blib/lib/Cucumber/Messages/Message.pm
Criterion Covered Total %
statement 12 53 22.6
branch 0 20 0.0
condition 0 10 0.0
subroutine 4 10 40.0
pod 2 2 100.0
total 18 95 18.9


line stmt bran cond sub pod time code
1              
2             package Cucumber::Messages::Message;
3             $Cucumber::Messages::Message::VERSION = '21.0.0';
4             =head1 NAME
5              
6             Cucumber::Messages::Message - Base class for cucumber messages
7              
8             =head1 SYNOPSIS
9              
10              
11             # Create a new message class:
12             use Moo;
13             extends 'Cucumber::Messages::Message';
14              
15             has 'prop1';
16             has 'prop2';
17              
18              
19             =head1 DESCRIPTION
20              
21              
22              
23             =cut
24              
25 3     3   233104 use Carp qw(croak);
  3         6  
  3         147  
26 3     3   979 use JSON::MaybeXS;
  3         11696  
  3         166  
27              
28 3     3   19 use Scalar::Util qw( blessed );
  3         6  
  3         130  
29              
30 3     3   1599 use Moo;
  3         34173  
  3         14  
31             # 'use Moo' implies 'use strict; use warnings;'
32              
33             # The message classes have been inspired by the Ruby implementation, which
34             # existed before this Perl implementation was created.
35              
36             # Perl has multiple object systems: Moo, Moose, Mouse, ...
37             # Moo is Perl's minimalistic object system: geared towards speed, not aimed
38             # at completeness. Moose (not used in this code) is the one aiming at
39             # completeness. Moose has type checking of attributes, Moo lacks that.
40             # In that respect, Ruby and Perl are very much alike. Looking at the Ruby
41             # code (which doesn't have type checking), I decided not to go for
42             # type-checking. Given the expected short-livedness of the objects
43             # and as a way to reduce the dependency tree for the Cucumber::Messages
44             # library, I decided to go for Moo instead of Moose.
45              
46              
47             my $json = JSON::MaybeXS->new(
48             utf8 => 0, pretty => 0, indent => 0, canonical => 1
49             );
50              
51             sub _camelize {
52 0     0     my ($self, $str) = @_;
53              
54             # "abc_def_ghi -> abcDefGhi
55 0           return ($str =~ s/(?:_(\w))/uc($1)/egr);
  0            
56             }
57              
58             sub _snaked {
59 0     0     my ($self, $str) = @_;
60              
61             # abcDefGhi -> abc_def_ghi
62 0           return ($str =~ s/([A-Z])/lc("_$1")/egr);
  0            
63             }
64              
65             sub _to_hash {
66 0     0     my ($value, %args) = @_;
67              
68 0 0         if (my $ref = ref $value) {
69 0 0         if ($ref eq 'ARRAY') {
70 0   0       $args{type} //= '';
71 0           my $type = $args{type} =~ s/^\[\]//r;
72 0           return [ map { _to_hash( $_, %args, type => $type ) } @$value ];
  0            
73             }
74              
75 0 0 0       croak 'Cucumber::Messages::Message expected in message serialization; found: ' . $ref
76             unless blessed( $value ) and $value->isa( 'Cucumber::Messages::Message' );
77              
78 0           my $types = $value->_types;
79             return {
80             map {
81             __PACKAGE__->_camelize($_)
82 0           => _to_hash( $value->{$_}, %args, type => $types->{$_} )
83 0           } grep { defined $value->{$_} } keys %$value
  0            
84             };
85             }
86             else {
87 0 0 0       if (not $args{type} or $args{type} ne 'boolean') {
88 0           return $value;
89             }
90             else {
91 0 0         return $value ? JSON::MaybeXS->true : JSON::MaybeXS->false;
92             }
93             }
94             }
95              
96              
97             sub _from_hash {
98 0     0     my ($value, %args) = @_;
99              
100 0 0         if (my $ref = ref $value) {
101 0 0         return $value ? 1 : ''
    0          
102             if $json->is_bool( $value );
103              
104 0 0         if ($ref eq 'ARRAY') {
105 0   0       $args{type} //= '';
106 0           my $type = $args{type} =~ s/^\[\]//r;
107 0           return [ map { _from_hash( $_, %args, type => $type ) } @$value ];
  0            
108             }
109             croak 'No type supplied to deserialize hash'
110 0 0         unless $args{type};
111              
112 0           my $types = $args{type}->_types;
113             return $args{type}->new(
114             map {
115 0           my $propname = __PACKAGE__->_snaked( $_ );
  0            
116             $propname
117             => _from_hash( $value->{$_}, %args,
118 0           type => $types->{$propname} )
119             } keys %$value
120             );
121             }
122             else {
123 0           return $value;
124             }
125             }
126              
127             =head1 METHODS
128              
129             =head2 $self->to_json
130              
131             Instance method.
132              
133             Returns the data encapsulated by C<$self> as a serialized byte string
134             represented as a single NDJSON line. Note that line-terminating newline
135             character (C<\n>) is not included in the return value.
136              
137             =cut
138              
139             sub to_json {
140 0     0 1   my ($self, %args) = @_;
141              
142 0           return $json->encode( _to_hash( $self, %args ) );
143             }
144              
145             =head2 $class->from_json( $str )
146              
147             Returns an instance of class C<$class> which encapsulates the data
148             from the bytestring C<$str>, assuming that it is a single valid NDJSON
149             line.
150              
151             =cut
152              
153             sub from_json {
154 0     0 1   my ($class, $msgstr ) = @_;
155              
156 0           my $args = $json->decode( $msgstr );
157 0           my $rv = _from_hash( $args, type => $class );
158 0           $rv;
159             }
160              
161             1;
162              
163             __END__