Skip to content

Put both upload routes in the API spec, and refuse a form part that is not a file - #25

Open
pallaoro wants to merge 1 commit into
mainfrom
uploads-spec
Open

pallaoro wants to merge 1 commit into
mainfrom
uploads-spec

Conversation

@pallaoro

@pallaoro pallaoro commented Sep 4, 2026

Copy link
Copy Markdown
Member

Uploads were the one corner of this API a generated client could not use.

The upload field had no type. POST /api/uploads published its file
property as {} — nothing in the document said it wanted a file. It now carries
the standard multipart spelling:

"file": { "type": "string", "format": "binary" }

The route that serves the file was not in the spec at all. The upload
response hands back a URL under /api/uploads/{filename}, but that route was
registered as a plain route, so it never reached /api/openapi.json. A client
generated from the spec could follow the URL it had just been given only by
guessing. It is now declared with its path parameter, a media-range body for the
stored bytes, and its 404.

A text part named file returned a 500. The handler cast the form part to a
file without checking, so a string satisfied the request schema and then blew up
on .arrayBuffer(). It is now an instance check, which returns the 400 the route
already meant to return — and declaring that 400 clears the repository's only
type error.

Verified

Against the real app, with the storage binding stubbed:

upload a file 200 { url, filename }
read it back 200, original bytes, original content type, immutable cache header
unknown filename 404
form part is a string 400 { "error": "No file" } (was 500)
no file part 400 from request validation

tsc --noEmit is clean; it was not before. The app's own uploader sends a real
file and is unaffected.

…s not a file

Uploads were the one corner of this API a generated client could not use. The
spec described `POST /api/uploads` as taking a property named `file` with no
type at all, so nothing could tell it wanted a file, and the URL that route
hands back pointed at `GET /api/uploads/{filename}`, which was registered as a
plain route and therefore never appeared in the document — a client could upload
by guesswork and then had no declared path to read the file back. Both are now
declared: the form part carries the standard binary spelling, and the serving
route is in the spec with its path parameter, a media-range body for the stored
bytes, and its 404.

The upload handler also cast the form part to a file without checking. A text
part named `file` satisfies the request schema, so it reached `.arrayBuffer()`
and came back as a 500. It is now an instance check, which is the 400 the route
already meant to return and, until now, could not declare — that undeclared 400
was the repository's only type error.

Nothing that worked before changes. The app's own uploader sends a real file and
takes the same round trip: 200 with the URL, then the file back with its own
content type and the immutable cache header, and a 404 for a filename that is
not stored.
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