systemprompt_database/
squash_baseline.rs1use std::path::{Path, PathBuf};
13
14use thiserror::Error;
15
16const CRATE_LAYERS: [&str; 5] = ["domain", "infra", "app", "shared", "entry"];
17
18#[derive(Debug, Error)]
19pub enum SquashBaselineError {
20 #[error(
21 "Could not locate source crate for extension '{extension_id}'. Tried: {tried:?}. The \
22 squash tool maps extension id → crate dir as crates/{{layer}}/{{id}}; if your extension \
23 lives elsewhere, write the baseline file by hand."
24 )]
25 ExtensionCrateNotFound {
26 extension_id: String,
27 tried: Vec<String>,
28 },
29
30 #[error("Failed to create directory {}", path.display())]
31 CreateDir {
32 path: PathBuf,
33 #[source]
34 source: std::io::Error,
35 },
36
37 #[error("Failed to write baseline SQL to {}", path.display())]
38 WriteBaseline {
39 path: PathBuf,
40 #[source]
41 source: std::io::Error,
42 },
43}
44
45#[derive(Debug, Clone, Copy)]
46pub struct SquashBaselineService;
47
48impl SquashBaselineService {
49 pub fn baseline_target_path(
50 start_dir: &Path,
51 extension_id: &str,
52 through: u32,
53 ) -> Result<PathBuf, SquashBaselineError> {
54 let crate_dir = locate_extension_crate(start_dir, extension_id)?;
55 Ok(crate_dir
56 .join("schema")
57 .join("migrations")
58 .join(format!("000_baseline_v{through}.sql")))
59 }
60
61 pub fn write_baseline_file(path: &Path, sql: &str) -> Result<(), SquashBaselineError> {
62 if let Some(parent) = path.parent() {
63 std::fs::create_dir_all(parent).map_err(|source| SquashBaselineError::CreateDir {
64 path: parent.to_path_buf(),
65 source,
66 })?;
67 }
68 std::fs::write(path, sql).map_err(|source| SquashBaselineError::WriteBaseline {
69 path: path.to_path_buf(),
70 source,
71 })
72 }
73}
74
75fn locate_extension_crate(
76 start_dir: &Path,
77 extension_id: &str,
78) -> Result<PathBuf, SquashBaselineError> {
79 let repo_root = find_repo_root(start_dir).unwrap_or_else(|| start_dir.to_path_buf());
80
81 let mut tried = Vec::new();
82 for layer in CRATE_LAYERS {
83 let candidate = repo_root.join("crates").join(layer).join(extension_id);
84 if candidate.join("Cargo.toml").is_file() {
85 return Ok(candidate);
86 }
87 tried.push(candidate.display().to_string());
88 }
89
90 Err(SquashBaselineError::ExtensionCrateNotFound {
91 extension_id: extension_id.to_owned(),
92 tried,
93 })
94}
95
96fn find_repo_root(start: &Path) -> Option<PathBuf> {
97 let mut cur = start;
98 loop {
99 if cur.join("Cargo.toml").is_file() && cur.join("crates").is_dir() {
100 return Some(cur.to_path_buf());
101 }
102 cur = cur.parent()?;
103 }
104}