camino_tempfile_ext/fixture/
errors.rs

1// Copyright (c) The camino-tempfile Contributors
2// Adapted from assert_fs: Copyright (c) The assert_fs Contributors
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use std::{error::Error, fmt};
7
8pub(crate) trait ChainError {
9    fn chain<F>(self, cause: F) -> Self
10    where
11        F: Error + Send + Sync + 'static;
12}
13
14pub(crate) trait ResultChainExt<T> {
15    fn chain<C>(self, chainable: C) -> Result<T, C>
16    where
17        C: ChainError;
18}
19
20impl<T, E> ResultChainExt<T> for Result<T, E>
21where
22    E: Error + Send + Sync + 'static,
23{
24    fn chain<C>(self, chainable: C) -> Result<T, C>
25    where
26        C: ChainError,
27    {
28        self.map_err(|e| chainable.chain(e))
29    }
30}
31
32/// Fixture initialization cause.
33#[derive(Copy, Clone, Debug, PartialEq, Eq)]
34#[non_exhaustive]
35pub enum FixtureKind {
36    /// Failed when walking the source tree.
37    Walk,
38    /// Failed when copying a file.
39    CopyFile,
40    /// Failed when writing to a file.
41    WriteFile,
42    /// Failed when creating a directory.
43    CreateDir,
44    /// Failed to cleanup a fixture.
45    Cleanup,
46    /// Failed to create a symlink.
47    Symlink,
48}
49
50impl fmt::Display for FixtureKind {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        match *self {
53            FixtureKind::Walk => write!(f, "error walking source tree"),
54            FixtureKind::CopyFile => write!(f, "error copying file"),
55            FixtureKind::WriteFile => write!(f, "error writing file"),
56            FixtureKind::CreateDir => write!(f, "error creating directory"),
57            FixtureKind::Cleanup => write!(f, "error cleaning up fixture"),
58            FixtureKind::Symlink => write!(f, "error creating symlink to target"),
59        }
60    }
61}
62
63/// Failure when initializing the fixture.
64#[derive(Debug)]
65pub struct FixtureError {
66    kind: FixtureKind,
67    source: Option<Box<dyn Error + Send + Sync + 'static>>,
68}
69
70impl FixtureError {
71    /// Create a `FixtureError`.
72    pub fn new(kind: FixtureKind) -> Self {
73        Self { kind, source: None }
74    }
75
76    /// Attach a source to the error.
77    pub fn with_source(
78        mut self,
79        source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
80    ) -> Self {
81        self.source = Some(source.into());
82        self
83    }
84
85    /// Return the fixture initialization cause.
86    pub fn kind(&self) -> FixtureKind {
87        self.kind
88    }
89}
90
91impl Error for FixtureError {
92    fn description(&self) -> &str {
93        "failed to initialize fixture"
94    }
95
96    fn source(&self) -> Option<&(dyn Error + 'static)> {
97        self.source.as_ref().map(|source| &**source as &dyn Error)
98    }
99}
100
101impl fmt::Display for FixtureError {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        write!(f, "failed to initialize fixture: {}", self.kind)
104    }
105}
106
107impl ChainError for FixtureError {
108    fn chain<F>(mut self, source: F) -> Self
109    where
110        F: Error + Send + Sync + 'static,
111    {
112        self.source = Some(Box::new(source));
113        self
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use anyhow::anyhow;
121    use std::io;
122
123    #[test]
124    fn error_types_work_with_source() {
125        // std::io::Error
126        let error = FixtureError::new(FixtureKind::WriteFile).with_source(io::Error::other("test"));
127        assert_eq!(error.kind(), FixtureKind::WriteFile);
128        assert_eq!(error.source().unwrap().to_string(), "test");
129
130        // anyhow::Error
131        let error = FixtureError::new(FixtureKind::WriteFile).with_source(anyhow!("test"));
132        assert_eq!(error.kind(), FixtureKind::WriteFile);
133        assert_eq!(error.source().unwrap().to_string(), "test");
134
135        // another FixtureError
136        let error = FixtureError::new(FixtureKind::WriteFile)
137            .with_source(FixtureError::new(FixtureKind::CopyFile));
138        assert_eq!(error.kind(), FixtureKind::WriteFile);
139        assert_eq!(
140            error.source().unwrap().to_string(),
141            "failed to initialize fixture: error copying file"
142        );
143    }
144}