respcodec

package module
v2.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2026 License: MIT Imports: 4 Imported by: 0

README

resp-codec

A Go library for encoding and decoding the Redis Serialization Protocol — RESP2 in the root package, and RESP3 in the resp3 subpackage.

CI Go Reference

Installation

go get github.com/0xRadioAc7iv/resp-codec/v2

Type Mapping

Go type RESP type Wire format
SimpleString Simple string +<value>\r\n
string Bulk string $<len>\r\n<data>\r\n
error Error -<message>\r\n
int Integer :<value>\r\n
[]any Array *<len>\r\n<elements>
Null Null bulk string $-1\r\n
NullArr Null array *-1\r\n

Usage

Encode
buf, err := respcodec.Encode(respcodec.SimpleString("OK"))  // "+OK\r\n"
buf, err := respcodec.Encode(errors.New("ERR unknown"))     // "-ERR unknown\r\n"
buf, err := respcodec.Encode(42)                            // ":42\r\n"
buf, err := respcodec.Encode("hello")                       // "$5\r\nhello\r\n"
buf, err := respcodec.Encode([]any{"GET", "key"})           // "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n"
buf, err := respcodec.Encode(respcodec.Null)                // "$-1\r\n"
buf, err := respcodec.Encode(respcodec.NullArr)             // "*-1\r\n"
AppendEncode

AppendEncode writes into a caller-supplied buffer, enabling buffer reuse and avoiding extra allocations — useful when writing to a net.Conn with a pooled buffer:

buf := make([]byte, 0, 64)
buf, err := respcodec.AppendEncode(buf, respcodec.SimpleString("OK"))
buf, err  = respcodec.AppendEncode(buf, 42)
Decode

Decode parses a single complete RESP frame and returns the decoded Go value, dispatched by wire-format prefix:

ss,  err := respcodec.Decode([]byte("+OK\r\n"))                          // SimpleString("OK")
msg, err := respcodec.Decode([]byte("-ERR unknown\r\n"))                 // error
n,   err := respcodec.Decode([]byte(":42\r\n"))                          // 42
s,   err := respcodec.Decode([]byte("$5\r\nhello\r\n"))                  // "hello"
arr, err := respcodec.Decode([]byte("*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n")) // []any{"GET", "key"}
null, err := respcodec.Decode([]byte("$-1\r\n"))                         // nil
nullArr, err := respcodec.Decode([]byte("*-1\r\n"))                      // nil

For the - (error) type, Decode returns an error value, not a plain string.

RESP3

The resp3 subpackage implements RESP3, the protocol used by Redis 6+ in protover 3 mode. It adds new types on top of RESP2 (big numbers, doubles, booleans, blob errors, verbatim strings, maps, sets, attributes, and push messages) and reinterprets RESP2's null as a single unified null type.

resp3.Decode is a recursive-descent parser over a shared cursor, the same approach Redis's own client-side reply parser uses: aggregate types (arrays, maps, sets, attributes, push) don't pre-compute how many bytes a nested element occupies, they just decode the next value and let the cursor advance by exactly as much as that value needed. This means arbitrarily deep nesting and binary-safe blob/verbatim/error strings (which may contain \r, \n, or bytes that look like other type sigils) decode correctly without special-casing.

import "github.com/0xRadioAc7iv/resp-codec/v2/resp3"
Go type RESP3 type Wire format
respcodec.SimpleString Simple string +<value>\r\n
string Blob string $<len>\r\n<data>\r\n
error Simple error -<message>\r\n
resp3.BlobError Blob error !<len>\r\n<data>\r\n
resp3.VerbatimString Verbatim string =<len>\r\n<data>\r\n
int Integer :<value>\r\n
*big.Int Big number (<value>\r\n
float64 / resp3.Inf / resp3.NegInf / resp3.NaN Double ,<value>\r\n
resp3.Null Null _\r\n
bool Boolean #t\r\n / #f\r\n
[]any Array *<len>\r\n<elements>
map[respcodec.SimpleString]any Map %<pairs>\r\n<key><value>...
map[any]struct{} Set ~<len>\r\n<elements>
resp3.AttributeType Attribute |<pairs>\r\n<key><value>...
resp3.Push Push ><len>\r\n<kind><args>...
buf, err := resp3.Encode([]any{"GET", "key"})                         // "*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n"
v,   err := resp3.Decode([]byte("*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n"))   // []any{"GET", "key"}

Testing

Run the test suite:

make test

with coverage:

make test-cov
Integration tests

resp3 also has integration tests that run against a real Redis 6+ server, verifying that Encode/Decode round-trip correctly with what an actual server sends and accepts on the wire (not just synthetic frames). They're excluded from the default test run via a build tag, so a Redis instance isn't required for normal development:

make test-integration

This connects to localhost:6379 by default; set REDIS_ADDR to point elsewhere. Tests skip automatically if no server is reachable.

Benchmarks

Run benchmarks:

make bench

License

This project is available under the MIT License.

Documentation

Overview

Package respcodec implements encoding and decoding for the Redis Serialization Protocol v2 (RESP2). It supports simple strings, errors, integers, bulk strings, arrays, null bulk strings, and null arrays.

Encoding: use Encode to serialize a value into a fresh buffer, or AppendEncode to write into a caller-supplied buffer for zero-allocation reuse.

Decoding: use Decode to parse a single complete frame and get back the decoded Go value, dispatched by wire-format prefix.

Example (DecodeHelpers)
ss, _ := decodeSimpleString([]byte("+OK\r\n"))
fmt.Printf("%q\n", ss)
e, _ := decodeErrorString([]byte("-ERR unknown command\r\n"))
fmt.Printf("%q\n", e)
n, _ := decodeInteger([]byte(":42\r\n"))
fmt.Printf("%d\n", n)
s, _ := decodeBulkString([]byte("$5\r\nhello\r\n"))
fmt.Printf("%q\n", s)
err := decodeNullBulkString([]byte("$-1\r\n"))
fmt.Printf("%v\n", err == nil)
arr, _ := decodeArray([]byte("*3\r\n:1\r\n:2\r\n:3\r\n"))
fmt.Printf("%v\n", arr)
err = decodeNullArray([]byte("*-1\r\n"))
fmt.Printf("%v\n", err == nil)
Output:
"OK"
"ERR unknown command"
42
"hello"
true
[1 2 3]
true

Index

Examples

Constants

This section is empty.

Variables

View Source
var Null = nullBulkString{}

Null is the sentinel value for encoding a RESP null bulk string ($-1\r\n). It signals the absence of a value, distinct from an empty string.

View Source
var NullArr = nullArray{}

NullArr is the sentinel value for encoding a RESP null array (*-1\r\n). It is an alternative null representation used by commands like BLPOP on timeout. Prefer Null for general null values; use NullArr only when the protocol specifically requires it.

Functions

func AppendEncode

func AppendEncode(buf []byte, data any) ([]byte, error)

AppendEncode appends the RESP encoding of data into buf and returns the extended slice. It makes zero additional allocations when buf has sufficient capacity, making it suitable for callers that manage their own buffer — for example, writing directly to a net.Conn using a pooled buffer from sync.Pool.

On error, buf is returned in its original state (no partial bytes are left behind), so it is safe to reuse after a failed call.

Supported types are identical to Encode.

Example
// Reuse a single buffer across multiple encodes — zero additional allocations
// when capacity is sufficient.
buf := make([]byte, 0, 128)

buf, _ = AppendEncode(buf, SimpleString("OK"))
buf, _ = AppendEncode(buf, errors.New("ERR unknown command"))
buf, _ = AppendEncode(buf, 42)
buf, _ = AppendEncode(buf, "hello")
fmt.Printf("%q\n", buf)
Output:
"+OK\r\n-ERR unknown command\r\n:42\r\n$5\r\nhello\r\n"

func Decode

func Decode(buf []byte) (any, error)

Decode parses a single complete RESP frame from buf and returns the decoded Go value. The caller must supply exactly one complete frame with no trailing bytes.

Type mapping:

`+` → SimpleString
`-` → error
`:` → int
`$` → string (nil for the null bulk string, "$-1\r\n")
`*` → []any (nil for the null array, "*-1\r\n")
Example
ss, _ := Decode([]byte("+OK\r\n"))
fmt.Println(ss)
e, _ := Decode([]byte("-ERR unknown command\r\n"))
fmt.Println(e)
n, _ := Decode([]byte(":42\r\n"))
fmt.Println(n)
s, _ := Decode([]byte("$5\r\nhello\r\n"))
fmt.Println(s)
null, _ := Decode([]byte("$-1\r\n"))
fmt.Println(null == nil)
arr, _ := Decode([]byte("*3\r\n:1\r\n:2\r\n:3\r\n"))
fmt.Println(arr)
nullArr, _ := Decode([]byte("*-1\r\n"))
fmt.Println(nullArr == nil)
_, err := Decode([]byte("?unknown\r\n"))
fmt.Println(err)
Output:
OK
ERR unknown command
42
hello
true
[1 2 3]
true
unknown RESP type sigil: '?'

func Encode

func Encode(data any) ([]byte, error)

Encode serializes a Go value into its RESP byte representation.

Supported types and their RESP encoding:

  • SimpleString → +<value>\r\n (must not contain CR or LF)
  • string → $<len>\r\n<data>\r\n (binary-safe bulk string)
  • error → -<message>\r\n (must not contain CR or LF)
  • int → :<value>\r\n
  • []any → *<len>\r\n<elements> (each element encoded recursively)
  • Null → $-1\r\n (null bulk string)
  • NullArr → *-1\r\n (null array)

Returns (nil, error) for unsupported types, invalid input, or arrays containing an invalid element.

Encode allocates a single initial buffer and grows it as needed; for outputs that fit within 64 bytes this is typically one allocation. Array elements are written into the same buffer via the internal append-style encode function, avoiding per-element allocations. Use AppendEncode to supply your own buffer.

Example
buf, _ := Encode(SimpleString("OK"))
fmt.Printf("%q\n", buf)
buf, _ = Encode(errors.New("ERR unknown command"))
fmt.Printf("%q\n", buf)
buf, _ = Encode(42)
fmt.Printf("%q\n", buf)
buf, _ = Encode("hello")
fmt.Printf("%q\n", buf)
buf, _ = Encode([]any{"GET", "key"})
fmt.Printf("%q\n", buf)
buf, _ = Encode(Null)
fmt.Printf("%q\n", buf)
buf, _ = Encode(NullArr)
fmt.Printf("%q\n", buf)
buf, err := Encode(3.14) // unknown type → nil, error
fmt.Printf("%v %v\n", buf, err)
Output:
"+OK\r\n"
"-ERR unknown command\r\n"
":42\r\n"
"$5\r\nhello\r\n"
"*2\r\n$3\r\nGET\r\n$3\r\nkey\r\n"
"$-1\r\n"
"*-1\r\n"
[] unsupported type float64: cannot encode to RESP

Types

type SimpleString

type SimpleString string

SimpleString represents a RESP simple string (prefix '+'). Simple strings are for short status messages like "OK" or "PONG". They must not contain CR (\r) or LF (\n) characters. Use the plain string type for binary-safe bulk string encoding instead.

Directories

Path Synopsis
internal
Package resp3 implements encoding and decoding for the RESP3 protocol (https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md), the superset of RESP2 used by Redis 6+ in protover 3 mode.
Package resp3 implements encoding and decoding for the RESP3 protocol (https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md), the superset of RESP2 used by Redis 6+ in protover 3 mode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL