You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3.4 KiB
3.4 KiB
+++ title = "Add build.rs — convert api/openapi.yaml to JSON at compile time for Workers embed" priority = 8 status = "done" ticket_type = "task" dependencies = [] +++
Resolved from TRIAGE ticket 2ec8b1. The `GET /api/` endpoint must serve the OpenAPI spec as JSON.The three strategies were:
- Compile-time embed (chosen)
- Runtime load from filesystem — impossible on Cloudflare Workers (no filesystem)
- utoipa programmatic generation — significant complexity; spec already exists and is complete
The chosen approach: a build.rs script reads api/openapi.yaml, parses it to a
serde_json::Value, serialises it as compact JSON, and writes the result to
$OUT_DIR/openapi.json. The GET /api/ handler then serves this via:
const OPENAPI_JSON: &str = include_str!(concat!(env!("OUT_DIR"), "/openapi.json"));
This means:
serde_yamlships only as a[build-dependencies]entry — it never enters the Workers binary.- The handler is a zero-overhead static string response with no runtime parsing.
cargo:rerun-if-changed=api/openapi.yamlensures the conversion re-runs whenever the spec is edited — no manual JSON regeneration step needed.api/openapi.yamlremains the single source of truth; the JSON output is ephemeral (in$OUT_DIR, not committed to the repository).
use std::{env, fs, path::Path};
fn main() {
// Re-run this script whenever the OpenAPI spec changes.
println!("cargo:rerun-if-changed=api/openapi.yaml");
let yaml =
fs::read_to_string("api/openapi.yaml").expect("api/openapi.yaml not found");
// Parse YAML to a generic JSON value, then re-serialise as compact JSON.
// serde_yaml is a build-only dependency — it does not appear in the final binary.
let value: serde_json::Value =
serde_yaml::from_str(&yaml).expect("api/openapi.yaml is invalid YAML");
let json = serde_json::to_string(&value).expect("JSON serialisation failed");
let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set");
let out_path = Path::new(&out_dir).join("openapi.json");
fs::write(&out_path, json).expect("failed to write openapi.json");
}
- Add the following to
Cargo.toml(ticket 1f5bb5 should also include this):
[build-dependencies]
serde_json = "1"
serde_yaml = "0.9"
- Verify the build succeeds and
$OUT_DIR/openapi.jsonis produced:
cargo check
# $OUT_DIR is typically target/debug/build/quotesdb-*/out/openapi.json
- `serde_yaml` must be a `[build-dependencies]` entry only — NOT in `[dependencies]`.
Adding it to `[dependencies]` would bloat the Workers WASM binary.
- Do NOT commit `$OUT_DIR/openapi.json` — it is generated automatically at build time.
- The `build.rs` file lives at the crate root (same level as `Cargo.toml`), not in `src/`.
- `api/openapi.yaml` is the source of truth; do not create or commit an `api/openapi.json`.
Ticket 28e7d9 (GET /api/ handler) depends on this ticket. The handler uses
`include_str!(concat!(env!("OUT_DIR"), "/openapi.json"))` to serve the spec — see 28e7d9
for the Axum handler implementation.
```sh
cargo fmt
cargo check
cargo clippy
cargo test
```
`chore(quotesdb): add build.rs to convert api/openapi.yaml to JSON at compile time`
quotesdb/api