Skip to main content

forge/
gen_rotation_schema.rs

1//! `gen-rotation-schema` subcommand: emit the rotation DSL JSON Schema the Supabase `ai` edge function feeds the LLM.
2
3use anyhow::{Context, Result};
4use wowlab_fs::{atomic, path::Path};
5use wowlab_types::sim::{Condition, Rotation, RotationAction};
6
7use crate::constants::engine_dir;
8
9#[derive(Debug, clap::Args)]
10pub(crate) struct GenRotationSchemaArgs;
11
12pub(crate) fn run(_args: &GenRotationSchemaArgs) -> Result<()> {
13    let repo_root = engine_dir()
14        .parent()
15        .context("engine crate has no parent")?
16        .parent()
17        .context("crates dir has no parent")?
18        .to_path_buf();
19    let out = repo_root.join("supabase/functions/ai/rotation.schema.json");
20    let json = schema_document()?;
21
22    persist(&out, &json)?;
23
24    wowlab_common::output::info(&format!("wrote rotation schema: {}", out.display()));
25
26    Ok(())
27}
28
29fn schema_document() -> Result<String> {
30    let doc = serde_json::json!({
31        "$comment": "@generated by `cargo forge gen-rotation-schema` from the wowlab-types rotation AST. Do not edit by hand.",
32        "rotation": schemars::schema_for!(Rotation),
33        "action": schemars::schema_for!(RotationAction),
34        "condition": schemars::schema_for!(Condition),
35    });
36
37    let mut json =
38        serde_json::to_string_pretty(&doc).context("failed to serialize rotation schema")?;
39
40    json.push('\n');
41
42    Ok(json)
43}
44
45fn persist(path: &Path, contents: &str) -> Result<()> {
46    atomic::replace(path, contents).with_context(|| format!("failed to write {}", path.display()))
47}
48
49#[cfg(test)]
50mod tests {
51    use googletest::prelude::*;
52    use wowlab_fs::{directory, file, temporary::Directory};
53
54    use super::{persist, schema_document};
55
56    #[gtest]
57    fn generated_schema_is_newline_terminated_and_atomically_replaced() -> Result<()> {
58        let temporary = Directory::new().or_fail()?;
59        let output = temporary.path().join("rotation.schema.json");
60        let schema = schema_document().or_fail()?;
61
62        verify_true!(schema.ends_with('\n'))?;
63        verify_true!(!schema.ends_with("\n\n"))?;
64
65        persist(&output, "stale").or_fail()?;
66        persist(&output, &schema).or_fail()?;
67
68        verify_that!(file::read_text(&output).or_fail()?, eq(&schema))?;
69
70        verify_that!(directory::entries(temporary.path()).or_fail()?, len(eq(1)))
71    }
72}