Skip to content

Conversation

@Grinnz
Copy link
Contributor

@Grinnz Grinnz commented Jan 3, 2026

RESP3 protocol documented at https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md . Since the original protocol is now referred to as RESP2, I allowed api => 2 to act as api => 1, and defined api => 3 as enabling RESP3 support.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 3, 2026

I wrote this over a year ago, I believe I completed the implementation but if there was anything left to implement I have now forgotten.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 3, 2026

Also, some of the details like the specific way Push (>) messages are used by the server are not relevant to the protocol parser; it will just be treated as an Array, but the implementation using it must be aware that the client may receive Push messages at any time (but not in the middle of other messages), and not as a response to a command. As the specification notes: "Clients should normally react to the publication of a push data by invoking a callback associated with the push data type. Synchronous clients may instead enter a loop and return every new message or command reply."

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 3, 2026

Additional note: though the RESP3 spec allows Map and Attribute keys to be any data type, but since they are best represented by hashes in Perl, they will be coerced to and specified as strings, as the specification suggests to fit the language's data types.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 3, 2026

The specification suggests using hashes to represent the Set type, but since hash keys can only be strings and Sets can contain any data type, and the protocol itself does not enforce element uniqueness, I chose to represent them as arrays instead so the values are not coerced to strings.

The encoder allows specifying the Map type as an array of alternating keys and values, in case the order in which they are encoded matters (though the specification refers to Map as unordered) or to allow encoding non-string Map keys.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 3, 2026

I should probably add mention of these implementation details to the documentation.

@und3f
Copy link
Owner

und3f commented Jan 5, 2026

Hello! Thank you for the PR, it looks good!

I have a question if it is possible to subclass version 3 and version 2 so we avoid statements like if ($self->{api} == 3)?

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 5, 2026

Do you mean for example, create a Protocol::Redis::APIv3 subclass that contains a separate implementation of parse and encode methods? That sounds reasonable to me.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 5, 2026

Actually I can call it ::RESP2 and ::RESP3, to separate it from the need to provide different API methods in the future.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 5, 2026

Do you think it makes sense to use api => 3 for this, or should we add a parameter specifically for RESP3 support as the API itself has not changed?

@und3f
Copy link
Owner

und3f commented Jan 6, 2026

Do you think it makes sense to use api => 3 for this, or should we add a parameter specifically for RESP3 support as the API itself has not changed?
It makes sense for me

Do you mean for example, create a Protocol::Redis::APIv3 subclass that contains a separate implementation of parse and encode methods? That sounds reasonable to me.
Yeah, I feel like it would be convenient. Probably those methods could reuse parts of V1 implementation

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 7, 2026

There's one problem with the subclass approach. If sub new returns a different subclass it will no longer be usable by subclasses to bless into their own package.

An alternative that doesn't complicate the subclassing is just to have separate parse and encode functions within the main package.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 7, 2026

I changed it to be implemented that way. Either way I don't think reusing code between them is useful enough to warrant the performance loss of adding subroutine calls to the encode and parse loops.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 7, 2026

I was thinking about two options that a user might want, if it would make sense to add options:

  • Return big numbers as a string rather than a Math::BigInt, in case Math::BigInt causes problems and math isn't needed - but I'm not sure whether this would be useful
  • Array is not the most convenient way to access a returned set (though it is the same as you get in RESP2). Could add an option to return sets as a hash (coercing all values to strings as they will be hash keys) or as a custom object type which is similar to Set::Object but handles the possible types that RESP3 can return instead, this would be some work to write of course.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 7, 2026

Both of these options could also be solved by a wrapper or higher level module based on the values returned, though.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 7, 2026

I realized that I had missed a factor of the Verbatim String type, which is that the first 4 bytes are required to be a 3 character format followed by a colon. It will now parse that out from the string data and add it to the string when encoding.

@Grinnz
Copy link
Contributor Author

Grinnz commented Jan 7, 2026

I also removed support for "nil" values by specifying -1 as the length of a string or array, since that is fully replaced by the null type and not supported by RESP3. I believe I had left it in just to simplify the combined logic.

@und3f
Copy link
Owner

und3f commented Jan 7, 2026

Approved. I suppose failing windows tests are not related to the PR.

@und3f und3f merged commit dac3e7e into und3f:master Jan 9, 2026
46 of 96 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants