Skip to content

feat: add multipart call-site identifier#77

Merged
crhntr merged 1 commit intomainfrom
feat/multipart-identifier
Apr 27, 2026
Merged

feat: add multipart call-site identifier#77
crhntr merged 1 commit intomainfrom
feat/multipart-identifier

Conversation

@crhntr
Copy link
Copy Markdown
Member

@crhntr crhntr commented Apr 26, 2026

Summary

  • Adds a new multipart call-site identifier to muxt template names, peer to the existing form identifier, that triggers request.ParseMultipartForm and supports binding *multipart.FileHeader / []*multipart.FileHeader struct fields alongside all existing form field types.
  • Two modes (mirroring form): raw mode (param type *multipart.Form — the parsed form is handed over directly) and struct-binding mode (text fields parsed via the existing generateParseValueFromStringStatements codepath; file fields bound from request.MultipartForm.File).
  • New CLI flag --output-multipart-max-memory=<size> accepts human-readable byte sizes (32MB, 64MiB, 1GB) via github.com/dustin/go-humanize. Default 32 MiB.
  • ParseMultipartForm errors are captured into td.errList with td.errStatusCode = http.StatusBadRequest (diverges from the silent-ignore behavior of ParseForm because multipart parse errors are real client-side faults). File reads are guarded by if request.MultipartForm != nil since the stdlib leaves it nil on parse failure.
  • Mutual exclusion: form and multipart in the same call site is a generation error.
  • Reuses existing name and template struct tags — no new tag namespace.
  • Documentation updated: docs/reference/template-names.md parameter-source table.

Test plan

  • go test ./... passes
  • 10 new txtar cases in cmd/muxt/testdata/:
    • reference_multipart_basic, _multiple_files, _mixed, _raw, _with_name_tag
    • reference_multipart_max_memory_flag — asserts the literal ParseMultipartForm(<bytes>) and a functional upload test
    • reference_multipart_parse_error — functional test: malformed multipart body → 400 via td.errList
    • err_multipart_with_form, err_multipart_unsupported_field_type
    • howto_multipart_file_upload — task-oriented walkthrough
  • gofumpt clean

Out of scope

  • Numeric-suffix identifiers like multipart32MB for per-route limits
  • Per-field tags for max file size or content-type allowlist (maxsize, accept)
  • Streaming uploads via *multipart.Reader
  • Auto-detecting multipart from Content-Type at runtime
  • Changing ParseForm error-handling semantics for the existing form identifier

Note: humanize uses standards-correct unit semantics — KB/MB/GB are decimal (1000-based), KiB/MiB/GiB are binary (1024-based). Use MiB if you want power-of-two values.

Assisted-by: Claude:claude-opus-4-7 gopls staticcheck gofumpt

Copilot AI review requested due to automatic review settings April 26, 2026 23:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds first-class multipart form handling to muxt template call-site identifiers, enabling generated handlers to parse multipart/form-data requests and bind uploaded files into strongly-typed receiver method parameters.

Changes:

  • Introduces a new multipart template call-site identifier (mutually exclusive with form) and adds generation support for raw *multipart.Form and struct-binding modes.
  • Adds --output-multipart-max-memory=<size> CLI flag (human-readable bytes) to control request.ParseMultipartForm’s maxMemory.
  • Updates docs and expands txtar-based generator tests for multipart parsing, binding, and error cases.

Reviewed changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/muxt/definition.go Extends the recognized template call-site scope identifiers to include multipart.
internal/generate/routes.go Implements multipart parsing + struct binding (including *multipart.FileHeader and []*multipart.FileHeader) and enforces form/multipart mutual exclusion.
internal/cli/commands.go Adds CLI flag parsing for multipart max memory via go-humanize.
go.mod Adds github.com/dustin/go-humanize dependency.
go.sum Updates sums for the new dependency and dependency graph changes.
docs/reference/template-names.md Documents multipart identifier semantics, supported types, mutual exclusion, and maxMemory flag.
cmd/muxt/testdata/reference_multipart_basic.txt Adds generator + runtime test for single *multipart.FileHeader binding.
cmd/muxt/testdata/reference_multipart_multiple_files.txt Adds generator + runtime test for []*multipart.FileHeader binding.
cmd/muxt/testdata/reference_multipart_mixed.txt Adds generator + runtime test for mixed text + file multipart struct binding.
cmd/muxt/testdata/reference_multipart_raw.txt Adds generator + runtime test for raw *multipart.Form parameter mode.
cmd/muxt/testdata/reference_multipart_with_name_tag.txt Adds generator + runtime test for name tag rebinding with multipart fields.
cmd/muxt/testdata/reference_multipart_max_memory_flag.txt Adds test asserting ParseMultipartForm(<bytes>) literal respects the new flag.
cmd/muxt/testdata/reference_multipart_parse_error.txt Adds runtime test asserting malformed multipart bodies produce 400 via td.errStatusCode.
cmd/muxt/testdata/err_multipart_with_form.txt Adds negative test ensuring form and multipart are rejected together.
cmd/muxt/testdata/err_multipart_unsupported_field_type.txt Adds negative test for unsupported multipart-bound field types.
cmd/muxt/testdata/howto_multipart_file_upload.txt Adds a task-oriented walkthrough test case for multipart file uploads.

Comment thread internal/generate/routes.go Outdated
Comment thread internal/cli/commands.go Outdated
Comment thread internal/cli/commands.go
Comment thread internal/cli/commands.go
Comment thread internal/generate/routes.go Outdated
@crhntr crhntr force-pushed the feat/multipart-identifier branch 3 times, most recently from 32cd910 to ac54a7d Compare April 27, 2026 00:14
@crhntr crhntr force-pushed the feat/multipart-identifier branch from ac54a7d to 2dbde2d Compare April 27, 2026 00:41
Adds a peer to the existing form identifier: multipart triggers
request.ParseMultipartForm and supports binding *multipart.FileHeader
and []*multipart.FileHeader fields on the receiver method's struct
param, alongside all existing form field types.

- Raw mode: receiver param is *multipart.Form (escape hatch).
- Struct mode: receiver param is a struct; text fields bind via
  request.PostForm, file fields via request.MultipartForm.File.
- form and multipart are mutually exclusive in one call site.
- ParseMultipartForm errors are captured into td.errList with
  td.errStatusCode = http.StatusBadRequest (real client-side faults).
- Default maxMemory is 32 MiB; override with the generator flag
  --output-multipart-max-memory=<size> (parses 32MB, 64MiB, etc.).

Assisted-by: Claude:claude-opus-4-7 gopls staticcheck gofumpt
@crhntr crhntr force-pushed the feat/multipart-identifier branch from 2dbde2d to a9b3bba Compare April 27, 2026 00:48
@crhntr crhntr merged commit d512342 into main Apr 27, 2026
1 of 2 checks passed
@crhntr crhntr deleted the feat/multipart-identifier branch April 27, 2026 01:15
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.

2 participants