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