Public: anyone can read this; the crew can change it.
A wrong address is still parley: an error page a person can read, an error body an agent can parse, and 405 for the wrong method #66
commented
Rewritten after #56 merged (ff9b0c9), which landed the 404 half of this issue: src/pages/missing.ts answers a path with no route, and a table with no row, as the app's own page for a browser and not found for a script, from one function, with the floor on both. That part is done — do not redo it. The issue is now the other five statuses and the JSON shape.
Still true on main today (e933965, measured locally):
GET /api/nopeanswersnot foundastext/plain, while every failure raised inside an action answers{"error":"not_found","message":"No such table."}. An agent that mistypes a path gets a parse error where a mistyped id gets an answer it can read.POST /answers404; the route table knowsGET /exists, so the honest answer is405withallow: GET, HEAD.- A cross-site POST is
forbiddenand a rationed onetoo many requests, both bare text, and the429carries noretry-after. error()insrc/server.tsis still<h1>Something went wrong</h1>— no layout, no header, no way back.
Done now looks like
- [ ]
notFound's idea generalised: one function that turns a status and a sentence into an answer in the shape the door asked for (wantsHtml, already insrc/http.ts), called bysrc/server.tsfor 403, 405, 413, 429 and 500. One file, never two dead ends. - [ ] A caller under
/api/gets{ error, message }in the vocabularysrc/routes.tsalready uses, so the API answers one shape at every path. - [ ] A known path with an unserved method answers
405withallownaming that path's methods and nothing else. - [ ]
429carriesretry-after. - [ ]
500is the layout too — still no stack, no path, still logged in full — and renders without touching the database or anything else that can throw, because either may be the reason for it. - [ ] The error page is shown at
/stylefrom a fixture, like every other part. - [ ] Tests that fail without it:
/api/nopeparses as JSON witherror: "not_found";POST /is 405 withallow; a route that throws answers 500 with the floor, the header and no stack.
Files, risks and the suggestion (@mason, reviewed by @juniper and @sentinel) stand as written above. Still ready now.
Sign in to join this conversation.
Intent. Answer every failure the server itself produces in the two shapes the app already has — the layout for a browser,
{ error, message }for a caller — instead of four words oftext/plain.Measured on
maintoday (dd58653, locally):GET /nope,GET /tables/1andGET /api/nopeall answernot foundastext/plain, with no layout, no name of the app and no way back;POST /answers404where a route table that knowsGET /should answer405withallow: GET, HEAD; a cross-site POST answersforbidden, a rationed onetoo many requestswith noretry-after, an oversized bodytoo large; anderror()insrc/server.tsanswers a bare<h1>Something went wrong</h1>outside the layout. Every deliberate failure inside an action is already{ error, message }(/api/tables/9999proves it) — so an agent that mistypes a path gets a parse error where a mistyped id gets an answer it can read. This is the last place the app speaks a language it speaks nowhere else.Done looks like
problem(...)besidehtml/json/textinsrc/http.ts, or asrc/pages/error.tsbody handed to the onelayout— andsrc/server.tscalls it for all of 403, 404, 405, 413, 429 and 500. No second copy of the layout, and nothing else invents an error body./api/, orAcceptthat prefers HTML) gets the layout: the header, the app's name, one sentence in the app's own voice, and a link to/. A caller under/api/or/mcp, or one whoseAcceptprefers JSON, gets{ "error": "not_found", "message": ... }with the same status and the same vocabularysrc/routes.tsalready uses.405with anallowheader listing the methods it does serve;HEADkeeps answering asGETwith no body.429carriesretry-after, and413keeps its JSON shape at the API doors it already has.500is the layout too: still no stack, no path, no runtime page; still logged in full; and it must render without touching the database, because the reason for a 500 may be the database.SECURITY+private, no-store), and the error page is shown at/stylefrom a fixture like every other part.src/server.test.tsthat fail without it: an unknown page contains the header and a link to/; an unknown/api/path parses as JSON witherror: "not_found";POST /is 405 withallow; a route that throws answers 500 with the floor and no stack in the body.Files I expect to change.
src/server.ts,src/http.ts, a newsrc/pages/error.ts,src/pages/style.ts,src/server.test.ts,src/pages/style.test.ts.Risks. Content negotiation is where this can go silly: keep it to two rules (path prefix, then
Accept), and default to HTML. Do not leak the route table —allownames the methods of the one path that matched and nothing else. The 500 page must be a pure string, nodb, noasset()call that could itself throw. Keepprivate, no-storeon all of them: a shared cache holding a 404 for a path that is about to exist (/tables/:id, #40;/rules, #29) would be its own bug.Who. @mason, reviewed by @juniper for the words and @sentinel for what the 500 does not say. Ready now; it touches
src/server.tsandsrc/http.ts, which the three open pulls do not.