| new file mode 100644 |
| index 0000000..322a565 |
| --- /dev/null |
| +++ b/apps/rust/README.md |
| @@ -0,0 +1,70 @@ |
| +# todo-rust |
| + |
| +TodoMVC for the CARLOS bakeoff, in Rust. The server renders every page |
| +and handles every action as a plain form POST, so the app works with |
| +JavaScript off. A small ES module (`static/app.js`) sends the same POSTs |
| +with `fetch` and swaps in the updated list, so with JavaScript on it |
| +behaves like the reference TodoMVC. |
| + |
| +Live at [https://todo-rust.bfj.oncarlos.com](https://todo-rust.bfj.oncarlos.com). |
| + |
| +## The stack, and why |
| + |
| +| Piece | Choice | Why | |
| +|---|---|---| |
| +| HTTP server and router | [axum](https://docs.rs/axum) 0.8 on tokio (hyper underneath) | The default choice for a Rust web service today. It serves a unix socket and a TCP port through the same `axum::serve`, and graceful shutdown on SIGTERM is one call | |
| +| Templates | [maud](https://maud.lambda.xyz) | HTML written as Rust macros and checked at compile time. Every interpolated value is escaped unless you opt out, which is what you want for user-typed titles | |
| +| SQLite | [rusqlite](https://docs.rs/rusqlite) with `bundled` | SQLite is compiled from source into the binary, so there is no shared library to find at run time. One connection behind a mutex, used from tokio's blocking pool | |
| +| Allocator | [mimalloc](https://docs.rs/mimalloc) | musl's own allocator is slow when many threads allocate; mimalloc is the usual fix for static musl builds | |
| +| Randomness | [getrandom](https://docs.rs/getrandom) | 128-bit cookie tokens straight from the OS | |
| +| Forms and query strings | [serde](https://serde.rs) (through axum's `Form` and `Query`) | | |
| + |
| +Flags are parsed by hand (there are three), and cookies too (there is |
| +one). The shared CSS files are compiled into the binary with |
| +`include_str!`, so they are served byte for byte. |
| + |
| +Lists are kept apart by a `todo_list` cookie holding 128 random bits. |
| +A cookie the app didn't issue gets a fresh list. Every query names the |
| +list, so an id from someone else's list is a 404. A POST whose `Origin` |
| +isn't the app's own is a 403. See `AGENTS.md` for the tenancy rules. |
| + |
| +### Layout |
| + |
| +- `src/main.rs`: flags, socket or TCP listener, SIGTERM. |
| +- `src/app.rs`: routes and handlers, including `GET /`. |
| +- `src/views.rs`: the markup, following `shared/reference.html`. |
| +- `src/db.rs`: schema, migrations (additive, tracked by `PRAGMA user_version`) and queries. |
| +- `static/app.js`: the JavaScript layer. |
| +- `tests/http.rs`: tests that drive the router in-process. |
| + |
| +### Without JavaScript |
| + |
| +The reference markup has checkboxes and a double-click to edit, neither |
| +of which can post a form. The page adds a submit button over each |
| +checkbox (and over the toggle-all arrow) and a link over each title that |
| +opens `?edit=<id>`. They're transparent and sit exactly on the parts of |
| +the design they stand in for, so the page looks the same either way. A |
| +`<noscript>` stylesheet turns them on; with JavaScript they stay |
| +`display: none`. |
| + |
| +## Build, run, test, deploy |
| + |
| +Toolchains come from the repo: the Makefile sources `scripts/env.sh` |
| +itself. Build output goes in `.cache/target` and `dist/`. |
| + |
| +```sh |
| +make build # dist/app for this machine |
| +make build-arm64 # dist/app-linux-arm64, static (cargo zigbuild, musl) |
| +make test # cargo test |
| +dist/app --listen 127.0.0.1:8080 --db "$TMPDIR/rust.db" |
| +../../scripts/test-app.sh http://127.0.0.1:8080 "$TMPDIR/rust-suite.json" |
| +../../scripts/boot-check.sh dist/app-linux-arm64 |
| +make deploy MESSAGE="what changed" |
| +``` |
| + |
| +`make clean` removes `dist/` and this crate's own compiled files. |
| +`make clean-deps` removes all of `.cache/target`, so the next build |
| +recompiles every dependency (the downloaded sources in `~/.cargo` stay). |
| + |
| +The version `/api/version` reports is the git short SHA at build time, |
| +stamped in through the `APP_VERSION` environment variable. |