1use std::{fmt, io};
4
5use crate::path::{Path, PathBuf};
6
7#[derive(Clone, Copy, Debug)]
8pub(crate) enum Operation {
9 Canonicalize,
10 CreateDirectory,
11 CreateFile,
12 #[cfg(not(target_family = "wasm"))]
13 CreateLink,
14 Inspect,
15 OpenFile,
16 ReadDirectory,
17 ReadFile,
18 RemoveDirectory,
19 RemoveFile,
20 Rename,
21 #[cfg(not(target_family = "wasm"))]
22 RetainFile,
23 #[cfg(not(target_family = "wasm"))]
24 SecureFile,
25 Sync,
26 WriteFile,
27}
28
29impl fmt::Display for Operation {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let value = match self {
32 Self::Canonicalize => "canonicalize",
33 Self::CreateDirectory => "create directory",
34 Self::CreateFile => "create file",
35 #[cfg(not(target_family = "wasm"))]
36 Self::CreateLink => "create symbolic link",
37 Self::Inspect => "inspect",
38 Self::OpenFile => "open file",
39 Self::ReadDirectory => "read directory",
40 Self::ReadFile => "read file",
41 Self::RemoveDirectory => "remove directory",
42 Self::RemoveFile => "remove file",
43 Self::Rename => "rename",
44 #[cfg(not(target_family = "wasm"))]
45 Self::RetainFile => "retain file",
46 #[cfg(not(target_family = "wasm"))]
47 Self::SecureFile => "secure file",
48 Self::Sync => "sync",
49 Self::WriteFile => "write file",
50 };
51
52 f.write_str(value)
53 }
54}
55
56#[derive(Debug, thiserror::Error)]
58#[error("failed to {operation} {path}{destination}: {source}")]
59pub struct Error {
60 operation: Operation,
61 path: PathBuf,
62 destination: Destination,
63 #[source]
64 source: io::Error,
65}
66
67impl Error {
68 pub(crate) fn new(operation: Operation, path: &Path, source: io::Error) -> Self {
69 Self {
70 operation,
71 path: path.to_path_buf(),
72 destination: Destination(None),
73 source,
74 }
75 }
76
77 pub(crate) fn between(
78 operation: Operation,
79 path: &Path,
80 other_path: &Path,
81 source: io::Error,
82 ) -> Self {
83 Self {
84 operation,
85 path: path.to_path_buf(),
86 destination: Destination(Some(other_path.to_path_buf())),
87 source,
88 }
89 }
90
91 #[must_use]
93 pub fn path(&self) -> &Path {
94 &self.path
95 }
96
97 #[must_use]
99 pub fn other_path(&self) -> Option<&Path> {
100 self.destination.0.as_deref()
101 }
102
103 #[must_use]
105 pub fn is_not_found(&self) -> bool {
106 self.source.kind() == io::ErrorKind::NotFound
107 }
108
109 #[must_use]
111 pub fn is_already_exists(&self) -> bool {
112 self.source.kind() == io::ErrorKind::AlreadyExists
113 }
114
115 #[must_use]
117 pub fn is_directory_not_empty(&self) -> bool {
118 self.source.kind() == io::ErrorKind::DirectoryNotEmpty
119 }
120}
121
122#[derive(Debug)]
123struct Destination(Option<PathBuf>);
124
125impl fmt::Display for Destination {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 if let Some(path) = &self.0 {
128 write!(f, " to {path}")?;
129 }
130
131 Ok(())
132 }
133}
134
135pub type Result<T> = std::result::Result<T, Error>;
137
138#[cfg(test)]
139mod tests {
140 use std::{error::Error as _, io};
141
142 use googletest::prelude::*;
143
144 use super::{Error, Operation};
145 use crate::path::Path;
146
147 #[gtest]
148 fn single_path_error_preserves_context_source_and_predicate() -> Result<()> {
149 let error = Error::new(
150 Operation::ReadFile,
151 Path::new("config.toml"),
152 io::Error::new(io::ErrorKind::NotFound, "missing"),
153 );
154
155 verify_eq!(
156 error.to_string(),
157 "failed to read file config.toml: missing"
158 )?;
159 verify_eq!(error.path(), Path::new("config.toml"))?;
160 verify_true!(error.other_path().is_none())?;
161 verify_true!(error.is_not_found())?;
162
163 verify_true!(error.source().is_some())
164 }
165
166 #[gtest]
167 fn two_path_error_preserves_both_paths() -> Result<()> {
168 let error = Error::between(
169 Operation::Rename,
170 Path::new("before"),
171 Path::new("after"),
172 io::Error::new(io::ErrorKind::AlreadyExists, "occupied"),
173 );
174
175 verify_eq!(
176 error.to_string(),
177 "failed to rename before to after: occupied"
178 )?;
179 verify_eq!(error.path(), Path::new("before"))?;
180 verify_eq!(error.other_path(), Some(Path::new("after")))?;
181
182 verify_true!(error.is_already_exists())
183 }
184}