use crate::errors::AnalysisError;
use std::io::{self, Write};
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
pub trait OutputDestination: Send + Sync {
fn write_str(&self, content: &str) -> Result<(), AnalysisError>;
fn flush(&self) -> Result<(), AnalysisError>;
fn description(&self) -> String;
}
#[derive(Debug, Clone)]
pub struct FileDestination {
path: PathBuf,
}
impl FileDestination {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
pub fn path(&self) -> &PathBuf {
&self.path
}
}
impl OutputDestination for FileDestination {
fn write_str(&self, content: &str) -> Result<(), AnalysisError> {
std::fs::write(&self.path, content).map_err(|e| {
AnalysisError::io_with_path(format!("Failed to write to file: {}", e), &self.path)
})
}
fn flush(&self) -> Result<(), AnalysisError> {
Ok(())
}
fn description(&self) -> String {
format!("file:{}", self.path.display())
}
}
#[derive(Debug, Clone)]
pub struct MemoryDestination {
buffer: Arc<RwLock<String>>,
}
impl Default for MemoryDestination {
fn default() -> Self {
Self::new()
}
}
impl MemoryDestination {
pub fn new() -> Self {
Self {
buffer: Arc::new(RwLock::new(String::new())),
}
}
pub fn get_content(&self) -> String {
self.buffer.read().expect("RwLock poisoned").clone()
}
pub fn clear(&self) {
self.buffer.write().expect("RwLock poisoned").clear();
}
pub fn len(&self) -> usize {
self.buffer.read().expect("RwLock poisoned").len()
}
pub fn is_empty(&self) -> bool {
self.buffer.read().expect("RwLock poisoned").is_empty()
}
}
impl OutputDestination for MemoryDestination {
fn write_str(&self, content: &str) -> Result<(), AnalysisError> {
self.buffer
.write()
.expect("RwLock poisoned")
.push_str(content);
Ok(())
}
fn flush(&self) -> Result<(), AnalysisError> {
Ok(())
}
fn description(&self) -> String {
"memory".to_string()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct StdoutDestination;
impl StdoutDestination {
pub fn new() -> Self {
Self
}
}
impl OutputDestination for StdoutDestination {
fn write_str(&self, content: &str) -> Result<(), AnalysisError> {
let stdout = io::stdout();
let mut handle = stdout.lock();
handle
.write_all(content.as_bytes())
.map_err(|e| AnalysisError::io(format!("Failed to write to stdout: {}", e)))?;
Ok(())
}
fn flush(&self) -> Result<(), AnalysisError> {
let stdout = io::stdout();
let mut handle = stdout.lock();
handle
.flush()
.map_err(|e| AnalysisError::io(format!("Failed to flush stdout: {}", e)))
}
fn description(&self) -> String {
"stdout".to_string()
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct StderrDestination;
impl StderrDestination {
pub fn new() -> Self {
Self
}
}
impl OutputDestination for StderrDestination {
fn write_str(&self, content: &str) -> Result<(), AnalysisError> {
let stderr = io::stderr();
let mut handle = stderr.lock();
handle
.write_all(content.as_bytes())
.map_err(|e| AnalysisError::io(format!("Failed to write to stderr: {}", e)))?;
Ok(())
}
fn flush(&self) -> Result<(), AnalysisError> {
let stderr = io::stderr();
let mut handle = stderr.lock();
handle
.flush()
.map_err(|e| AnalysisError::io(format!("Failed to flush stderr: {}", e)))
}
fn description(&self) -> String {
"stderr".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_file_destination_write() {
let temp_dir = TempDir::new().unwrap();
let path = temp_dir.path().join("test.txt");
let dest = FileDestination::new(path.clone());
dest.write_str("Hello, World!").unwrap();
let content = std::fs::read_to_string(&path).unwrap();
assert_eq!(content, "Hello, World!");
}
#[test]
fn test_file_destination_description() {
let dest = FileDestination::new(PathBuf::from("/tmp/test.md"));
assert!(dest.description().contains("test.md"));
}
#[test]
fn test_memory_destination_write() {
let dest = MemoryDestination::new();
dest.write_str("Hello").unwrap();
dest.write_str(", World!").unwrap();
assert_eq!(dest.get_content(), "Hello, World!");
}
#[test]
fn test_memory_destination_clear() {
let dest = MemoryDestination::new();
dest.write_str("Some content").unwrap();
assert!(!dest.is_empty());
dest.clear();
assert!(dest.is_empty());
assert_eq!(dest.len(), 0);
}
#[test]
fn test_memory_destination_thread_safe() {
use std::thread;
let dest = MemoryDestination::new();
let dest_clone = dest.clone();
let handle = thread::spawn(move || {
dest_clone.write_str("Thread content").unwrap();
});
handle.join().unwrap();
assert!(dest.get_content().contains("Thread content"));
}
#[test]
fn test_stdout_destination_description() {
let dest = StdoutDestination::new();
assert_eq!(dest.description(), "stdout");
}
#[test]
fn test_stderr_destination_description() {
let dest = StderrDestination::new();
assert_eq!(dest.description(), "stderr");
}
}