Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/cpan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
jobs:
build:
uses: PerlToolsTeam/github_workflows/.github/workflows/cpan-test.yml@main
with:
os: '["macos-latest", "ubuntu-latest"]'

coverage:
uses: PerlToolsTeam/github_workflows/.github/workflows/cpan-coverage.yml@main
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ MANIFEST*
pm_to_blib
inc/
MYMETA.*
/Protocol-Redis*.tar.gz
3 changes: 3 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
This file documents the revision history for Perl extension Protocol::Redis

2.0000 18.01.2026 00:00:00
- Add support for RESP2 and RESP3 versions (Grinnz).

1.0021 03.09.2024 00:00:00
- Test unknown version excaption implementation

Expand Down
131 changes: 131 additions & 0 deletions README.pod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,63 @@ Protocol::Redis - Redis protocol parser/encoder with asynchronous capabilities.
{type => '+', data => 'OK'}
]});

# RESP3 supported with api 3 specified
my $redis = Protocol::Redis->new(api => 3);

print $redis->encode({type => '%', data => {
null => {type => '_', data => undef},
bignum => {type => '(', data => '3492890328409238509324850943850943825024385'},
string => {type => '$', data => 'this is a string'},
# format prepended to verbatim strings (defaults to txt)
verbatim => {type => '=', data => '*verbatim* string', format => 'mkd'},
# set is unordered but specified as array
booleans => {type => '~', data => [
{type => '#', data => 1},
{type => '#', data => 0},
]},
# map is hashlike but can be specified as an
# (even-sized) array to encode non-string keys
special_map => {type => '%', data => [
{type => ':', data => 42} => {type => '$', data => 'The answer'},
{type => '_'} => {type => '$', data => 'No data'},
]},
}, attributes => {
coordinates => {type => '*', data => [
{type => ',', data => '36.001516'},
{type => ',', data => '-78.943319'},
]},
});

# "|1\r\n\$11\r\ncoordinates\r\n*2\r\n,36.001516\r\n,-78.943319\r\n" .
# "%6\r\n" .
# "\$6\r\nbignum\r\n(3492890328409238509324850943850943825024385\r\n" .
# "\$8\r\nbooleans\r\n~2\r\n#t\r\n#f\r\n" .
# "\$4\r\nnull\r\n_\r\n" .
# "\$11\r\nspecial_map\r\n%2\r\n:42\r\n\$10\r\nThe answer\r\n_\r\n\$7\r\nNo data\r\n" .
# "\$6\r\nstring\r\n\$16\r\nthis is a string\r\n" .
# "\$8\r\nverbatim\r\n=21\r\nmkd:*verbatim* string\r\n"

# sets represented in the protocol the same as arrays
# remapping into a hash may be useful to access string set elements
$redis->parse("~3\r\n+x\r\n+y\r\n+z\r\n");
my %set = map {($_->{data} => 1)} @{$redis->get_message->{data}};
die unless exists $set{x};
print join ',', keys %set; # x,y,z in unspecified order

# verbatim strings are prefixed by a format
# this will be returned as the format key
$redis->parse("=16\r\nmkd:* one\n* two\n\r\n");
my $verbatim = $redis->get_message;
die unless $verbatim->{format} eq 'mkd';
print $verbatim->{data};
# * one
# * two

# attributes are maps that apply to the following value
$redis->parse("|1\r\n+hits\r\n:6\r\n\$4\r\nterm\r\n");
my $term = $redis->get_message;
print "$term->{data}: $term->{attributes}{hits}{data}\n";

=head1 DESCRIPTION

Redis protocol parser/encoder with asynchronous capabilities and L<pipelining|http://redis.io/topics/pipelining> support.
Expand All @@ -40,6 +97,80 @@ Protocol::Redis APIv1 uses
encoding/parsing and supports methods described further. Client libraries
should specify API version during Protocol::Redis construction.

API version 1 corresponds to the protocol now known as
L<RESP2|https://github.com/redis/redis-specifications/blob/master/protocol/RESP2.md>
and can also thusly be specified as API version 2. It supports the RESP2 data
types C<+-:$*>.

=head1 APIv3

API version 3 supports the same methods as API version 1, corresponding to the
L<RESP3|https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md>
protocol. RESP3 contains support for several additional data types (null,
boolean, double, big number, blob error, verbatim string, map, and set), data
attributes, streamed strings and aggregate data, and explicitly specified push
data so that asynchronous and synchronous responses can share a connection. A
client must request RESP3 support from the server with the HELLO command to use
these features.

APIv3 supports the RESP2 data types C<+-:$*> as well as the RESP3-specific data
types C<< _,#!=(%~|> >>, with the following implementation notes:

=over

=item * Verbatim String

The Verbatim String type, specified with the initial byte C<=>, is treated the
same as the Blob String type C<$> except that the first three bytes specify
the C<format>, followed by a colon C<:>, and the remaining bytes are the string
data. When parsing, the C<format> will be returned as a separate key and not
included in the string C<data>. When encoding, a C<format> can be specified and
otherwise defaults to C<txt>, and will be prepended to the string data.

=item * Big Number

The Big Number type, specified with the initial byte C<(>, is parsed to a
L<Math::BigInt> object, which can be used in numeric or string operations
without losing precision.

=item * Map

The Map type, specified with the initial byte C<%>, is represented in Perl as a
hash. The keys of a Map are allowed to be any data type, but for simplicity
they are coerced to strings as required by Perl hashes, which the specification
allows. When encoding a Map, a hash reference is normally passed, but an array
reference of alternating keys and values may also be passed to allow specifying
non-string keys. If passed as an array, the values will be encoded in the order
specified, but the Map type is defined as unordered.

=item * Attribute

The Attribute type, specified with the initial byte C<|>, is much like the Map
type, but instead of acting as a value in the message, it is applied as the
C<attributes> key of the following value. Like Map, its keys are coerced to
strings as it is represented as a Perl hash.

=item * Set

The Set type, specified with the initial byte C<~>, is represented as an array,
since a Set can contain values of any data type, which native Perl hashes
cannot represent as keys, and the specification does not require enforcing
element uniqueness. If desired, the higher level client and server should
handle deduplication of Set elements, and should also be aware that the type
is defined as unordered and the values are likely to be tested for existence
rather than position.

=item * Push

The Push type, specified with the initial byte C<< > >>, is treated no
differently from an Array, but a client supporting RESP3 must be prepared to
handle a Push value at any time rather than in response to a command. An
asynchronous client would generally execute a predefined callback when a Push
value is received; a synchronous client must also take this into consideration
for how and when it reads messages.

=back

=head2 C<new>

my $redis = Protocol::Redis->new(api => 1);
Expand Down
2 changes: 1 addition & 1 deletion lib/Protocol/Redis.pm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use strict;
use warnings;
use 5.008_001;

our $VERSION = '1.0021';
our $VERSION = '2.0000';

require Carp;

Expand Down
Loading