Skip to content

refactor: share the JSON body reader in internal/jsonbody - #160

Merged
Bakhomious merged 1 commit into
mainfrom
step/shared-json-body
Oct 29, 2026
Merged

Bakhomious merged 1 commit into
mainfrom
step/shared-json-body

Conversation

@Bakhomious

Copy link
Copy Markdown
Contributor

Brief

Goal. todos and notify read a request's JSON body with the same function, a copy each, and worklog-add would copy it a third time into time. AGENTS.md moves code that two or more services need to the root internal/, so it moves to a package of its own, internal/jsonbody, a step ahead of worklog-add, as shared-jira (#147) and shared-poller (#109) did. Nothing changes in behaviour. The fifteenth of time-tracking's eighteen steps (decision 190).

Scope.

  • New package internal/jsonbody: Read(w, r, v, refusal) bool, todos' readBody, moved: it decodes r's body, a JSON object without members that v lacks, into v, through json/v2; a body over 64 KiB gets a plain 413, and anything else it can't read a plain 400 with the refusal.
  • Unit tests for the package: a body read, the refusals, and the 64 KiB limit.
  • todos (add.go, edit.go) and notify (api.go) call jsonbody.Read; their readBody and maxBody are gone, and todos' body.go with them.
  • Docs: plan §0's row, §8.2 (time-tracking's eighteen steps: shared-json-body and jira-own-writes before worklog-add), and decision 190.

Out of scope. nudge's two readers, whose 400s say what went wrong (read the question: …, read the answer: …) rather than a fixed refusal: sharing them would change what nudge answers. The 64 KiB limit as a setting. Anything of worklog-add itself.

Acceptance criteria.

  1. internal/jsonbody holds Read; no readBody or 64 KiB maxBody is left in todos or notify.
  2. todos' and notify's handler tests pass unchanged: the same answers to the same bodies, 413s included.
  3. The package's own tests cover a body read, the refusals (an unknown member, not JSON, a wrong type, an empty body, two values, invalid UTF-8, a repeated member) and the limit (64 KiB read, a byte more refused), and the package has 100% coverage.
  4. mise run check passes.

What changed

  • internal/jsonbody/jsonbody.go: Read, moved from todos' body.go byte for byte apart from its name and doc (checked with diff), and the unexported maxBody (64 KiB).
  • internal/jsonbody/jsonbody_test.go: a body read (every member, one missing, none), ten refusals, and the 64 KiB limit (64 KiB read; a byte more, and a 64 KiB string, refused with a plain 413).
  • todos: body.go deleted; add.go and edit.go call jsonbody.Read, with the same refusals, and their docs say "64 KiB" rather than maxBody.
  • notify: maxBody and readBody deleted from api/api.go; route.handler calls jsonbody.Read.
  • docs/plan.md: the §0 row; §8.2 time-tracking has eighteen steps, with shared-json-body and jira-own-writes before worklog-add; decision 190 (the split, the shared reader, Jira's search lag and the plan for jira-own-writes, with the evidence and the rejected reconcileIssues).

QA

  • AC1: grep finds MaxBytesReader and RejectUnknownMembers only in internal/jsonbody and nudge's two readers; the only maxBody left in todos or notify is push.go's 500-character cut of a notification's text, which is another thing.
  • AC2: no test file in todos or notify changed; their handler tests pass, todos' against the dev database (TestPostTodosTakesUpTo64KiB and TestPatchTodoTakesUpTo64KiB checked to run, not skip).
  • AC3: internal/jsonbody at 100%.
  • Mutations (mutate.py, kept outside the repo): 17 mutants of Read, among them a byte less or more, no cap, unknown or repeated members, invalid UTF-8, names in any case, one value read with the rest left, a 400 or another text for too large, another status or text for a refusal, and each return flipped. With the package's own tests alone 16 of 17 were caught at first: json.MatchCaseInsensitiveNames(true) survived, so QA added the refusal of {"Title":"Buy milk"} (json/v2 matches names exactly, where v1 didn't). Now 17 of 17 with the package's tests alone, and 17 of 17 with todos' and notify's handler tests added.
  • AC4: mise run --jobs 1 check green: golangci-lint 0 issues, buf lint, govulncheck none, go test -race, coverage 96.1% (thresholds met), web lint, knip, svelte-check, 1108 unit tests, audit, build, and E2E 339 passed + 7 skipped.

Review

  • Fixed: the package doc said "the size that every service takes", which nudge doesn't; it now says "up to 64 KiB".
  • Non-blocking: nudge's readers stay apart. agent.go's answers a 400 with the decoder's error; phone.go's also answers a 400, not a 413, for an answer over its own limit. That's the nudge session's code (nudge-merge), so this step leaves it alone.
  • Non-blocking: null reads as a body and zeroes v (json/v2's rule, checked), as before the move; todos then refuses it for its missing title.
  • Separation of concerns, naming (jsonbody says what it does, clashes with no standard package), error handling (each error answered once) and the gates' limits: no findings.

Merged under the user's standing OK (2026-10-26): merge once CI's check is green, and review everything at the end.

todos and notify each had a copy of the same function that reads a
request's JSON body, and worklog-add would need a third. It moves to
the root internal/, as AGENTS.md asks of code that two services need.
Nothing changes in behaviour.

The plan records decision 190: time-tracking gains this step and
jira-own-writes, since Jira's search lags behind its writes.
@Bakhomious
Bakhomious merged commit dc7bcd8 into main Oct 29, 2026
2 checks passed
@Bakhomious
Bakhomious deleted the step/shared-json-body branch October 29, 2026 20:35
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