Skip to main content

vtcode_commons/
reference.rs

1use std::path::{Path, PathBuf};
2use std::sync::{Arc, Mutex};
3
4use anyhow::{Error, Result};
5
6use crate::{ErrorReporter, TelemetrySink, WorkspacePaths};
7
8/// Reference implementation of [`WorkspacePaths`] backed by static [`PathBuf`]s.
9///
10/// This is useful for adopters who want to drive the extracted crates from an
11/// existing application without wiring additional indirection layers. The
12/// implementation is intentionally straightforward: callers provide the root
13/// workspace directory and configuration path up front and can optionally
14/// supply cache or telemetry directories.
15#[derive(Debug, Clone)]
16pub struct StaticWorkspacePaths {
17    root: PathBuf,
18    config: PathBuf,
19    cache: Option<PathBuf>,
20    telemetry: Option<PathBuf>,
21}
22
23impl StaticWorkspacePaths {
24    /// Creates a new [`StaticWorkspacePaths`] with the required workspace and
25    /// configuration directories.
26    pub fn new(root: impl Into<PathBuf>, config: impl Into<PathBuf>) -> Self {
27        Self {
28            root: root.into(),
29            config: config.into(),
30            cache: None,
31            telemetry: None,
32        }
33    }
34
35    /// Configures an optional cache directory used by the consumer.
36    pub fn with_cache_dir(mut self, cache: impl Into<PathBuf>) -> Self {
37        self.cache = Some(cache.into());
38        self
39    }
40
41    /// Configures an optional telemetry directory used by the consumer.
42    pub fn with_telemetry_dir(mut self, telemetry: impl Into<PathBuf>) -> Self {
43        self.telemetry = Some(telemetry.into());
44        self
45    }
46}
47
48impl WorkspacePaths for StaticWorkspacePaths {
49    fn workspace_root(&self) -> &Path {
50        &self.root
51    }
52
53    fn config_dir(&self) -> PathBuf {
54        self.config.clone()
55    }
56
57    fn cache_dir(&self) -> Option<PathBuf> {
58        self.cache.clone()
59    }
60
61    fn telemetry_dir(&self) -> Option<PathBuf> {
62        self.telemetry.clone()
63    }
64}
65
66/// In-memory telemetry sink that records cloned events for later inspection.
67///
68/// This helper is primarily intended for tests, examples, or prototypes that
69/// want to assert on the events emitted by a component without integrating a
70/// full telemetry backend. The recorded events can be retrieved via
71/// [`MemoryTelemetry::take`].
72#[derive(Debug, Default, Clone)]
73pub struct MemoryTelemetry<Event> {
74    events: Arc<Mutex<Vec<Event>>>,
75}
76
77impl<Event> MemoryTelemetry<Event> {
78    /// Creates a new memory-backed telemetry sink.
79    pub fn new() -> Self {
80        Self { events: Arc::new(Mutex::new(Vec::new())) }
81    }
82
83    /// Returns the recorded events, draining the internal buffer.
84    pub fn take(&self) -> Vec<Event> {
85        let mut events = match self.events.lock() {
86            Ok(guard) => guard,
87            Err(poisoned) => poisoned.into_inner(),
88        };
89        std::mem::take(&mut *events)
90    }
91}
92
93impl<Event> TelemetrySink<Event> for MemoryTelemetry<Event>
94where
95    Event: Clone + Send + Sync,
96{
97    fn record(&self, event: &Event) -> Result<()> {
98        let mut events = match self.events.lock() {
99            Ok(guard) => guard,
100            Err(poisoned) => poisoned.into_inner(),
101        };
102        events.push(event.clone());
103        Ok(())
104    }
105}
106
107/// Simple [`ErrorReporter`] that stores error messages in memory.
108///
109/// This helper is designed for tests and examples that need to assert on the
110/// errors emitted by a component without wiring an external monitoring system.
111/// Callers can retrieve captured messages via [`MemoryErrorReporter::take`].
112#[derive(Debug, Default, Clone)]
113pub struct MemoryErrorReporter {
114    messages: Arc<Mutex<Vec<String>>>,
115}
116
117impl MemoryErrorReporter {
118    /// Creates a new memory-backed error reporter.
119    pub fn new() -> Self {
120        Self { messages: Arc::new(Mutex::new(Vec::new())) }
121    }
122
123    /// Returns the captured error messages, draining the buffer.
124    pub fn take(&self) -> Vec<String> {
125        let mut messages = match self.messages.lock() {
126            Ok(guard) => guard,
127            Err(poisoned) => poisoned.into_inner(),
128        };
129        std::mem::take(&mut *messages)
130    }
131}
132
133impl ErrorReporter for MemoryErrorReporter {
134    fn capture(&self, error: &Error) -> Result<()> {
135        let mut messages = match self.messages.lock() {
136            Ok(guard) => guard,
137            Err(poisoned) => poisoned.into_inner(),
138        };
139        messages.push(format!("{error:?}"));
140        Ok(())
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147    use std::path::PathBuf;
148
149    #[test]
150    fn static_paths_exposes_optional_directories() {
151        let paths = StaticWorkspacePaths::new("/tmp/work", "/tmp/work/config")
152            .with_cache_dir("/tmp/work/cache")
153            .with_telemetry_dir("/tmp/work/telemetry");
154
155        assert_eq!(paths.workspace_root(), Path::new("/tmp/work"));
156        assert_eq!(paths.config_dir(), PathBuf::from("/tmp/work/config"));
157        assert_eq!(paths.cache_dir(), Some(PathBuf::from("/tmp/work/cache")));
158        assert_eq!(paths.telemetry_dir(), Some(PathBuf::from("/tmp/work/telemetry")));
159    }
160
161    #[test]
162    fn memory_telemetry_records_events() {
163        let telemetry = MemoryTelemetry::new();
164        telemetry.record(&"event-1").unwrap();
165        telemetry.record(&"event-2").unwrap();
166
167        assert_eq!(telemetry.take(), vec!["event-1", "event-2"]);
168        assert!(telemetry.take().is_empty());
169    }
170
171    #[test]
172    fn memory_error_reporter_captures_messages() {
173        let reporter = MemoryErrorReporter::new();
174        reporter.capture(&Error::msg("error-1")).unwrap();
175        reporter.capture(&Error::msg("error-2")).unwrap();
176
177        let messages = reporter.take();
178        assert_eq!(messages.len(), 2);
179        assert!(messages[0].contains("error-1"));
180        assert!(messages[1].contains("error-2"));
181        assert!(reporter.take().is_empty());
182    }
183}