use anyhow::{bail, Context, Result};
use pushkin_compiler::{compile, CompileRequest, Target};
use pushkin_core::manifest::Manifest;
use super::authoring::{emit_schema_from_zod, export_name_for};
use super::load_manifest;
pub fn regenerate_one(manifest: &Manifest, file_name: &str) -> Result<(), String> {
let (contract_name, target) = invert_target_for(file_name)
.ok_or_else(|| format!("'{file_name}' does not match any generated-file pattern"))?;
let contract = manifest
.contracts
.iter()
.find(|c| c.name.as_str() == contract_name)
.ok_or_else(|| format!("no contract '{contract_name}' in the manifest"))?;
if !contract.emit.iter().any(|e| e == target_key(target)) {
return Err(format!(
"contract '{contract_name}' does not emit '{}'",
target_key(target)
));
}
let schema_path = format!("schemas/{contract_name}.schema.json");
let schema_json = std::fs::read_to_string(&schema_path)
.map_err(|e| format!("cannot read {schema_path}: {e}"))?;
let binding = compile(&CompileRequest {
contract_name: contract_name.clone(),
schema_json,
target,
epoch: manifest.schema_epoch,
})
.map_err(|e| e.to_string())?;
let out_path = format!("generated/{file_name}");
std::fs::write(&out_path, binding.content).map_err(|e| format!("cannot write {out_path}: {e}"))
}
fn invert_target_for(file_name: &str) -> Option<(String, Target)> {
let inversions: [(&str, Target); 4] = [
(".zod.gen.ts", Target::Zod),
("_models.gen.py", Target::Pydantic),
(".gen.rs", Target::Rust),
(".gen.sql", Target::Sql),
];
inversions.iter().find_map(|(suffix, target)| {
file_name
.strip_suffix(suffix)
.map(|contract| (contract.to_owned(), *target))
})
}
fn target_key(target: Target) -> &'static str {
match target {
Target::Zod => "zod",
Target::Pydantic => "pydantic",
Target::Rust => "rust",
Target::Sql => "sql",
}
}
pub fn run() -> Result<i32> {
let manifest = load_manifest()?;
std::fs::create_dir_all("generated").context("cannot create generated/")?;
std::fs::create_dir_all("schemas").context("cannot create schemas/")?;
for contract in &manifest.contracts {
let schema_path = format!("schemas/{}.schema.json", contract.name.as_str());
if std::path::Path::new(&contract.source).exists() {
let export = export_name_for(contract.name.as_str());
let schema = emit_schema_from_zod(&contract.source, &export, manifest.schema_epoch)?;
std::fs::write(&schema_path, schema)
.with_context(|| format!("cannot write {schema_path}"))?;
println!("wrote {schema_path}");
}
let schema_json = std::fs::read_to_string(&schema_path).with_context(|| {
format!(
"cannot read {schema_path} (no canonical schema and no authoring source \
'{}' to emit one from)",
contract.source
)
})?;
for emit in &contract.emit {
let (target, file_name) = target_for(emit, contract.name.as_str())?;
let binding = compile(&CompileRequest {
contract_name: contract.name.as_str().to_owned(),
schema_json: schema_json.clone(),
target,
epoch: manifest.schema_epoch,
})?;
let out_path = format!("generated/{file_name}");
std::fs::write(&out_path, binding.content)
.with_context(|| format!("cannot write {out_path}"))?;
println!("wrote {out_path}");
}
}
Ok(0)
}
fn target_for(emit: &str, contract: &str) -> Result<(Target, String)> {
let pair = match emit {
"zod" => (Target::Zod, format!("{contract}.zod.gen.ts")),
"pydantic" => (Target::Pydantic, format!("{contract}_models.gen.py")),
"rust" => (Target::Rust, format!("{contract}.gen.rs")),
"sql" => (Target::Sql, format!("{contract}.gen.sql")),
other => bail!("unknown emit target '{other}' (supported: zod, pydantic, rust, sql)"),
};
Ok(pair)
}