From 61bc0e90435e66e23bc4f21a7dbe56ec536d58b2 Mon Sep 17 00:00:00 2001 From: Serhii Zasenko Date: Sun, 18 Jan 2026 21:22:17 +0200 Subject: [PATCH 1/2] Release Preparation --- .gitignore | 1 + Changes | 3 + README.pod | 131 ++++++++++++++++++++++++++++++++++++++++++ lib/Protocol/Redis.pm | 2 +- 4 files changed, 136 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6ffd88b..2ccb3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ MANIFEST* pm_to_blib inc/ MYMETA.* +/Protocol-Redis*.tar.gz diff --git a/Changes b/Changes index 5c18613..7b46436 100644 --- a/Changes +++ b/Changes @@ -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 diff --git a/README.pod b/README.pod index a6c774f..c66fc32 100644 --- a/README.pod +++ b/README.pod @@ -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 support. @@ -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 +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 +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, followed by a colon C<:>, and the remaining bytes are the string +data. When parsing, the C will be returned as a separate key and not +included in the string C. When encoding, a C can be specified and +otherwise defaults to C, 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 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 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 my $redis = Protocol::Redis->new(api => 1); diff --git a/lib/Protocol/Redis.pm b/lib/Protocol/Redis.pm index e1af470..39ab7f5 100644 --- a/lib/Protocol/Redis.pm +++ b/lib/Protocol/Redis.pm @@ -4,7 +4,7 @@ use strict; use warnings; use 5.008_001; -our $VERSION = '1.0021'; +our $VERSION = '2.0000'; require Carp; From 3b087d57ce560862c84af9c3d1ec153ba14f997d Mon Sep 17 00:00:00 2001 From: Serhii Zasenko Date: Sun, 18 Jan 2026 21:53:28 +0200 Subject: [PATCH 2/2] Fixup pipeline --- .github/workflows/cpan.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cpan.yaml b/.github/workflows/cpan.yaml index 6b7a1b9..9619182 100644 --- a/.github/workflows/cpan.yaml +++ b/.github/workflows/cpan.yaml @@ -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