web: std/web package: WebError, Handler and the request and response helpers (step 1 of #94) #575

Closed
opened 2026-10-29 08:21:16 +00:00 by orion · 2 comments
Collaborator

Context

Step 1 of the epic #94 (WEB slice 2, std/web core): the package std/web with its error type, its handler type and the helpers that turn a std/http Request and Response into the vocabulary the rest of the slice builds on. No middleware, no web { } block and no test helper yet (steps 2 and 3 of #94).

Source

  • WEB.md sections 3.1, 3.4 and 3.8: the handler type, the helper table, WebError.
  • WEB.md section 13: D13 (package std/web, no HTTP in std/html), D10 (a panic ends the process, so no 500 page for one).
  • std/http Request, Response, Handler, HttpError (std/http/src/message.trb, server.trb).

Design record point

WEB.md slice 2, first part; D13. Decided by the loop (details in the comment on #94):

  • std/web declares type Handler = (request: Request) => Task<Result<Response, WebError>> with Request and Response taken from std/http. It does not re-export std/http's Handler; a program that needs both writes use * as http from "std/http".
  • WebError is a capsule: status(): Status, a public message, a private cause(): Error?; constructors badRequest, forbidden, notFound, payloadTooLarge, unprocessable, internal; From<DecodeError> (400) and From<HttpError> (400, the cause kept; the mapping is a table in the doc comment). WebError.response() renders the default error page of its status; the errors hook of the application is step 2.
  • Helpers are members added by extend on std/http's Request and Response: Request.decodedSegments() (percent-decoded, empty segments dropped; Request.segments() of std/http stays as it is), Request.queryParameters() and queryParameter(name) over String.formDecoded of std/uri; Response.html(page) (200, text/html; charset=utf-8), Response.redirect(to: Link) (303), withStatus, withHeader. Response.jsonOf of std/http already exists and is used as it is.
  • Request and Response are shared types that own a stream, not values (WEB.md section 9 assumed values). The participles therefore answer a new Response that takes over the same Body; the old binding is not read afterwards. The doc comment says so, a test holds it, and WEB.md section 3.4 gets a "Built" note saying where this departs from the record, like URI.md section 9a does.
  • Not in this step: request.query<Value>() (needs the form-urlencoded Decoder that #97 builds), request.form, cookies and withCookie (#98), request.json (Response.json of std/http exists; the request side follows with forms).
  • Touches the seed? No: new package, no natives, no std name the compiler looks up by string.

Acceptance criteria

  • std/web/project.trb and src/ exist with WebError, Handler and the helpers above, exported from lib.trb; every public item has a doc comment with an example.
  • Tests (std/web/tests/): the status of each WebError constructor and the From conversions; the default error page per status; decodedSegments on /orders/a%20b//7 answers ["orders", "a b", "7"]; queryParameter on a repeated and on a missing name; Response.html sets status and content type; Response.redirect is 303 with Location; withStatus and withHeader leave the headers and the body readable on the answered response. Handlers in the tests are fns answering a Task that the test file awaits at its top level (a test body cannot wait yet, WEB.md F13).
  • Docs page docs/standard-library/web.md, linked from the index; skills/ regenerated (torb docs skill docs skills).
  • WEB.md: the status line and slice table say slice 2 is partly built, with the "Built" note on Request/Response above.
  • Lands as at most two Conventional Commits: feat(std/web): add WebError and Handler, feat(std/web): add the request and response helpers.

Dependencies

  • Needs: #93 (closed), std/http Request/Response (on main), std/uri Link users via std/html (on main).
  • Unblocks: step 2 of #94 (Middleware, Next, WebApp, web { }), #97, #98.

Part of #94.

## Context Step 1 of the epic #94 (WEB slice 2, `std/web` core): the package `std/web` with its error type, its handler type and the helpers that turn a `std/http` `Request` and `Response` into the vocabulary the rest of the slice builds on. No middleware, no `web { }` block and no test helper yet (steps 2 and 3 of #94). ## Source - [WEB.md sections 3.1, 3.4 and 3.8](https://git.torb.dev/torbscript/language/src/commit/4b59fae049eb4348fdb204d8849ed29997cbb797/docs/design/WEB.md#L116): the handler type, the helper table, `WebError`. - [WEB.md section 13](https://git.torb.dev/torbscript/language/src/commit/4b59fae049eb4348fdb204d8849ed29997cbb797/docs/design/WEB.md#L1030): D13 (package `std/web`, no HTTP in `std/html`), D10 (a panic ends the process, so no 500 page for one). - `std/http` `Request`, `Response`, `Handler`, `HttpError` (`std/http/src/message.trb`, `server.trb`). ## Design record point WEB.md slice 2, first part; D13. Decided by the loop (details in the comment on #94): - `std/web` declares `type Handler = (request: Request) => Task<Result<Response, WebError>>` with `Request` and `Response` taken from `std/http`. It does not re-export `std/http`'s `Handler`; a program that needs both writes `use * as http from "std/http"`. - `WebError` is a capsule: `status(): Status`, a public message, a private `cause(): Error?`; constructors `badRequest`, `forbidden`, `notFound`, `payloadTooLarge`, `unprocessable`, `internal`; `From<DecodeError>` (400) and `From<HttpError>` (400, the cause kept; the mapping is a table in the doc comment). `WebError.response()` renders the default error page of its status; the `errors` hook of the application is step 2. - Helpers are members added by `extend` on `std/http`'s `Request` and `Response`: `Request.decodedSegments()` (percent-decoded, empty segments dropped; `Request.segments()` of `std/http` stays as it is), `Request.queryParameters()` and `queryParameter(name)` over `String.formDecoded` of `std/uri`; `Response.html(page)` (200, `text/html; charset=utf-8`), `Response.redirect(to: Link)` (303), `withStatus`, `withHeader`. `Response.jsonOf` of `std/http` already exists and is used as it is. - `Request` and `Response` are `shared type`s that own a stream, not values (WEB.md section 9 assumed values). The participles therefore answer a new `Response` that takes over the same `Body`; the old binding is not read afterwards. The doc comment says so, a test holds it, and WEB.md section 3.4 gets a "Built" note saying where this departs from the record, like URI.md section 9a does. - Not in this step: `request.query<Value>()` (needs the form-urlencoded `Decoder` that #97 builds), `request.form`, cookies and `withCookie` (#98), `request.json` (`Response.json` of `std/http` exists; the request side follows with forms). - Touches the seed? No: new package, no natives, no std name the compiler looks up by string. ## Acceptance criteria - [ ] `std/web/project.trb` and `src/` exist with `WebError`, `Handler` and the helpers above, exported from `lib.trb`; every public item has a doc comment with an example. - [ ] Tests (`std/web/tests/`): the status of each `WebError` constructor and the `From` conversions; the default error page per status; `decodedSegments` on `/orders/a%20b//7` answers `["orders", "a b", "7"]`; `queryParameter` on a repeated and on a missing name; `Response.html` sets status and content type; `Response.redirect` is 303 with `Location`; `withStatus` and `withHeader` leave the headers and the body readable on the answered response. Handlers in the tests are `fn`s answering a `Task` that the test file awaits at its top level (a `test` body cannot wait yet, WEB.md F13). - [ ] Docs page `docs/standard-library/web.md`, linked from the index; `skills/` regenerated (`torb docs skill docs skills`). - [ ] WEB.md: the status line and slice table say slice 2 is partly built, with the "Built" note on `Request`/`Response` above. - [ ] Lands as at most two Conventional Commits: `feat(std/web): add WebError and Handler`, `feat(std/web): add the request and response helpers`. ## Dependencies - Needs: #93 (closed), `std/http` `Request`/`Response` (on `main`), `std/uri` `Link` users via `std/html` (on `main`). - Unblocks: step 2 of #94 (`Middleware`, `Next`, `WebApp`, `web { }`), #97, #98. Part of #94.
Author
Collaborator

[loop:torbscript] claim implement by session:loop-torbscript-20261029 until 2026-10-29T14:35Z

[loop:torbscript] claim implement by session:loop-torbscript-20261029 until 2026-10-29T14:35Z
Author
Collaborator

[loop:torbscript] implement: PR #578 open, CI pending (commit messages, what the change touches and pdisc green; gates, bootstrap and tiers queued on the runners), review requested from argus, next: waiting (forge CI and merge gate). Local: 47 std/web tests pass, format and docs checks clean. Tokens: one implementer run (Sonnet 5.5), usage in the run record.

[loop:torbscript] implement: PR https://git.torb.dev/torbscript/language/pulls/578 open, CI pending (commit messages, what the change touches and pdisc green; gates, bootstrap and tiers queued on the runners), review requested from argus, next: waiting (forge CI and merge gate). Local: 47 std/web tests pass, format and docs checks clean. Tokens: one implementer run (Sonnet 5.5), usage in the run record.
argus closed this issue 2026-10-29 13:25:55 +00:00
Sign in to join this conversation.
No labels
loop
ready
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language#575
No description provided.