#![deny(missing_docs)]
use crate::error::Result;
use crate::profile::layout;
use std::path::PathBuf;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct InstallTarget {
pub dir: PathBuf,
pub base: PathBuf,
pub game_dir: PathBuf,
pub game: String,
pub profile: Option<String>,
}
impl InstallTarget {
pub fn resolve(
base: PathBuf,
game_dir: PathBuf,
game: &str,
profile: Option<&str>,
) -> Result<Self> {
let dir = match profile {
Some(name) => layout::require_profile(&base, game, name)?,
None => game_dir.clone(),
};
Ok(Self {
dir,
base,
game_dir,
game: game.to_string(),
profile: profile.map(|name| name.to_string()),
})
}
pub fn mods_yml(&self) -> PathBuf {
self.dir.join("mods.yml")
}
pub fn exports_dir(&self) -> PathBuf {
self.base.join(&self.game).join("exports")
}
pub fn name(&self) -> &str {
self.profile.as_deref().unwrap_or("default")
}
pub fn needs_staging(&self) -> bool {
let dir = self.dir.canonicalize().unwrap_or_else(|_| self.dir.clone());
let game = self
.game_dir
.canonicalize()
.unwrap_or_else(|_| self.game_dir.clone());
dir != game
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[test]
fn game_dir_mode_targets_the_game_dir_and_never_stages() {
let base = tempdir().unwrap();
let game_dir = tempdir().unwrap();
let target = InstallTarget::resolve(
base.path().to_path_buf(),
game_dir.path().to_path_buf(),
"valheim",
None,
)
.unwrap();
assert_eq!(target.dir, game_dir.path());
assert_eq!(target.name(), "default");
assert_eq!(target.mods_yml(), game_dir.path().join("mods.yml"));
assert!(!target.needs_staging());
}
#[test]
fn profile_mode_targets_the_profile_dir_and_stages() {
let base = tempdir().unwrap();
let game_dir = tempdir().unwrap();
crate::profile::layout::create(base.path(), "valheim", "experiment").unwrap();
let target = InstallTarget::resolve(
base.path().to_path_buf(),
game_dir.path().to_path_buf(),
"valheim",
Some("experiment"),
)
.unwrap();
assert_eq!(
target.dir,
base
.path()
.join("valheim")
.join("profiles")
.join("experiment")
);
assert_eq!(target.name(), "experiment");
assert!(target.needs_staging());
assert_eq!(
target.exports_dir(),
base.path().join("valheim").join("exports")
);
}
#[test]
fn resolve_rejects_a_missing_profile() {
let base = tempdir().unwrap();
let game_dir = tempdir().unwrap();
let err = InstallTarget::resolve(
base.path().to_path_buf(),
game_dir.path().to_path_buf(),
"valheim",
Some("nope"),
)
.unwrap_err();
assert!(
err.to_string().contains("there is no profile named"),
"expected missing profile error, got: {err}"
);
}
#[test]
fn resolve_rejects_an_unsafe_profile_name() {
let base = tempdir().unwrap();
let game_dir = tempdir().unwrap();
let err = InstallTarget::resolve(
base.path().to_path_buf(),
game_dir.path().to_path_buf(),
"valheim",
Some("../escape"),
)
.unwrap_err();
assert!(
err.to_string().contains("invalid profile name"),
"expected unsafe name error, got: {err}"
);
}
}