use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use anyhow::{Error, Result};
use crate::{ErrorReporter, TelemetrySink, WorkspacePaths};
#[derive(Debug, Clone)]
pub struct StaticWorkspacePaths {
root: PathBuf,
config: PathBuf,
cache: Option<PathBuf>,
telemetry: Option<PathBuf>,
}
impl StaticWorkspacePaths {
pub fn new(root: impl Into<PathBuf>, config: impl Into<PathBuf>) -> Self {
Self {
root: root.into(),
config: config.into(),
cache: None,
telemetry: None,
}
}
pub fn with_cache_dir(mut self, cache: impl Into<PathBuf>) -> Self {
self.cache = Some(cache.into());
self
}
pub fn with_telemetry_dir(mut self, telemetry: impl Into<PathBuf>) -> Self {
self.telemetry = Some(telemetry.into());
self
}
}
impl WorkspacePaths for StaticWorkspacePaths {
fn workspace_root(&self) -> &Path {
&self.root
}
fn config_dir(&self) -> PathBuf {
self.config.clone()
}
fn cache_dir(&self) -> Option<PathBuf> {
self.cache.clone()
}
fn telemetry_dir(&self) -> Option<PathBuf> {
self.telemetry.clone()
}
}
#[derive(Debug, Default, Clone)]
pub struct MemoryTelemetry<Event> {
events: Arc<Mutex<Vec<Event>>>,
}
impl<Event> MemoryTelemetry<Event> {
pub fn new() -> Self {
Self {
events: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn take(&self) -> Vec<Event> {
let mut events = match self.events.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
std::mem::take(&mut *events)
}
}
impl<Event> TelemetrySink<Event> for MemoryTelemetry<Event>
where
Event: Clone + Send + Sync,
{
fn record(&self, event: &Event) -> Result<()> {
let mut events = match self.events.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
events.push(event.clone());
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct MemoryErrorReporter {
messages: Arc<Mutex<Vec<String>>>,
}
impl MemoryErrorReporter {
pub fn new() -> Self {
Self {
messages: Arc::new(Mutex::new(Vec::new())),
}
}
pub fn take(&self) -> Vec<String> {
let mut messages = match self.messages.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
std::mem::take(&mut *messages)
}
}
impl ErrorReporter for MemoryErrorReporter {
fn capture(&self, error: &Error) -> Result<()> {
let mut messages = match self.messages.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
messages.push(format!("{error:?}"));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
#[test]
fn static_paths_exposes_optional_directories() {
let paths = StaticWorkspacePaths::new("/tmp/work", "/tmp/work/config")
.with_cache_dir("/tmp/work/cache")
.with_telemetry_dir("/tmp/work/telemetry");
assert_eq!(paths.workspace_root(), Path::new("/tmp/work"));
assert_eq!(paths.config_dir(), PathBuf::from("/tmp/work/config"));
assert_eq!(paths.cache_dir(), Some(PathBuf::from("/tmp/work/cache")));
assert_eq!(
paths.telemetry_dir(),
Some(PathBuf::from("/tmp/work/telemetry"))
);
}
#[test]
fn memory_telemetry_records_events() {
let telemetry = MemoryTelemetry::new();
telemetry.record(&"event-1").unwrap();
telemetry.record(&"event-2").unwrap();
assert_eq!(telemetry.take(), vec!["event-1", "event-2"]);
assert!(telemetry.take().is_empty());
}
#[test]
fn memory_error_reporter_captures_messages() {
let reporter = MemoryErrorReporter::new();
reporter.capture(&Error::msg("error-1")).unwrap();
reporter.capture(&Error::msg("error-2")).unwrap();
let messages = reporter.take();
assert_eq!(messages.len(), 2);
assert!(messages[0].contains("error-1"));
assert!(messages[1].contains("error-2"));
assert!(reporter.take().is_empty());
}
}