casper_client/
output_kind.rs

1use std::{
2    fs::{self, File},
3    io::{self, Write},
4    path::{Path, PathBuf},
5};
6
7use rand::{self, distributions::Alphanumeric, Rng};
8
9use crate::Error;
10
11/// An output abstraction for associating a [`Write`] object with some metadata.
12#[derive(Debug, Clone, Eq, PartialEq)]
13pub enum OutputKind<'a> {
14    /// An output file.
15    File {
16        /// The path of the output file.
17        path: &'a Path,
18        /// The path to a temporary file in the same directory as the output file, which is used to
19        /// make the write operation transactional and ensures that the file at `path` is not
20        /// damaged if it pre-exists.
21        tmp_path: PathBuf,
22        /// If `overwrite_if_exists` is `true`, then the file at `path` will be overwritten.
23        overwrite_if_exists: bool,
24    },
25    /// Stdout.
26    Stdout,
27}
28
29impl<'a> OutputKind<'a> {
30    /// Returns a new `OutputKind::File`.
31    pub fn file(path: &'a Path, overwrite_if_exists: bool) -> Self {
32        let collision_resistant_string = rand::thread_rng()
33            .sample_iter(&Alphanumeric)
34            .take(5)
35            .map(char::from)
36            .collect::<String>();
37        let extension = format!(".{}.tmp", &collision_resistant_string);
38        let tmp_path = path.with_extension(extension);
39        OutputKind::File {
40            path,
41            tmp_path,
42            overwrite_if_exists,
43        }
44    }
45
46    /// Returns a `Result` containing a `Write` trait object.
47    pub(super) fn get(&self) -> Result<Box<dyn Write>, Error> {
48        match self {
49            OutputKind::File {
50                path,
51                tmp_path,
52                overwrite_if_exists,
53            } => {
54                if path.exists() && !*overwrite_if_exists {
55                    return Err(Error::FileAlreadyExists(PathBuf::from(path)));
56                }
57                let file = File::create(tmp_path).map_err(|error| Error::IoError {
58                    context: format!("failed to create {}", tmp_path.display()),
59                    error,
60                })?;
61
62                let write: Box<dyn Write> = Box::new(file);
63                Ok(write)
64            }
65            OutputKind::Stdout if cfg!(test) => Ok(Box::new(io::sink())),
66            OutputKind::Stdout => Ok(Box::new(io::stdout())),
67        }
68    }
69
70    /// When `self` is `OutputKind::File`, causes the temp file to be renamed (moved) to its `path`.
71    /// When `self` is `OutputKind::Stdout` this is a no-op.
72    pub(super) fn commit(self) -> Result<(), Error> {
73        match self {
74            OutputKind::File { path, tmp_path, .. } => {
75                fs::rename(&tmp_path, path).map_err(|error| Error::IoError {
76                    context: format!(
77                        "could not move tmp file {} to destination {}",
78                        tmp_path.display(),
79                        path.display()
80                    ),
81                    error,
82                })
83            }
84            OutputKind::Stdout => Ok(()),
85        }
86    }
87}