forge/
gen_rotation_schema.rs1use 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}