use crate::env::AnalysisEnv;
use crate::errors::AnalysisError;
use std::path::PathBuf;
use stillwater::effect::prelude::*;
use stillwater::Effect;
pub fn read_file_effect<Env>(
path: PathBuf,
) -> impl Effect<Output = String, Error = AnalysisError, Env = Env>
where
Env: AnalysisEnv + Clone + Send + Sync + 'static,
{
from_fn(move |env: &Env| {
env.file_system()
.read_to_string(&path)
.map_err(|e| match e {
AnalysisError::IoError { message, path: _ } => {
AnalysisError::io_with_path(message, &path)
}
other => other,
})
})
}
pub fn read_file_bytes_effect<Env>(
path: PathBuf,
) -> impl Effect<Output = Vec<u8>, Error = AnalysisError, Env = Env>
where
Env: AnalysisEnv + Clone + Send + Sync + 'static,
{
from_fn(move |env: &Env| {
env.file_system().read_bytes(&path).map_err(|e| match e {
AnalysisError::IoError { message, path: _ } => {
AnalysisError::io_with_path(message, &path)
}
other => other,
})
})
}
pub fn file_exists_effect<Env>(
path: PathBuf,
) -> impl Effect<Output = bool, Error = AnalysisError, Env = Env>
where
Env: AnalysisEnv + Clone + Send + Sync + 'static,
{
stillwater::asks(move |env: &Env| env.file_system().exists(&path))
}
pub fn is_file_effect<Env>(
path: PathBuf,
) -> impl Effect<Output = bool, Error = AnalysisError, Env = Env>
where
Env: AnalysisEnv + Clone + Send + Sync + 'static,
{
stillwater::asks(move |env: &Env| env.file_system().is_file(&path))
}
pub fn is_dir_effect<Env>(
path: PathBuf,
) -> impl Effect<Output = bool, Error = AnalysisError, Env = Env>
where
Env: AnalysisEnv + Clone + Send + Sync + 'static,
{
stillwater::asks(move |env: &Env| env.file_system().is_dir(&path))
}
pub fn write_file_effect<Env>(
path: PathBuf,
content: String,
) -> impl Effect<Output = (), Error = AnalysisError, Env = Env>
where
Env: AnalysisEnv + Clone + Send + Sync + 'static,
{
from_fn(move |env: &Env| {
env.file_system()
.write(&path, &content)
.map_err(|e| match e {
AnalysisError::IoError { message, path: _ } => {
AnalysisError::io_with_path(message, &path)
}
other => other,
})
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::DebtmapConfig;
use crate::env::RealEnv;
use tempfile::TempDir;
#[tokio::test]
async fn test_read_file_effect_success() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.txt");
std::fs::write(&file_path, "Hello, World!").unwrap();
let env = RealEnv::new(DebtmapConfig::default());
let effect = read_file_effect::<RealEnv>(file_path);
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), "Hello, World!");
}
#[tokio::test]
async fn test_read_file_effect_not_found() {
let env = RealEnv::new(DebtmapConfig::default());
let effect = read_file_effect::<RealEnv>("/nonexistent/file.txt".into());
let result = effect.run(&env).await;
assert!(result.is_err());
let err = result.unwrap_err();
assert_eq!(err.category(), "I/O");
}
#[tokio::test]
async fn test_read_file_bytes_effect() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("test.bin");
let bytes = vec![0u8, 1, 2, 3, 4, 255];
std::fs::write(&file_path, &bytes).unwrap();
let env = RealEnv::new(DebtmapConfig::default());
let effect = read_file_bytes_effect::<RealEnv>(file_path);
let result = effect.run(&env).await;
assert!(result.is_ok());
assert_eq!(result.unwrap(), bytes);
}
#[tokio::test]
async fn test_file_exists_effect_true() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("exists.txt");
std::fs::write(&file_path, "content").unwrap();
let env = RealEnv::new(DebtmapConfig::default());
let effect = file_exists_effect::<RealEnv>(file_path);
let result = effect.run(&env).await;
assert!(result.is_ok());
assert!(result.unwrap());
}
#[tokio::test]
async fn test_file_exists_effect_false() {
let env = RealEnv::new(DebtmapConfig::default());
let effect = file_exists_effect::<RealEnv>("/definitely/not/here.txt".into());
let result = effect.run(&env).await;
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[tokio::test]
async fn test_is_file_effect() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("file.txt");
std::fs::write(&file_path, "content").unwrap();
let env = RealEnv::new(DebtmapConfig::default());
let effect = is_file_effect::<RealEnv>(file_path);
assert!(effect.run(&env).await.unwrap());
let effect = is_file_effect::<RealEnv>(temp_dir.path().to_path_buf());
assert!(!effect.run(&env).await.unwrap());
}
#[tokio::test]
async fn test_is_dir_effect() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("file.txt");
std::fs::write(&file_path, "content").unwrap();
let env = RealEnv::new(DebtmapConfig::default());
let effect = is_dir_effect::<RealEnv>(temp_dir.path().to_path_buf());
assert!(effect.run(&env).await.unwrap());
let effect = is_dir_effect::<RealEnv>(file_path);
assert!(!effect.run(&env).await.unwrap());
}
#[tokio::test]
async fn test_write_file_effect() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("output.txt");
let env = RealEnv::new(DebtmapConfig::default());
let effect = write_file_effect::<RealEnv>(file_path.clone(), "test content".into());
let result = effect.run(&env).await;
assert!(result.is_ok());
let content = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(content, "test content");
}
#[tokio::test]
async fn test_write_file_effect_overwrites() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("overwrite.txt");
std::fs::write(&file_path, "original").unwrap();
let env = RealEnv::new(DebtmapConfig::default());
let effect = write_file_effect::<RealEnv>(file_path.clone(), "new content".into());
effect.run(&env).await.unwrap();
let content = std::fs::read_to_string(&file_path).unwrap();
assert_eq!(content, "new content");
}
}