Fix Form.parse and Form.parse-pairs on an empty body#18
Conversation
An empty urlencoded body has no fields, but both parsers relied on
(String.split-by "" &[\&]) which returns a single empty element, so
an empty input produced a spurious one-entry result: Form.parse ""
yielded {"": ""} and Form.parse-pairs "" yielded [("", "")].
Guard empty input at the top of both functions, returning an empty
map/array immediately (mirroring the empty-input handling in the
Accept and CacheControl parsers). All non-empty behaviour, including a
bare key with no '=', is unchanged.
Fixes the previously-failing 'form-data on empty body returns empty
map' suite test and adds a regression test for the parse-pairs case.
There was a problem hiding this comment.
Build & Tests
Checked out the branch and ran carp -x test/http.carp locally: 144 pass / 0 fail (was 142/1) — matches the PR description exactly, and the previously-red "form-data on empty body returns empty map" test is now green. carp-fmt -c clean; both CI jobs (ubuntu, macos) pass.
I also drove the parsers directly to confirm the behavior beyond the suite:
| input | Form.parse count |
notes |
|---|---|---|
"" |
0 | ✅ the fix |
" " (one space) |
1 | ✅ whitespace is a valid bare key, correctly not swallowed |
"flag" |
1 (flag→"") |
✅ bare-key behavior preserved |
"a=1&b=2" |
2 | ✅ normal parse unchanged |
Form.parse-pairs "" → [] likewise. The fix only adds a top-level (if (String.empty? s) …) guard, so every non-empty input runs the exact original code path — no regression surface.
Findings
The fix is correct and precisely scoped. One non-blocking observation for a future pass (not something I'd hold this PR for):
- Empty segments are still turned into spurious empty-key entries.
Form.parse "a=1&"returns 2 entries ({"a":"1", "":""}), and likewise for a leading&or&&. Per the WHATWG urlencoded parse rules empty segments should be skipped, so these are technically the same class of bug as the empty-body case this PR fixes. A single per-segmentString.empty?skip inside the loop would subsume this top-level guard and also handle the trailing/leading/double-&cases — worth considering if you'd rather fix the general case. But the current change is a clean, verified fix for the one failing test and introduces no regressions, so this is purely optional follow-up.
No CHANGELOG in this repo, so nothing to update there.
Verdict: merge
Correct, minimal, fully green locally and in CI, and it turns the http suite fully green. The empty-segment case above is pre-existing and out of scope.
Problem
An empty
application/x-www-form-urlencodedbody has no fields, but both form parsers relied onString.split-by, and(String.split-by "" &[\&])returns a single empty element rather than an empty array. As a result an empty input produced a spurious one-entry result:Form.parse ""→{"": ""}Form.parse-pairs ""→[("", "")]Reached through
Request.form-data, this meant an empty body reported one bogus field. It was also the single red test in everyhttpCI run:test/http.carp— "form-data on empty body returns empty map".Fix
Guard empty input at the top of both
Form.parseandForm.parse-pairs, returning an empty map/array immediately before the split/loop. This mirrors the empty-input handling theAcceptandCacheControlparsers in this module already do.All existing behaviour for non-empty input is unchanged, including a bare key with no
=(Form.parse "flag"→{"flag": ""}).Tests
carp -x test/http.carp→ 144 pass / 0 fail (was 142 / 1).carp-fmt -candanglerclean on both changed files.Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.