use std::path::{Path, PathBuf};
use rskit_errors::{AppError, AppResult, ErrorCode};
use rskit_fs::sync_io::file;
#[derive(Debug)]
pub struct TempFile {
inner: tempfile::NamedTempFile,
}
impl TempFile {
pub fn new() -> AppResult<Self> {
let inner = tempfile::NamedTempFile::new().map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("failed to create temp file: {e}"),
)
})?;
Ok(Self { inner })
}
pub fn with_extension(ext: &str) -> AppResult<Self> {
let inner = tempfile::Builder::new()
.suffix(&format!(".{ext}"))
.tempfile()
.map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("failed to create temp file with extension .{ext}: {e}"),
)
})?;
Ok(Self { inner })
}
pub fn in_dir(dir: &Path) -> AppResult<Self> {
let inner = tempfile::NamedTempFile::new_in(dir).map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("failed to create temp file in {}: {e}", dir.display()),
)
})?;
Ok(Self { inner })
}
pub fn in_dir_with_extension(dir: &Path, ext: &str) -> AppResult<Self> {
let inner = tempfile::Builder::new()
.suffix(&format!(".{ext}"))
.tempfile_in(dir)
.map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!(
"failed to create temp file in {} with extension .{ext}: {e}",
dir.display()
),
)
})?;
Ok(Self { inner })
}
pub fn path(&self) -> &Path {
self.inner.path()
}
pub fn into_source(self) -> super::FileSource {
super::FileSource::Temp(self)
}
pub fn try_clone(&self) -> AppResult<Self> {
let new = TempFile::new()?;
file::copy(self.path(), new.path()).map_err(|error| error.context("clone temp file"))?;
Ok(new)
}
pub fn persist(self, target: impl AsRef<Path>) -> AppResult<PathBuf> {
let target = target.as_ref().to_path_buf();
self.inner.persist(&target).map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("failed to persist temp file to {}: {e}", target.display()),
)
})?;
Ok(target)
}
}
pub struct TempDir {
inner: tempfile::TempDir,
}
impl TempDir {
pub fn new() -> AppResult<Self> {
let inner = tempfile::TempDir::new().map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("failed to create temp dir: {e}"),
)
})?;
Ok(Self { inner })
}
pub fn path(&self) -> &Path {
self.inner.path()
}
pub fn create_file(&self, name: &str) -> AppResult<TempFile> {
let inner = tempfile::Builder::new()
.prefix(name)
.tempfile_in(self.path())
.map_err(|e| {
AppError::new(
ErrorCode::Internal,
format!("failed to create file {name} in temp dir: {e}"),
)
})?;
Ok(TempFile { inner })
}
pub fn create_file_with_extension(&self, ext: &str) -> AppResult<TempFile> {
TempFile::in_dir_with_extension(self.path(), ext)
}
}
impl std::fmt::Debug for TempDir {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TempDir")
.field("path", &self.inner.path())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn temp_file_clone_source_and_persist_keep_expected_contents() {
let file = TempFile::with_extension("txt").unwrap();
std::fs::write(file.path(), b"hello").unwrap();
let clone = file.try_clone().unwrap();
assert_eq!(std::fs::read(clone.path()).unwrap(), b"hello");
assert!(matches!(clone.into_source(), crate::FileSource::Temp(_)));
let dir = tempfile::tempdir().unwrap();
let target = dir.path().join("persisted.txt");
let persisted = file.persist(&target).unwrap();
assert_eq!(persisted, target);
assert_eq!(std::fs::read(&target).unwrap(), b"hello");
}
#[test]
fn temp_dir_creates_files_and_debug_exposes_path() {
let dir = TempDir::new().unwrap();
let named = dir.create_file("named").unwrap();
let with_ext = dir.create_file_with_extension("bin").unwrap();
let in_dir = TempFile::in_dir(dir.path()).unwrap();
let in_dir_with_ext = TempFile::in_dir_with_extension(dir.path(), "txt").unwrap();
assert!(named.path().starts_with(dir.path()));
assert!(with_ext.path().starts_with(dir.path()));
assert!(in_dir.path().starts_with(dir.path()));
assert_eq!(
in_dir_with_ext
.path()
.extension()
.and_then(|ext| ext.to_str()),
Some("txt")
);
assert!(
format!("{dir:?}").contains(dir.path().file_name().unwrap().to_string_lossy().as_ref())
);
}
#[test]
fn temp_file_new_creates_existing_path() {
let file = TempFile::new().unwrap();
assert!(file.path().exists());
}
#[test]
fn temp_file_reports_invalid_directory_errors() {
let missing =
std::env::temp_dir().join(format!("rskit-storage-missing-{}", std::process::id()));
let error = TempFile::in_dir(&missing).unwrap_err();
assert_eq!(error.code(), ErrorCode::Internal);
assert!(error.to_string().contains("failed to create temp file"));
}
}