File Coverage

blib/lib/Protocol/Redis.pm
Criterion Covered Total %
statement 76 79 96.2
branch 29 34 85.2
condition 4 4 100.0
subroutine 10 10 100.0
pod 6 6 100.0
total 125 133 93.9


line stmt bran cond sub pod time code
1             package Protocol::Redis;
2              
3 1     1   6 use strict;
  1         2  
  1         31  
4 1     1   6 use warnings;
  1         2  
  1         31  
5 1     1   21 use 5.008_001;
  1         3  
6              
7             our $VERSION = '1.0011';
8              
9             require Carp;
10              
11             sub new {
12 1     1 1 55 my $class = shift;
13 1 50       6 $class = ref $class if ref $class;
14              
15 1         3 my $self = {@_};
16              
17 1 50       5 return unless $self->{api} == '1';
18              
19 1         3 bless $self, $class;
20              
21 1         23 $self->on_message(delete $self->{on_message});
22 1         2 $self->{_messages} = [];
23              
24 1         3 $self;
25             }
26              
27             sub api {
28 1     1 1 3 my $self = shift;
29              
30 1         5 $self->{api};
31             }
32              
33             my %simple_types = ('+' => 1, '-' => 1, ':' => 1);
34             my $rn = "\r\n";
35              
36             sub encode {
37 11     11 1 24 my $self = shift;
38              
39 11         15 my $encoded_message = '';
40 11         30 while (@_) {
41 17         27 my $message = shift;
42              
43             # Bulk string
44 17 100       50 if ($message->{type} eq '$') {
    100          
    50          
45 9 100       17 if (defined $message->{data}) {
46 7         30 $encoded_message .= '$' . length($message->{data}) . "\r\n" . $message->{data} . "\r\n";
47             }
48             else {
49 2         8 $encoded_message .= "\$-1\r\n";
50             }
51             }
52             # Array (multi bulk)
53             elsif ($message->{type} eq '*') {
54 5 100       14 if (defined $message->{data}) {
55 4         7 $encoded_message .= '*' . scalar(@{$message->{data}}) . "\r\n";
  4         14  
56 4         6 unshift @_, @{$message->{data}};
  4         13  
57             }
58             else {
59 1         3 $encoded_message .= "*-1\r\n";
60             }
61             }
62             # String, error, integer
63             elsif (exists $simple_types{$message->{type}}) {
64 3         15 $encoded_message .= $message->{type} . $message->{data} . "\r\n";
65             }
66             else {
67 0         0 Carp::croak(qq/Unknown message type $message->{type}/);
68             }
69             }
70              
71 11         52 return $encoded_message;
72             }
73              
74             sub get_message {
75 29     29 1 47 shift @{$_[0]->{_messages}};
  29         258  
76             }
77              
78             sub on_message {
79 4     4 1 13 my ($self, $cb) = @_;
80 4   100     43 $self->{_on_message_cb} = $cb || \&_gather_messages;
81             }
82              
83             sub parse {
84 37     37 1 65 my $self = shift;
85 37         63 $self->{_buffer}.= shift;
86              
87 37   100     93 my $message = $self->{_message} ||= {};
88 37         64 my $buffer = \$self->{_buffer};
89              
90             CHUNK:
91 37         107 while ((my $pos = index($$buffer, "\r\n")) != -1) {
92             # Check our state: are we parsing new message or completing existing
93 53 100       101 if (!$message->{type}) {
94 50 50       88 if ($pos < 1) {
95 0         0 Carp::croak(qq/Unexpected input "$$buffer"/);
96             }
97              
98 50         114 $message->{type} = substr $$buffer, 0, 1;
99 50         100 $message->{_argument} = substr $$buffer, 1, $pos - 1;
100 50         113 substr $$buffer, 0, $pos + 2, ''; # Remove type + argument + \r\n
101             }
102              
103             # Simple Strings, Errors, Integers
104 53 100       157 if (exists $simple_types{$message->{type}}) {
    100          
    50          
105 14         29 $message->{data} = delete $message->{_argument};
106             }
107             # Bulk Strings
108             elsif ($message->{type} eq '$') {
109 29 100       122 if ($message->{_argument} eq '-1') {
    100          
110 4         10 $message->{data} = undef;
111             }
112             elsif (length($$buffer) >= $message->{_argument} + 2) {
113 22         51 $message->{data} = substr $$buffer, 0, $message->{_argument}, '';
114 22         42 substr $$buffer, 0, 2, ''; # Remove \r\n
115             }
116             else {
117             return # Wait more data
118 3         10 }
119             }
120             # Arrays
121             elsif ($message->{type} eq '*') {
122 10 100       22 if ($message->{_argument} eq '-1') {
123 1         4 $message->{data} = undef;
124             } else {
125 9         18 $message->{data} = [];
126 9 100       25 if ($message->{_argument} > 0) {
127 8         21 $message = $self->{_message} = {_parent => $message};
128 8         27 next;
129             }
130             }
131             }
132             # Invalid input
133             else {
134 0         0 Carp::croak(qq/Unexpected input "$self->{_message}{type}"/);
135             }
136              
137 42         69 delete $message->{_argument};
138 42         55 delete $self->{_message};
139              
140             # Fill parents with data
141 42         94 while (my $parent = delete $message->{_parent}) {
142 18         24 push @{$parent->{data}}, $message;
  18         34  
143              
144 18 100       25 if (@{$parent->{data}} < $parent->{_argument}) {
  18         39  
145 10         23 $message = $self->{_message} = {_parent => $parent};
146 10         37 next CHUNK;
147             }
148             else {
149 8         13 $message = $parent;
150 8         23 delete $parent->{_argument};
151             }
152             }
153              
154 32         73 $self->{_on_message_cb}->($self, $message);
155 32         112 $message = $self->{_message} = {};
156             }
157             }
158              
159             sub _gather_messages {
160 28     28   42 push @{$_[0]->{_messages}}, $_[1];
  28         67  
161             }
162              
163             1;
164             __END__