use std::{fmt, io, path::Path};
#[derive(Debug)]
pub enum NamedWriteError<E> {
Producer {
source: E,
cleanup_error: Option<io::Error>,
},
BeforePublication {
source: io::Error,
cleanup_error: Option<io::Error>,
},
AfterPublication {
source: io::Error,
},
}
impl<E> NamedWriteError<E> {
pub(super) const fn before(source: io::Error) -> Self {
Self::BeforePublication {
source,
cleanup_error: None,
}
}
}
impl<E: fmt::Display> fmt::Display for NamedWriteError<E> {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Producer { source, .. } => write!(formatter, "staged producer failed: {source}"),
Self::BeforePublication { source, .. } => {
write!(formatter, "publication failed before rename: {source}")
}
Self::AfterPublication { source } => write!(
formatter,
"output published but completion failed: {source}"
),
}?;
if let Self::Producer {
cleanup_error: Some(cleanup),
..
}
| Self::BeforePublication {
cleanup_error: Some(cleanup),
..
} = self
{
write!(formatter, "; staging cleanup failed: {cleanup}")?;
}
Ok(())
}
}
impl<E: std::error::Error + 'static> std::error::Error for NamedWriteError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Producer { source, .. } => Some(source),
Self::BeforePublication { source, .. } | Self::AfterPublication { source } => {
Some(source)
}
}
}
}
pub fn write_named_with<T, E>(
path: &Path,
produce: impl FnOnce(&Path) -> Result<T, E>,
) -> Result<T, NamedWriteError<E>> {
#[cfg(any(target_os = "linux", target_os = "android", target_vendor = "apple"))]
{
super::supported::validate_target_path(path).map_err(NamedWriteError::before)?;
let absolute = std::path::absolute(path).map_err(NamedWriteError::before)?;
super::supported::commit_path_with_options(
&absolute,
super::REPLACE_OPTIONS,
|stage, _| produce(stage),
None::<(bool, fn(&Path, &T) -> Result<(), E>)>,
|_, _| Ok(()),
)
}
#[cfg(not(any(target_os = "linux", target_os = "android", target_vendor = "apple")))]
{
let _ = (path, produce);
Err(NamedWriteError::before(io::Error::new(
io::ErrorKind::Unsupported,
"durable atomic file publication is unsupported",
)))
}
}
#[cfg(all(test, unix))]
mod tests;