1use std::{
4 fs::{File, create_dir_all},
5 io::{BufWriter, Write},
6 path::{Path, PathBuf},
7};
8
9use log::trace;
10
11use crate::{
12 Error,
13 identifiers::{Context, Os, Purpose, Technology},
14};
15
16pub trait VerifierWriter {
86 fn to_bytes(&self) -> Result<Vec<u8>, Error>;
92
93 fn technology(&self) -> Technology;
95
96 fn file_name(&self) -> PathBuf;
100
101 fn write_to_hierarchy(
117 &self,
118 path: impl AsRef<Path>,
119 os: Os,
120 purpose: Purpose,
121 context: Option<Context>,
122 ) -> Result<(), Error> {
123 let context = context.unwrap_or_default();
124 let path = path.as_ref();
125 let target_dir = path
126 .join(os.to_string())
127 .join(purpose.to_string())
128 .join(context.to_string())
129 .join(self.technology().to_string());
130
131 trace!("Create parent directory for verifier: {target_dir:?}");
132 create_dir_all(&target_dir).map_err(|source| Error::IoPath {
133 path: target_dir.clone(),
134 context: "creating the directory",
135 source,
136 })?;
137
138 let file_path = target_dir.join(self.file_name());
139 trace!("Write verifier data to file: {file_path:?}");
140 if file_path.exists() && !file_path.is_file() {
142 return Err(Error::ExpectedFile {
143 path: file_path.to_path_buf(),
144 });
145 }
146 let mut writer =
147 BufWriter::new(File::create(&file_path).map_err(|source| Error::IoPath {
148 path: file_path.clone(),
149 context: "creating file for writing",
150 source,
151 })?);
152 writer
153 .write_all(&self.to_bytes()?)
154 .map_err(|source| Error::IoPath {
155 path: file_path,
156 context: "writing the verifier contents",
157 source,
158 })?;
159
160 Ok(())
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use std::{fs::read_to_string, os::unix::fs::symlink};
167
168 use log::debug;
169 use simplelog::{ColorChoice, Config, LevelFilter, TermLogger, TerminalMode};
170 use tempfile::tempdir;
171 use testresult::TestResult;
172
173 use super::*;
174 use crate::identifiers::CustomTechnology;
175
176 const VERIFIER_DATA: &str = "test";
177
178 struct TestWriter;
179
180 impl VerifierWriter for TestWriter {
181 fn to_bytes(&self) -> Result<Vec<u8>, Error> {
182 Ok(VERIFIER_DATA.as_bytes().to_vec())
183 }
184
185 fn technology(&self) -> Technology {
186 Technology::Custom(CustomTechnology::new("technology".parse().unwrap()))
187 }
188
189 fn file_name(&self) -> PathBuf {
190 PathBuf::from("dummy.test")
191 }
192 }
193
194 fn init_logger() {
196 if TermLogger::init(
197 LevelFilter::Trace,
198 Config::default(),
199 TerminalMode::Stderr,
200 ColorChoice::Auto,
201 )
202 .is_err()
203 {
204 debug!("Not initializing another logger, as one is initialized already.");
205 }
206 }
207
208 #[test]
210 fn write_to_hierarchy_succeeds() -> TestResult {
211 init_logger();
212
213 let test_dir = tempdir()?;
214 let path = test_dir.path();
215 let test_writer = TestWriter;
216
217 test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None)?;
218
219 let target_dir = path
220 .join("os")
221 .join("purpose")
222 .join("default")
223 .join("technology");
224 assert!(target_dir.is_dir());
225
226 let verifier_file = target_dir.join(test_writer.file_name());
227 assert!(verifier_file.is_file());
228 let verifier_contents = read_to_string(verifier_file)?;
229 assert_eq!(verifier_contents, VERIFIER_DATA);
230
231 Ok(())
232 }
233
234 #[test]
237 fn write_to_hierarchy_fails_on_target_is_dir() -> TestResult {
238 init_logger();
239
240 let test_dir = tempdir()?;
241 let path = test_dir.path();
242 let test_writer = TestWriter;
243
244 let target_file = path
246 .join("os")
247 .join("purpose")
248 .join("default")
249 .join("technology")
250 .join(test_writer.file_name());
251 create_dir_all(&target_file)?;
252 assert!(target_file.is_dir());
253
254 match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
255 Ok(()) => {
256 return Err(format!(
257 "Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
258 ).into());
259 }
260 Err(error) => match error {
261 Error::ExpectedFile { .. } => {}
262 error => {
263 return Err(format!("Expected Error::ExpectedFile, but got:\n{error}").into());
264 }
265 },
266 }
267
268 Ok(())
269 }
270
271 #[test]
274 fn write_to_hierarchy_fails_on_target_is_symlink() -> TestResult {
275 init_logger();
276
277 let test_dir = tempdir()?;
278 let path = test_dir.path();
279 let test_writer = TestWriter;
280
281 let parent_dir = path
283 .join("os")
284 .join("purpose")
285 .join("default")
286 .join("technology");
287 create_dir_all(&parent_dir)?;
288 let target_file = parent_dir.join(test_writer.file_name());
289 symlink("/dev/null", &target_file)?;
291 assert!(target_file.is_symlink());
292
293 match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
294 Ok(()) => {
295 return Err(format!(
296 "Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
297 ).into());
298 }
299 Err(error) => match error {
300 Error::ExpectedFile { .. } => {}
301 error => {
302 return Err(format!("Expected Error::ExpectedFile, but got:\n{error}").into());
303 }
304 },
305 }
306
307 Ok(())
308 }
309
310 #[test]
313 fn write_to_hierarchy_fails_on_dir_structure_has_file() -> TestResult {
314 init_logger();
315
316 let test_dir = tempdir()?;
317 let path = test_dir.path();
318 let test_writer = TestWriter;
319
320 let parent_dir = path.join("os").join("purpose").join("default");
322 create_dir_all(&parent_dir)?;
323
324 let parent_file = parent_dir.join("technology");
326 let mut file = File::create(&parent_file)?;
327 file.write_all(b"Occupied!")?;
328 assert!(parent_file.is_file());
329 let target_file = parent_file.join(test_writer.file_name());
330
331 match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
332 Ok(()) => {
333 return Err(format!(
334 "Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
335 ).into());
336 }
337 Err(error) => match error {
338 Error::IoPath { .. } => {}
339 error => {
340 return Err(format!("Expected Error::IoPath, but got:\n{error}").into());
341 }
342 },
343 }
344
345 Ok(())
346 }
347
348 #[test]
351 fn write_to_hierarchy_fails_on_dir_structure_has_symlink() -> TestResult {
352 init_logger();
353
354 let test_dir = tempdir()?;
355 let path = test_dir.path();
356 let test_writer = TestWriter;
357
358 let parent_dir = path.join("os").join("purpose").join("default");
360 create_dir_all(&parent_dir)?;
361
362 let parent_symlink = parent_dir.join("technology");
364 symlink("/dev/null", &parent_symlink)?;
365 assert!(parent_symlink.is_symlink());
366 let target_file = parent_symlink.join(test_writer.file_name());
367
368 match test_writer.write_to_hierarchy(path, "os".parse()?, "purpose".parse()?, None) {
369 Ok(()) => {
370 return Err(format!(
371 "Should have failed but succeeded to write a verifier to the VOA hierarchy at {target_file:?}"
372 ).into());
373 }
374 Err(error) => match error {
375 Error::IoPath { .. } => {}
376 error => {
377 return Err(format!("Expected Error::IoPath, but got:\n{error}").into());
378 }
379 },
380 }
381
382 Ok(())
383 }
384}