use std::{ env::temp_dir, path::{Path, PathBuf}, }; use uuid::Uuid; use crate::{ plan::RawPlan, project::{RawProject, CUDDLE_PROJECT_FILE}, }; pub trait NickelPlanExt { fn schema_path(&self, schema: &Path) -> PathBuf; } impl NickelPlanExt for RawPlan { fn schema_path(&self, schema: &Path) -> PathBuf { self.root.join(schema) } } pub trait NickelProjectExt { fn project_path(&self) -> PathBuf; } impl NickelProjectExt for RawProject { fn project_path(&self) -> PathBuf { self.root.join(CUDDLE_PROJECT_FILE) } } fn unique_contract_file() -> anyhow::Result { let p = temp_dir() .join("cuddle") .join("nickel-contracts") .join(Uuid::new_v4().to_string()); std::fs::create_dir_all(&p)?; let file = p.join("contract.ncl"); Ok(TempDirGuard { dir: p, file }) } pub struct TempDirGuard { dir: PathBuf, file: PathBuf, } impl Drop for TempDirGuard { fn drop(&mut self) { if let Err(e) = std::fs::remove_dir_all(&self.dir) { panic!("failed to remove tempdir: {}", e) } } } impl std::ops::Deref for TempDirGuard { type Target = PathBuf; fn deref(&self) -> &Self::Target { &self.file } } pub struct NickelSchemaValidator {} impl NickelSchemaValidator { pub fn validate(plan: &RawPlan, project: &RawProject, nickel: &Path) -> anyhow::Result<()> { let nickel_file = plan.schema_path(nickel); let cuddle_file = project.project_path(); let nickel_file = format!( r##" let {{ProjectSchema, ..}} = import "{}" in let Schema = {{ project | ProjectSchema, .. }} in {{ config | Schema = import "{}" }} "##, nickel_file.display(), cuddle_file.display() ); let contract_file = unique_contract_file()?; std::fs::write(contract_file.as_path(), nickel_file)?; let mut cmd = std::process::Command::new("nickel"); cmd.args(["export", &contract_file.display().to_string()]); let output = cmd.output()?; if !output.status.success() { anyhow::bail!( "failed to run nickel command: output: {} {}", std::str::from_utf8(&output.stdout)?, std::str::from_utf8(&output.stderr)? ) } Ok(()) } }