- media_type(req): content-type lowercased, "; charset=..." stripped,
"" when absent — the content-negotiation hook
- form_values(req): application/x-www-form-urlencoded body -> decoded
pairs through the existing query decoder ('+' as space, %XX); nil on
any other content-type so a JSON body is never misread as a form key
- web-app CreateProduct accepts form OR JSON; shared create_product
insert path; field/number validation answers 400
- probe 7/7 (plus/pct decode, empty value, case + charset param, json
and missing content-type nil, empty body, media_type strip) + ASan
- gate grows 17 -> 19: form create 201 with decoded name, non-numeric
price 400; hit() gains a content-type argument
- README: checklist row form ✅ (multipart stays candidate), limits
paragraph updated; story 16 + board record the landing
- gates: web-app 19/0, oop-e2e 89/0, deps-accept 8/0, log-watcher 7/0,
employee 8/0, woc-test green
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| main.wo | ||
| README.md | ||
| types.wo | ||
| wo.toml | ||
web-app — the storefront sample
A small store: Product/Order as @table classes, JSON routes, one auth
middleware — built on writeonce-framework, which
it imports through [deps] (iteration 15). This app is iteration 16's
acceptance workload: just web-app runs the whole chain — fetch → lock →
build → serve → curl matrix → restart persistence → SIGTERM.
Routes
| Route | What |
|---|---|
GET /products |
list (JSON array) |
GET /products/:id |
one product or 404 |
POST /products |
create from a JSON body (name, price, stock); 400 on malformed JSON; 409 on a duplicate name (@unique) |
POST /orders |
create (product, qty); FK checked |
DELETE /products/:id |
409 while orders reference it (FK restrict), 200 after |
Every request needs authorization: Bearer <token> (the auth middleware);
the token comes from the WA_TOKEN env var.
Run
woc . # fetches deps, builds target/web-app
WA_TOKEN=secret WO_DATA=./data ./target/web-app 8080
TLS / HTTP2
None here, deliberately: deploy behind nginx/caddy — the proxy terminates TLS+ALPN and speaks h2 to browsers while this backend serves HTTP/1.1 keep-alive. Sketch:
server {
listen 443 ssl;
http2 on;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}