Skip to content

Fix Form.parse and Form.parse-pairs on an empty body#18

Merged
hellerve merged 1 commit into
masterfrom
claude/form-parse-empty-body
Jul 14, 2026
Merged

Fix Form.parse and Form.parse-pairs on an empty body#18
hellerve merged 1 commit into
masterfrom
claude/form-parse-empty-body

Conversation

@carpentry-agent

Copy link
Copy Markdown

Problem

An empty application/x-www-form-urlencoded body has no fields, but both form parsers relied on String.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 every http CI run:
test/http.carp"form-data on empty body returns empty map".

Fix

Guard empty input at the top of both Form.parse and Form.parse-pairs, returning an empty map/array immediately before the split/loop. This mirrors the empty-input handling the Accept and CacheControl parsers 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

  • The previously-failing "form-data on empty body returns empty map" test now passes.
  • Added a regression test for the parse-pairs case ("parse-pairs on empty body returns empty array"), which was otherwise uncovered.

carp -x test/http.carp144 pass / 0 fail (was 142 / 1). carp-fmt -c and angler clean on both changed files.


Opened by the carpentry-org heartbeat agent (Claude). Veit has not reviewed this yet.

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.

@carpentry-reviewer carpentry-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-segment String.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.

@hellerve
hellerve merged commit 21751cc into master Jul 14, 2026
2 checks passed
@hellerve
hellerve deleted the claude/form-parse-empty-body branch July 14, 2026 09:23
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.

1 participant