imdl/
file_error.rs

1use crate::common::*;
2
3#[derive(Debug)]
4pub(crate) enum FileError {
5  Io(io::Error),
6  Missing,
7  Directory,
8  Surfeit(Bytes),
9  Dearth(Bytes),
10  Md5 {
11    expected: Md5Digest,
12    actual: Md5Digest,
13  },
14}
15
16impl FileError {
17  pub(crate) fn verify(
18    path: &Path,
19    expected_length: Bytes,
20    expected_md5: Option<Md5Digest>,
21  ) -> Result<(), FileError> {
22    let metadata = match path.metadata() {
23      Ok(metadata) => metadata,
24      Err(error) => {
25        return Err(if error.kind() == io::ErrorKind::NotFound {
26          FileError::Missing
27        } else {
28          FileError::Io(error)
29        })
30      }
31    };
32
33    if metadata.is_dir() {
34      return Err(FileError::Directory);
35    }
36
37    let actual = Bytes(metadata.len());
38
39    let difference = actual.absolute_difference(expected_length);
40
41    if actual > expected_length {
42      return Err(FileError::Surfeit(difference));
43    }
44
45    if actual < expected_length {
46      return Err(FileError::Dearth(difference));
47    }
48
49    if let Some(expected) = expected_md5 {
50      let mut reader = File::open(path)?;
51      let mut context = md5::Context::new();
52      io::copy(&mut reader, &mut context)?;
53      let actual = context.compute().into();
54
55      if actual != expected {
56        return Err(FileError::Md5 { actual, expected });
57      }
58    }
59
60    Ok(())
61  }
62}
63
64impl From<io::Error> for FileError {
65  fn from(io_error: io::Error) -> Self {
66    Self::Io(io_error)
67  }
68}
69
70impl Print for FileError {
71  fn print(&self, stream: &mut OutputStream) -> io::Result<()> {
72    let style = stream.style();
73
74    if let Self::Md5 { actual, expected } = self {
75      write!(
76        stream,
77        "MD5 checksum mismatch: {} (expected {})",
78        style.error().paint(actual.to_string()),
79        style.good().paint(expected.to_string()),
80      )?;
81
82      return Ok(());
83    }
84
85    match self {
86      Self::Io(io_error) => write!(stream, "{io_error}")?,
87      Self::Missing => write!(stream, "File missing")?,
88      Self::Directory => write!(stream, "Expected file but found directory")?,
89      Self::Surfeit(difference) => write!(stream, "{difference} too long")?,
90      Self::Dearth(difference) => write!(stream, "{difference} too short")?,
91      Self::Md5 { .. } => {
92        return Err(io::Error::new(
93          io::ErrorKind::Other,
94          Error::internal("Reached unreachable branch").to_string(),
95        ))
96      }
97    }
98
99    Ok(())
100  }
101}