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

+++ title = "Add build.rs — convert api/openapi.yaml to JSON at compile time for Workers embed" priority = 8 status = "todo" ticket_type = "task" dependencies = [] +++

Resolved from TRIAGE ticket 2ec8b1. The `GET /api/` endpoint must serve the OpenAPI spec as JSON.

The three strategies were:

  1. Compile-time embed (chosen)
  2. Runtime load from filesystem — impossible on Cloudflare Workers (no filesystem)
  3. 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_yaml ships 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.yaml ensures the conversion re-runs whenever the spec is edited — no manual JSON regeneration step needed.
  • api/openapi.yaml remains the single source of truth; the JSON output is ephemeral (in $OUT_DIR, not committed to the repository).
1. Create `build.rs` at the `quotesdb/` project root containing:
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");
}
  1. Add the following to Cargo.toml (ticket 1f5bb5 should also include this):
[build-dependencies]
serde_json = "1"
serde_yaml = "0.9"
  1. Verify the build succeeds and $OUT_DIR/openapi.json is 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