ts_io/
read_file.rs

1//! Wrappers over [`fs::read`] to return user friendly errors.
2
3use std::{
4    fs, io,
5    path::{Path, PathBuf},
6};
7
8use ts_path::DisplayPath;
9
10/// Error variants for reading a file.
11#[derive(Debug)]
12#[non_exhaustive]
13#[allow(missing_docs)]
14pub enum ReadFileError {
15    #[non_exhaustive]
16    DoesNotExist { path: PathBuf },
17
18    #[non_exhaustive]
19    NotAFile { path: PathBuf },
20
21    #[non_exhaustive]
22    ReadError { path: PathBuf, source: io::Error },
23}
24impl core::fmt::Display for ReadFileError {
25    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
26        match &self {
27            Self::DoesNotExist { path, .. } => {
28                write!(f, "`{}` does not exist", path.opinionated_display())
29            }
30            Self::NotAFile { path, .. } => {
31                write!(f, "`{}` is not a file", path.opinionated_display())
32            }
33            Self::ReadError { path, .. } => {
34                write!(f, "could not read `{}`", path.opinionated_display())
35            }
36        }
37    }
38}
39impl core::error::Error for ReadFileError {
40    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
41        match &self {
42            Self::ReadError { source, .. } => Some(source),
43            _ => None,
44        }
45    }
46}
47impl ReadFileError {
48    #[allow(clippy::missing_docs_in_private_items)]
49    pub(crate) fn read_error(source: io::Error, path: &Path) -> Self {
50        Self::ReadError {
51            path: path.to_path_buf(),
52            source,
53        }
54    }
55}
56
57/// Read a file, returning presentable error variants.
58pub fn read_file(path: &Path) -> Result<Vec<u8>, ReadFileError> {
59    if !fs::exists(path).map_err(|source| ReadFileError::read_error(source, path))? {
60        return Err(ReadFileError::DoesNotExist {
61            path: path.to_path_buf(),
62        });
63    }
64
65    let metadata = path
66        .metadata()
67        .map_err(|source| ReadFileError::read_error(source, path))?;
68
69    if metadata.is_dir() {
70        return Err(ReadFileError::NotAFile {
71            path: path.to_path_buf(),
72        });
73    }
74
75    fs::read(path).map_err(|source| ReadFileError::read_error(source, path))
76}
77
78/// Read a file to a string, returning presentable error variants.
79pub fn read_file_to_string(path: &Path) -> Result<String, ReadFileError> {
80    if !fs::exists(path).map_err(|source| ReadFileError::read_error(source, path))? {
81        return Err(ReadFileError::DoesNotExist {
82            path: path.to_path_buf(),
83        });
84    }
85
86    let metadata = path
87        .metadata()
88        .map_err(|source| ReadFileError::read_error(source, path))?;
89
90    if metadata.is_dir() {
91        return Err(ReadFileError::NotAFile {
92            path: path.to_path_buf(),
93        });
94    }
95
96    fs::read_to_string(path).map_err(|source| ReadFileError::read_error(source, path))
97}