Extract shared RESP.encode-command for the command wire format - #15
Merged
Conversation
Redis.send and RedisPipeline.add each built the identical RESP multi-bulk wire string inline. Any future change to how commands are serialized had to be made in both call sites or they would silently diverge. Consolidate the encoding into a public RESP.encode-command in the wire-format module (where to-redis and RESP.str already live) and route both call sites through it, giving one source of truth for the command wire format. No behavior change: the bytes produced are identical. Add pure encoder tests pinning the wire bytes for a command with arguments, a space-separated command name (CLIENT ID), and a multi-word command with an argument (CONFIG GET). The existing pipeline byte assertions still pass, confirming the refactor is behavior-preserving.
There was a problem hiding this comment.
Build & Tests
Checked out claude/resp-encode-command (470566f, based on current master) on armhf:
carp -x test/resp.carp→ 134/0 (all three newencode-commandtests pass).carp -b examples/simple.carp→ exit 0, no errors (only the pre-existing benignNo 'prn' function for StringBufwarning, unrelated to this change).- CI:
test (ubuntu-latest)+test (macos-latest)both green. - No CHANGELOG file exists and this is an internal refactor — correctly declined an entry.
Findings
No findings — this is a clean, genuinely behavior-preserving extraction.
- Byte-identity, read through. The new
RESP.encode-commandperforms the same operations the two inline sites did (to-redis-map +Box.init,Array.concat,str &(Arr …)); it's a straight relocation. The type note is right —argsis&(Array RESP)(elements already encoded by thedefredis/pipeline-cmd-layer), not&(Array (Box RESP)). - Non-vacuity, independently verified. I mutated the helper's
Pattern.split #" "→#"_":SET foo barstill passed but both multi-word tests (CLIENT ID,CONFIG GET maxmemory) failed (132/2). So the space-split path — previously untested — is now genuinely covered, and the harness catches divergence. Reverted. - Refactor is behavior-locked both ways.
RedisPipeline.addnow routes through the helper, and the pre-existing pipeline byte assertions still pass unchanged →add's wire output is provably identical.Redis.sendshares that same helper (its only change is the one-line call), so it's transitively byte-identical to the old inline encoder. - Visibility is correct. Kept
encode-commandpublic in theRESPwire-format module (alongsideto-redis/RESP.str), so bothRedisandRedisPipelinereach it without the local-looser-than-CIprivatecross-module trap — both CI OSes green confirm it.
Verdict: merge
Minimal (+31/-11, 2 files), correct, and behavior-preserving with the right kind of proof — the shared helper makes the pipeline and direct-send paths a single source of truth. Draft is intentional; leaving it for you to un-draft.
hellerve
approved these changes
Jul 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Redis.send(direct send) andRedisPipeline.add(batched send) each built the byte-identical RESP multi-bulk wire string inline:This extracts that into one public function,
RESP.encode-command, and routes both call sites through it.RESPmodule — the wire-format module, whereto-redis(thedefinterface) andRESP.stralready live, so it can see everything it needs. Public, so bothRedisandRedisPipelinereach it without tripping the cross-moduleprivatevisibility difference between local Carp and CI.(Fn [&String &(Array RESP)] String). Both call sites already passcmdas an ownedStringandargsas&(Array RESP)— thedefredismacro andpipeline-cmd-both build(ref (array (to-redis …))), i.e. an array of already-encodedRESPvalues — so the element type isRESP, not(Box RESP). (Nosigform is added, since the file uses none; the type is fully pinned by the two concrete call sites.)Redis.send→ encode via the helper, thenTcpStream.send.RedisPipeline.add→ encode via the helper, thenStringBuf.append-str, bump count.Why
The encoding was a duplicated copy in two places. Any future change to how commands are serialized (inline vs multi-bulk, escaping, …) had to be made in both or the two paths would silently diverge. One
RESP.encode-commandgives a single source of truth for the command wire format and makes the pipeline path provably identical to the direct-send path.Behavior-preserving
No wire-format change — the bytes produced are identical.
The existing pipeline byte assertions (
SET k v,GET foo, multi-command concatenation, no-argPING) go throughRedisPipeline.add, which now delegates toRESP.encode-command, and all still pass unchanged.Added three pure encoder tests (no live server) pinning the wire bytes directly:
SET foo bar→*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\nCLIENT ID(multi-word name, no args) →*2\r\n$6\r\nCLIENT\r\n$2\r\nID\r\nCONFIG GET maxmemory(multi-word name + arg) →*3\r\n$6\r\nCONFIG\r\n$3\r\nGET\r\n$9\r\nmaxmemory\r\nThe multi-word command path (
Pattern.split #" ") was previously untested.Verification
carp -x test/resp.carp→ 134 passed, 0 failedcarp -b examples/simple.carpbuildscarp-fmt --checkandanglerclean onredis.carpandtest/resp.carpcarp -x gendocs.carpruns without errorOpened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.