use std::path::Path;
use crate::error::FetchError;
pub(crate) async fn write_atomic(
path: &Path,
tmp: &Path,
contents: &[u8],
) -> Result<(), FetchError> {
if let Some(parent) = path.parent() {
tokio::fs::create_dir_all(parent)
.await
.map_err(|e| FetchError::Io {
path: parent.to_path_buf(),
source: e,
})?;
}
tokio::fs::write(tmp, contents)
.await
.map_err(|e| FetchError::Io {
path: tmp.to_path_buf(),
source: e,
})?;
tokio::fs::rename(tmp, path)
.await
.map_err(|e| FetchError::Io {
path: path.to_path_buf(),
source: e,
})?;
Ok(())
}
pub(crate) fn write_atomic_sync(
path: &Path,
tmp: &Path,
contents: &[u8],
) -> Result<(), FetchError> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| FetchError::Io {
path: parent.to_path_buf(),
source: e,
})?;
}
std::fs::write(tmp, contents).map_err(|e| FetchError::Io {
path: tmp.to_path_buf(),
source: e,
})?;
std::fs::rename(tmp, path).map_err(|e| FetchError::Io {
path: path.to_path_buf(),
source: e,
})?;
Ok(())
}
#[cfg(test)]
mod tests {
#![allow(clippy::panic, clippy::unwrap_used, clippy::expect_used)]
use super::*;
#[tokio::test]
async fn write_atomic_creates_missing_parent_dir() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("nested").join("file.json");
let tmp = path.with_extension("json.tmp");
write_atomic(&path, &tmp, b"{}").await.expect("write");
assert!(path.exists());
assert!(!tmp.exists(), "tmp file must be renamed away");
assert_eq!(std::fs::read_to_string(&path).unwrap(), "{}");
}
#[tokio::test]
async fn write_atomic_overwrites_an_existing_file() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("file.json");
let tmp = path.with_extension("json.tmp");
write_atomic(&path, &tmp, b"first").await.expect("write");
write_atomic(&path, &tmp, b"second").await.expect("write");
assert_eq!(std::fs::read_to_string(&path).unwrap(), "second");
}
#[tokio::test]
async fn write_atomic_does_not_leave_tmp_behind() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("file.json");
let tmp = path.with_extension("json.tmp");
write_atomic(&path, &tmp, b"payload").await.expect("write");
assert!(!tmp.exists());
assert!(path.exists());
}
#[test]
fn write_atomic_sync_creates_missing_parent_dir() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("nested").join("file.json");
let tmp = path.with_extension("json.tmp");
write_atomic_sync(&path, &tmp, b"{}").expect("write");
assert!(path.exists());
assert!(!tmp.exists(), "tmp file must be renamed away");
assert_eq!(std::fs::read_to_string(&path).unwrap(), "{}");
}
#[test]
fn write_atomic_sync_overwrites_an_existing_file() {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("file.json");
let tmp = path.with_extension("json.tmp");
write_atomic_sync(&path, &tmp, b"first").expect("write");
write_atomic_sync(&path, &tmp, b"second").expect("write");
assert_eq!(std::fs::read_to_string(&path).unwrap(), "second");
}
}