use std::path::{Path, PathBuf};
pub fn resolve_state_base(config_path: &Path, state_dir: Option<&str>) -> PathBuf {
let base = config_path.parent().unwrap_or_else(|| Path::new(""));
match state_dir {
Some(s) => {
let p = Path::new(s);
if p.is_absolute() {
p.to_path_buf()
} else {
base.join(p)
}
}
None => base.join("state"),
}
}
fn active_workspace(project_root: &Path) -> Option<String> {
crate::cli::workspace::current_workspace_in(project_root)
.filter(|ws| crate::cli::workspace::validate_workspace_name(ws).is_ok())
}
pub fn resolve_state_dir(config_path: &Path, state_dir: Option<&str>) -> PathBuf {
let base = resolve_state_base(config_path, state_dir);
if state_dir.is_some() {
return base;
}
let project_root = config_path.parent().unwrap_or_else(|| Path::new(""));
match active_workspace(project_root) {
Some(ws) => base.join(ws),
None => base,
}
}
pub fn resolve_state_base_opt(config_path: Option<&str>, state_dir: Option<&str>) -> PathBuf {
match config_path {
Some(cp) => resolve_state_base(Path::new(cp), state_dir),
None => PathBuf::from(state_dir.unwrap_or("state")),
}
}
pub fn resolve_state_dir_opt(config_path: Option<&str>, state_dir: Option<&str>) -> PathBuf {
match config_path {
Some(cp) => resolve_state_dir(Path::new(cp), state_dir),
None => PathBuf::from(state_dir.unwrap_or("state")),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn project() -> tempfile::TempDir {
tempfile::tempdir().expect("tempdir")
}
fn select(root: &Path, name: &str) {
std::fs::create_dir_all(root.join(".forjar")).expect("mkdir");
std::fs::write(root.join(".forjar").join("workspace"), name).expect("write");
}
#[test]
fn defaults_beside_the_config_not_the_cwd() {
let d = project();
assert_eq!(
resolve_state_dir(&d.path().join("forjar.yaml"), None),
d.path().join("state"),
"the regression: an absolute config path must not have its state \
looked for in the server's cwd (GH-208)"
);
}
#[test]
fn absolute_override_is_honoured_verbatim() {
let d = project();
assert_eq!(
resolve_state_dir(&d.path().join("forjar.yaml"), Some("/var/lib/st")),
PathBuf::from("/var/lib/st")
);
}
#[test]
fn relative_override_resolves_against_the_config_dir() {
let d = project();
assert_eq!(
resolve_state_dir(&d.path().join("forjar.yaml"), Some("alt-state")),
d.path().join("alt-state"),
"a relative override names a place inside the project"
);
}
#[test]
fn bare_config_name_keeps_historical_behaviour() {
assert_eq!(
resolve_state_base(Path::new("forjar.yaml"), None),
PathBuf::from("state")
);
}
#[test]
fn nested_relative_override() {
let d = project();
assert_eq!(
resolve_state_dir(&d.path().join("b").join("forjar.yaml"), Some("x/y")),
d.path().join("b").join("x").join("y")
);
}
#[test]
fn the_default_follows_the_workspace_selection() {
let d = project();
select(d.path(), "prod");
assert_eq!(
resolve_state_dir(&d.path().join("forjar.yaml"), None),
d.path().join("state").join("prod"),
"the selection the CLI honours was ignored, so a converged \
resource reads back as CREATE (paiml/forjar#367)"
);
}
#[test]
fn an_explicit_state_dir_is_never_joined_onto() {
let d = project();
select(d.path(), "prod");
let designated = d.path().join("state").join("prod");
assert_eq!(
resolve_state_dir(
&d.path().join("forjar.yaml"),
Some(&designated.display().to_string())
),
designated,
"handing `workspace_state_dir` back as `state_dir` — the documented \
workaround — resolved state/prod/prod"
);
}
#[test]
fn the_state_base_never_follows_the_selection() {
let d = project();
select(d.path(), "prod");
assert_eq!(
resolve_state_base(&d.path().join("forjar.yaml"), None),
d.path().join("state"),
"the `workspace` verb enumerates this directory; joining the \
selection here lists machine dirs as workspaces"
);
}
#[test]
fn a_workspace_name_that_is_a_path_is_refused_not_joined() {
let d = project();
select(d.path(), "../../victim");
assert_eq!(
resolve_state_dir(&d.path().join("forjar.yaml"), None),
d.path().join("state"),
"a marker naming a traversal must fall back to the unjoined base, \
never be joined (GH-208 deleted a tree outside the project this way)"
);
}
#[test]
fn no_marker_means_the_bare_state_dir() {
let d = project();
std::fs::create_dir_all(d.path().join(".forjar")).unwrap();
std::fs::write(d.path().join(".forjar").join("workspace"), " \n").unwrap();
assert_eq!(
resolve_state_dir(&d.path().join("forjar.yaml"), None),
d.path().join("state"),
"an empty marker is no selection, not a selection called \"\""
);
}
#[test]
fn the_opt_form_with_no_path_is_unchanged() {
assert_eq!(
resolve_state_dir_opt(None, None),
PathBuf::from("state"),
"with no config there is no project dir to look beside; joining a \
cwd-derived selection onto a cwd-derived base deepens the guess"
);
assert_eq!(
resolve_state_dir_opt(None, Some("/var/lib/st")),
PathBuf::from("/var/lib/st")
);
}
}