vtcode_commons/
reference.rs1use std::path::{Path, PathBuf};
2use std::sync::{Arc, Mutex};
3
4use anyhow::{Error, Result};
5
6use crate::{ErrorReporter, TelemetrySink, WorkspacePaths};
7
8#[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 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 pub fn with_cache_dir(mut self, cache: impl Into<PathBuf>) -> Self {
37 self.cache = Some(cache.into());
38 self
39 }
40
41 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#[derive(Debug, Default, Clone)]
73pub struct MemoryTelemetry<Event> {
74 events: Arc<Mutex<Vec<Event>>>,
75}
76
77impl<Event> MemoryTelemetry<Event> {
78 pub fn new() -> Self {
80 Self {
81 events: Arc::new(Mutex::new(Vec::new())),
82 }
83 }
84
85 pub fn take(&self) -> Vec<Event> {
87 let mut events = self.events.lock().expect("telemetry poisoned");
88 std::mem::take(&mut *events)
89 }
90}
91
92impl<Event> TelemetrySink<Event> for MemoryTelemetry<Event>
93where
94 Event: Clone + Send + Sync,
95{
96 fn record(&self, event: &Event) -> Result<()> {
97 let mut events = self.events.lock().expect("telemetry poisoned");
98 events.push(event.clone());
99 Ok(())
100 }
101}
102
103#[derive(Debug, Default, Clone)]
109pub struct MemoryErrorReporter {
110 messages: Arc<Mutex<Vec<String>>>,
111}
112
113impl MemoryErrorReporter {
114 pub fn new() -> Self {
116 Self {
117 messages: Arc::new(Mutex::new(Vec::new())),
118 }
119 }
120
121 pub fn take(&self) -> Vec<String> {
123 let mut messages = self.messages.lock().expect("reporter poisoned");
124 std::mem::take(&mut *messages)
125 }
126}
127
128impl ErrorReporter for MemoryErrorReporter {
129 fn capture(&self, error: &Error) -> Result<()> {
130 let mut messages = self.messages.lock().expect("reporter poisoned");
131 messages.push(format!("{error:?}"));
132 Ok(())
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use std::path::PathBuf;
140
141 #[test]
142 fn static_paths_exposes_optional_directories() {
143 let paths = StaticWorkspacePaths::new("/tmp/work", "/tmp/work/config")
144 .with_cache_dir("/tmp/work/cache")
145 .with_telemetry_dir("/tmp/work/telemetry");
146
147 assert_eq!(paths.workspace_root(), Path::new("/tmp/work"));
148 assert_eq!(paths.config_dir(), PathBuf::from("/tmp/work/config"));
149 assert_eq!(paths.cache_dir(), Some(PathBuf::from("/tmp/work/cache")));
150 assert_eq!(
151 paths.telemetry_dir(),
152 Some(PathBuf::from("/tmp/work/telemetry"))
153 );
154 }
155
156 #[test]
157 fn memory_telemetry_records_events() {
158 let telemetry = MemoryTelemetry::new();
159 telemetry.record(&"event-1").unwrap();
160 telemetry.record(&"event-2").unwrap();
161
162 assert_eq!(telemetry.take(), vec!["event-1", "event-2"]);
163 assert!(telemetry.take().is_empty());
164 }
165
166 #[test]
167 fn memory_error_reporter_captures_messages() {
168 let reporter = MemoryErrorReporter::new();
169 reporter.capture(&Error::msg("error-1")).unwrap();
170 reporter.capture(&Error::msg("error-2")).unwrap();
171
172 let messages = reporter.take();
173 assert_eq!(messages.len(), 2);
174 assert!(messages[0].contains("error-1"));
175 assert!(messages[1].contains("error-2"));
176 assert!(reporter.take().is_empty());
177 }
178}