thunderstore_engine/profile/
target.rs1#![deny(missing_docs)]
8
9use crate::error::Result;
10use crate::profile::layout;
11use std::path::PathBuf;
12
13#[derive(Debug, Clone)]
15#[non_exhaustive]
16pub struct InstallTarget {
17 pub dir: PathBuf,
19 pub base: PathBuf,
21 pub game_dir: PathBuf,
24 pub game: String,
26 pub profile: Option<String>,
28}
29
30impl InstallTarget {
31 pub fn resolve(
40 base: PathBuf,
41 game_dir: PathBuf,
42 game: &str,
43 profile: Option<&str>,
44 ) -> Result<Self> {
45 let dir = match profile {
46 Some(name) => layout::require_profile(&base, game, name)?,
47 None => game_dir.clone(),
48 };
49
50 Ok(Self {
51 dir,
52 base,
53 game_dir,
54 game: game.to_string(),
55 profile: profile.map(|name| name.to_string()),
56 })
57 }
58
59 pub fn mods_yml(&self) -> PathBuf {
61 self.dir.join("mods.yml")
62 }
63
64 pub fn exports_dir(&self) -> PathBuf {
66 self.base.join(&self.game).join("exports")
67 }
68
69 pub fn name(&self) -> &str {
72 self.profile.as_deref().unwrap_or("default")
73 }
74
75 pub fn needs_staging(&self) -> bool {
88 let dir = self.dir.canonicalize().unwrap_or_else(|_| self.dir.clone());
89 let game = self
90 .game_dir
91 .canonicalize()
92 .unwrap_or_else(|_| self.game_dir.clone());
93
94 dir != game
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use tempfile::tempdir;
102
103 #[test]
104 fn game_dir_mode_targets_the_game_dir_and_never_stages() {
105 let base = tempdir().unwrap();
106 let game_dir = tempdir().unwrap();
107 let target = InstallTarget::resolve(
108 base.path().to_path_buf(),
109 game_dir.path().to_path_buf(),
110 "valheim",
111 None,
112 )
113 .unwrap();
114
115 assert_eq!(target.dir, game_dir.path());
116 assert_eq!(target.name(), "default");
117 assert_eq!(target.mods_yml(), game_dir.path().join("mods.yml"));
118 assert!(!target.needs_staging());
120 }
121
122 #[test]
123 fn profile_mode_targets_the_profile_dir_and_stages() {
124 let base = tempdir().unwrap();
125 let game_dir = tempdir().unwrap();
126
127 crate::profile::layout::create(base.path(), "valheim", "experiment").unwrap();
128
129 let target = InstallTarget::resolve(
130 base.path().to_path_buf(),
131 game_dir.path().to_path_buf(),
132 "valheim",
133 Some("experiment"),
134 )
135 .unwrap();
136
137 assert_eq!(
138 target.dir,
139 base
140 .path()
141 .join("valheim")
142 .join("profiles")
143 .join("experiment")
144 );
145 assert_eq!(target.name(), "experiment");
146 assert!(target.needs_staging());
147 assert_eq!(
148 target.exports_dir(),
149 base.path().join("valheim").join("exports")
150 );
151 }
152
153 #[test]
154 fn resolve_rejects_a_missing_profile() {
155 let base = tempdir().unwrap();
156 let game_dir = tempdir().unwrap();
157
158 let err = InstallTarget::resolve(
159 base.path().to_path_buf(),
160 game_dir.path().to_path_buf(),
161 "valheim",
162 Some("nope"),
163 )
164 .unwrap_err();
165
166 assert!(
167 err.to_string().contains("there is no profile named"),
168 "expected missing profile error, got: {err}"
169 );
170 }
171
172 #[test]
173 fn resolve_rejects_an_unsafe_profile_name() {
174 let base = tempdir().unwrap();
175 let game_dir = tempdir().unwrap();
176
177 let err = InstallTarget::resolve(
178 base.path().to_path_buf(),
179 game_dir.path().to_path_buf(),
180 "valheim",
181 Some("../escape"),
182 )
183 .unwrap_err();
184
185 assert!(
186 err.to_string().contains("invalid profile name"),
187 "expected unsafe name error, got: {err}"
188 );
189 }
190}