use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let root = workspace_root()?;
let schema_path = root.join("shared/protocol/events.schema.json");
let schema_raw = fs::read_to_string(&schema_path)?;
let schema: Value = serde_json::from_str(&schema_raw)?;
let values = parse_patch_types(&schema)?;
let rust_out = root.join("in-cli/src/hotreload/generated_protocol.rs");
let swift_out =
root.join("runtime/swift-preview-host/Sources/SwiftPreviewHost/GeneratedProtocol.swift");
write_rust(&rust_out, &values)?;
write_swift(&swift_out, &values)?;
println!("generated: {}", rust_out.display());
println!("generated: {}", swift_out.display());
Ok(())
}
fn workspace_root() -> Result<PathBuf, Box<dyn std::error::Error>> {
if let Some(arg) = std::env::args().nth(1) {
return Ok(PathBuf::from(arg));
}
let mut dir = std::env::current_dir()?;
loop {
if dir.join("shared/protocol/events.schema.json").exists() {
return Ok(dir);
}
if !dir.pop() {
break;
}
}
Err(
"could not find repository root containing shared/protocol/events.schema.json (pass root as argv[1])"
.into(),
)
}
fn parse_patch_types(schema: &Value) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let arr = schema
.pointer("/$defs/ReloadPatch/properties/patch_type/enum")
.and_then(|v| v.as_array())
.ok_or("schema missing $defs.ReloadPatch.properties.patch_type.enum")?;
let mut out = Vec::new();
for item in arr {
let s = item
.as_str()
.ok_or("patch_type enum values must be strings")?;
out.push(s.to_string());
}
if out.is_empty() {
return Err("patch_type enum is empty".into());
}
Ok(out)
}
fn snake_to_pascal(value: &str) -> String {
value
.split('_')
.filter(|p| !p.is_empty())
.map(|part| {
let mut c = part.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
})
.collect()
}
fn snake_to_camel(value: &str) -> String {
let pascal = snake_to_pascal(value);
let mut ch = pascal.chars();
match ch.next() {
None => String::new(),
Some(f) => f.to_lowercase().collect::<String>() + ch.as_str(),
}
}
fn write_rust(out_path: &Path, values: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let mut lines: Vec<String> = Vec::new();
lines.push("// AUTO-GENERATED by inauguration protocol-gen (Rust)".to_string());
lines.push("// Source: shared/protocol/events.schema.json".to_string());
lines.push(String::new());
lines.push(
"#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]".to_string(),
);
lines.push("#[serde(rename_all = \"snake_case\")]".to_string());
lines.push("pub enum PatchType {".to_string());
for v in values {
lines.push(format!(" {},", snake_to_pascal(v)));
}
lines.push("}".to_string());
lines.push(String::new());
fs::write(out_path, lines.join("\n"))?;
Ok(())
}
fn write_swift(out_path: &Path, values: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let mut lines: Vec<String> = Vec::new();
lines.push("// AUTO-GENERATED by inauguration protocol-gen (Rust)".to_string());
lines.push("// Source: shared/protocol/events.schema.json".to_string());
lines.push(String::new());
lines.push("public enum GeneratedWirePatchType: String, Codable, Sendable {".to_string());
for v in values {
lines.push(format!(" case {} = \"{}\"", snake_to_camel(v), v));
}
lines.push("}".to_string());
lines.push(String::new());
fs::write(out_path, lines.join("\n"))?;
Ok(())
}