use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use tokio::fs;
pub async fn ensure_dir_exists(path: &Path) -> Result<()> {
if !path.exists() {
fs::create_dir_all(path)
.await
.with_context(|| format!("Failed to create directory: {}", path.display()))?;
}
Ok(())
}
pub async fn read_file_with_context(path: &Path, context: &str) -> Result<String> {
fs::read_to_string(path)
.await
.with_context(|| format!("Failed to read {}: {}", context, path.display()))
}
pub async fn write_file_with_context(path: &Path, content: &str, context: &str) -> Result<()> {
if let Some(parent) = path.parent() {
ensure_dir_exists(parent).await?;
}
fs::write(path, content)
.await
.with_context(|| format!("Failed to write {}: {}", context, path.display()))
}
pub async fn write_json_file<T: Serialize>(path: &Path, data: &T) -> Result<()> {
let json = serde_json::to_string_pretty(data)
.with_context(|| format!("Failed to serialize data for {}", path.display()))?;
write_file_with_context(path, &json, "JSON data").await
}
pub async fn read_json_file<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T> {
let content = read_file_with_context(path, "JSON file").await?;
serde_json::from_str(&content)
.with_context(|| format!("Failed to parse JSON from {}", path.display()))
}
pub fn parse_json_with_context<T: for<'de> Deserialize<'de>>(
content: &str,
context: &str,
) -> Result<T> {
serde_json::from_str(content).with_context(|| format!("Failed to parse JSON from {}", context))
}
pub fn serialize_json_with_context<T: Serialize>(data: &T, context: &str) -> Result<String> {
serde_json::to_string(data).with_context(|| format!("Failed to serialize JSON for {}", context))
}
pub fn serialize_json_pretty_with_context<T: Serialize>(data: &T, context: &str) -> Result<String> {
serde_json::to_string_pretty(data)
.with_context(|| format!("Failed to pretty-serialize JSON for {}", context))
}
pub fn canonicalize_with_context(path: &Path, context: &str) -> Result<PathBuf> {
path.canonicalize().with_context(|| {
format!(
"Failed to canonicalize {} path: {}",
context,
path.display()
)
})
}
pub fn ensure_dir_exists_sync(path: &Path) -> Result<()> {
if !path.exists() {
std::fs::create_dir_all(path)
.with_context(|| format!("Failed to create directory: {}", path.display()))?;
}
Ok(())
}
pub fn read_file_with_context_sync(path: &Path, context: &str) -> Result<String> {
std::fs::read_to_string(path)
.with_context(|| format!("Failed to read {}: {}", context, path.display()))
}
pub fn write_file_with_context_sync(path: &Path, content: &str, context: &str) -> Result<()> {
if let Some(parent) = path.parent() {
ensure_dir_exists_sync(parent)?;
}
std::fs::write(path, content)
.with_context(|| format!("Failed to write {}: {}", context, path.display()))
}
pub fn write_json_file_sync<T: Serialize>(path: &Path, data: &T) -> Result<()> {
let json = serde_json::to_string_pretty(data)
.with_context(|| format!("Failed to serialize data for {}", path.display()))?;
write_file_with_context_sync(path, &json, "JSON data")
}
pub fn read_json_file_sync<T: for<'de> Deserialize<'de>>(path: &Path) -> Result<T> {
let content = read_file_with_context_sync(path, "JSON file")?;
serde_json::from_str(&content)
.with_context(|| format!("Failed to parse JSON from {}", path.display()))
}
pub fn is_image_path(path: &Path) -> bool {
let Some(extension) = path.extension().and_then(|ext| ext.to_str()) else {
return false;
};
matches!(
extension,
_ if extension.eq_ignore_ascii_case("png")
|| extension.eq_ignore_ascii_case("jpg")
|| extension.eq_ignore_ascii_case("jpeg")
|| extension.eq_ignore_ascii_case("gif")
|| extension.eq_ignore_ascii_case("bmp")
|| extension.eq_ignore_ascii_case("webp")
|| extension.eq_ignore_ascii_case("tiff")
|| extension.eq_ignore_ascii_case("tif")
|| extension.eq_ignore_ascii_case("svg")
)
}