1use std::collections::{HashMap, VecDeque};
6
7use chrono::{DateTime, Utc};
8use lsp_types::{Diagnostic as LspDiagnostic, Uri};
9use serde::{Deserialize, Serialize};
10
11const MAX_LOG_ENTRIES: usize = 100;
13
14const MAX_SERVER_MESSAGES: usize = 50;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct DiagnosticInfo {
20 pub uri: Uri,
22 pub version: Option<i32>,
24 pub diagnostics: Vec<LspDiagnostic>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct LogEntry {
31 pub level: LogLevel,
33 pub message: String,
35 pub timestamp: DateTime<Utc>,
37}
38
39#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
41#[serde(rename_all = "lowercase")]
42pub enum LogLevel {
43 Error,
45 Warning,
47 Info,
49 Debug,
51}
52
53impl From<lsp_types::MessageType> for LogLevel {
54 fn from(msg_type: lsp_types::MessageType) -> Self {
55 match msg_type {
56 lsp_types::MessageType::ERROR => Self::Error,
57 lsp_types::MessageType::WARNING => Self::Warning,
58 lsp_types::MessageType::INFO => Self::Info,
59 _ => Self::Debug,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct ServerMessage {
68 pub message_type: MessageType,
70 pub message: String,
72 pub timestamp: DateTime<Utc>,
74}
75
76#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
78#[serde(rename_all = "lowercase")]
79pub enum MessageType {
80 Error,
82 Warning,
84 Info,
86 Log,
88}
89
90impl From<lsp_types::MessageType> for MessageType {
91 fn from(msg_type: lsp_types::MessageType) -> Self {
92 match msg_type {
93 lsp_types::MessageType::ERROR => Self::Error,
94 lsp_types::MessageType::WARNING => Self::Warning,
95 lsp_types::MessageType::INFO => Self::Info,
96 _ => Self::Log,
98 }
99 }
100}
101
102#[derive(Debug)]
104pub struct NotificationCache {
105 diagnostics: HashMap<String, DiagnosticInfo>,
107 logs: VecDeque<LogEntry>,
109 messages: VecDeque<ServerMessage>,
111}
112
113impl Default for NotificationCache {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl NotificationCache {
120 #[must_use]
122 pub fn new() -> Self {
123 Self {
124 diagnostics: HashMap::with_capacity(32),
125 logs: VecDeque::with_capacity(MAX_LOG_ENTRIES),
126 messages: VecDeque::with_capacity(MAX_SERVER_MESSAGES),
127 }
128 }
129
130 pub fn store_diagnostics(
134 &mut self,
135 uri: &Uri,
136 version: Option<i32>,
137 diagnostics: Vec<LspDiagnostic>,
138 ) {
139 let info = DiagnosticInfo {
140 uri: uri.clone(),
141 version,
142 diagnostics,
143 };
144 self.diagnostics.insert(uri.to_string(), info);
145 }
146
147 pub fn store_log(&mut self, level: LogLevel, message: String) {
151 let entry = LogEntry {
152 level,
153 message,
154 timestamp: Utc::now(),
155 };
156
157 if self.logs.len() >= MAX_LOG_ENTRIES {
158 self.logs.pop_front();
159 }
160 self.logs.push_back(entry);
161 }
162
163 pub fn store_message(&mut self, message_type: MessageType, message: String) {
167 let msg = ServerMessage {
168 message_type,
169 message,
170 timestamp: Utc::now(),
171 };
172
173 if self.messages.len() >= MAX_SERVER_MESSAGES {
174 self.messages.pop_front();
175 }
176 self.messages.push_back(msg);
177 }
178
179 #[inline]
181 #[must_use]
182 pub fn get_diagnostics(&self, uri: &str) -> Option<&DiagnosticInfo> {
183 self.diagnostics.get(uri)
184 }
185
186 #[inline]
188 #[must_use]
189 pub const fn get_logs(&self) -> &VecDeque<LogEntry> {
190 &self.logs
191 }
192
193 #[inline]
195 #[must_use]
196 pub const fn get_messages(&self) -> &VecDeque<ServerMessage> {
197 &self.messages
198 }
199
200 pub fn clear_diagnostics(&mut self, uri: &str) -> Option<DiagnosticInfo> {
204 self.diagnostics.remove(uri)
205 }
206
207 pub fn clear_all_diagnostics(&mut self) {
209 self.diagnostics.clear();
210 }
211
212 pub fn clear_logs(&mut self) {
214 self.logs.clear();
215 }
216
217 pub fn clear_messages(&mut self) {
219 self.messages.clear();
220 }
221
222 #[inline]
224 #[must_use]
225 pub fn diagnostics_count(&self) -> usize {
226 self.diagnostics.len()
227 }
228
229 #[inline]
231 #[must_use]
232 pub fn logs_count(&self) -> usize {
233 self.logs.len()
234 }
235
236 #[inline]
238 #[must_use]
239 pub fn messages_count(&self) -> usize {
240 self.messages.len()
241 }
242}
243
244#[cfg(test)]
245#[allow(clippy::unwrap_used)]
246mod tests {
247 use lsp_types::{Position, Range};
248
249 use super::*;
250
251 #[test]
252 fn test_notification_cache_new() {
253 let cache = NotificationCache::new();
254 assert_eq!(cache.diagnostics_count(), 0);
255 assert_eq!(cache.logs_count(), 0);
256 assert_eq!(cache.messages_count(), 0);
257 }
258
259 #[test]
260 fn test_store_and_get_diagnostics() {
261 let mut cache = NotificationCache::new();
262 let uri: Uri = "file:///test.rs".parse().unwrap();
263
264 let diagnostic = LspDiagnostic {
265 range: Range {
266 start: Position {
267 line: 0,
268 character: 0,
269 },
270 end: Position {
271 line: 0,
272 character: 5,
273 },
274 },
275 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
276 message: "test error".to_string(),
277 code: None,
278 source: None,
279 code_description: None,
280 related_information: None,
281 tags: None,
282 data: None,
283 };
284
285 cache.store_diagnostics(&uri, Some(1), vec![diagnostic]);
286
287 let stored = cache.get_diagnostics(uri.as_str()).unwrap();
288 assert_eq!(stored.uri, uri);
289 assert_eq!(stored.version, Some(1));
290 assert_eq!(stored.diagnostics.len(), 1);
291 assert_eq!(stored.diagnostics[0].message, "test error");
292 }
293
294 #[test]
295 fn test_store_diagnostics_replaces_existing() {
296 let mut cache = NotificationCache::new();
297 let uri: Uri = "file:///test.rs".parse().unwrap();
298
299 cache.store_diagnostics(&uri, Some(1), vec![]);
300 assert_eq!(cache.diagnostics_count(), 1);
301
302 cache.store_diagnostics(&uri, Some(2), vec![]);
303 assert_eq!(cache.diagnostics_count(), 1);
304
305 let stored = cache.get_diagnostics(uri.as_str()).unwrap();
306 assert_eq!(stored.version, Some(2));
307 }
308
309 #[test]
310 fn test_clear_diagnostics() {
311 let mut cache = NotificationCache::new();
312 let uri: Uri = "file:///test.rs".parse().unwrap();
313
314 cache.store_diagnostics(&uri, Some(1), vec![]);
315 assert_eq!(cache.diagnostics_count(), 1);
316
317 let cleared = cache.clear_diagnostics(uri.as_str());
318 assert!(cleared.is_some());
319 assert_eq!(cache.diagnostics_count(), 0);
320 }
321
322 #[test]
323 fn test_clear_all_diagnostics() {
324 let mut cache = NotificationCache::new();
325 let uri1: Uri = "file:///test1.rs".parse().unwrap();
326 let uri2: Uri = "file:///test2.rs".parse().unwrap();
327
328 cache.store_diagnostics(&uri1, Some(1), vec![]);
329 cache.store_diagnostics(&uri2, Some(1), vec![]);
330 assert_eq!(cache.diagnostics_count(), 2);
331
332 cache.clear_all_diagnostics();
333 assert_eq!(cache.diagnostics_count(), 0);
334 }
335
336 #[test]
337 fn test_store_and_get_logs() {
338 let mut cache = NotificationCache::new();
339
340 cache.store_log(LogLevel::Error, "error message".to_string());
341 cache.store_log(LogLevel::Info, "info message".to_string());
342
343 let logs = cache.get_logs();
344 assert_eq!(logs.len(), 2);
345 assert_eq!(logs[0].level, LogLevel::Error);
346 assert_eq!(logs[0].message, "error message");
347 assert_eq!(logs[1].level, LogLevel::Info);
348 assert_eq!(logs[1].message, "info message");
349 }
350
351 #[test]
352 fn test_logs_max_capacity() {
353 let mut cache = NotificationCache::new();
354
355 for i in 0..MAX_LOG_ENTRIES + 10 {
357 cache.store_log(LogLevel::Info, format!("message {i}"));
358 }
359
360 assert_eq!(cache.logs_count(), MAX_LOG_ENTRIES);
361
362 let logs = cache.get_logs();
364 assert_eq!(logs.front().unwrap().message, "message 10");
365 assert_eq!(
366 logs.back().unwrap().message,
367 format!("message {}", MAX_LOG_ENTRIES + 9)
368 );
369 }
370
371 #[test]
372 fn test_clear_logs() {
373 let mut cache = NotificationCache::new();
374 cache.store_log(LogLevel::Info, "test".to_string());
375 assert_eq!(cache.logs_count(), 1);
376
377 cache.clear_logs();
378 assert_eq!(cache.logs_count(), 0);
379 }
380
381 #[test]
382 fn test_store_and_get_messages() {
383 let mut cache = NotificationCache::new();
384
385 cache.store_message(MessageType::Error, "error msg".to_string());
386 cache.store_message(MessageType::Warning, "warning msg".to_string());
387
388 let messages = cache.get_messages();
389 assert_eq!(messages.len(), 2);
390 assert_eq!(messages[0].message_type, MessageType::Error);
391 assert_eq!(messages[0].message, "error msg");
392 assert_eq!(messages[1].message_type, MessageType::Warning);
393 assert_eq!(messages[1].message, "warning msg");
394 }
395
396 #[test]
397 fn test_messages_max_capacity() {
398 let mut cache = NotificationCache::new();
399
400 for i in 0..MAX_SERVER_MESSAGES + 10 {
402 cache.store_message(MessageType::Info, format!("message {i}"));
403 }
404
405 assert_eq!(cache.messages_count(), MAX_SERVER_MESSAGES);
406
407 let messages = cache.get_messages();
409 assert_eq!(messages.front().unwrap().message, "message 10");
410 assert_eq!(
411 messages.back().unwrap().message,
412 format!("message {}", MAX_SERVER_MESSAGES + 9)
413 );
414 }
415
416 #[test]
417 fn test_clear_messages() {
418 let mut cache = NotificationCache::new();
419 cache.store_message(MessageType::Info, "test".to_string());
420 assert_eq!(cache.messages_count(), 1);
421
422 cache.clear_messages();
423 assert_eq!(cache.messages_count(), 0);
424 }
425
426 #[test]
427 fn test_log_levels() {
428 let mut cache = NotificationCache::new();
429
430 cache.store_log(LogLevel::Error, "error".to_string());
431 cache.store_log(LogLevel::Warning, "warning".to_string());
432 cache.store_log(LogLevel::Info, "info".to_string());
433 cache.store_log(LogLevel::Debug, "debug".to_string());
434
435 let logs = cache.get_logs();
436 assert_eq!(logs[0].level, LogLevel::Error);
437 assert_eq!(logs[1].level, LogLevel::Warning);
438 assert_eq!(logs[2].level, LogLevel::Info);
439 assert_eq!(logs[3].level, LogLevel::Debug);
440 }
441
442 #[test]
443 fn test_message_types() {
444 let mut cache = NotificationCache::new();
445
446 cache.store_message(MessageType::Error, "error".to_string());
447 cache.store_message(MessageType::Warning, "warning".to_string());
448 cache.store_message(MessageType::Info, "info".to_string());
449 cache.store_message(MessageType::Log, "log".to_string());
450
451 let messages = cache.get_messages();
452 assert_eq!(messages[0].message_type, MessageType::Error);
453 assert_eq!(messages[1].message_type, MessageType::Warning);
454 assert_eq!(messages[2].message_type, MessageType::Info);
455 assert_eq!(messages[3].message_type, MessageType::Log);
456 }
457
458 #[test]
459 fn test_timestamp_ordering() {
460 let mut cache = NotificationCache::new();
461
462 cache.store_log(LogLevel::Info, "first".to_string());
463 std::thread::sleep(std::time::Duration::from_millis(10));
464 cache.store_log(LogLevel::Info, "second".to_string());
465
466 let logs = cache.get_logs();
467 assert!(logs[0].timestamp < logs[1].timestamp);
468 }
469
470 #[test]
471 fn test_store_diagnostics_empty_list() {
472 let mut cache = NotificationCache::new();
473 let uri: Uri = "file:///test.rs".parse().unwrap();
474
475 let diagnostic = LspDiagnostic {
476 range: Range {
477 start: Position {
478 line: 0,
479 character: 0,
480 },
481 end: Position {
482 line: 0,
483 character: 5,
484 },
485 },
486 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
487 message: "test error".to_string(),
488 code: None,
489 source: None,
490 code_description: None,
491 related_information: None,
492 tags: None,
493 data: None,
494 };
495
496 cache.store_diagnostics(&uri, Some(1), vec![diagnostic]);
497 assert_eq!(
498 cache
499 .get_diagnostics(uri.as_str())
500 .unwrap()
501 .diagnostics
502 .len(),
503 1
504 );
505
506 cache.store_diagnostics(&uri, Some(2), vec![]);
507 let stored = cache.get_diagnostics(uri.as_str()).unwrap();
508 assert_eq!(stored.diagnostics.len(), 0);
509 assert_eq!(stored.version, Some(2));
510 }
511
512 #[test]
513 fn test_store_many_diagnostics_single_file() {
514 let mut cache = NotificationCache::new();
515 let uri: Uri = "file:///test.rs".parse().unwrap();
516
517 let diagnostics: Vec<LspDiagnostic> = (0..100)
518 .map(|i| LspDiagnostic {
519 range: Range {
520 start: Position {
521 line: i,
522 character: 0,
523 },
524 end: Position {
525 line: i,
526 character: 10,
527 },
528 },
529 message: format!("Error {i}"),
530 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
531 code: None,
532 source: None,
533 code_description: None,
534 related_information: None,
535 tags: None,
536 data: None,
537 })
538 .collect();
539
540 cache.store_diagnostics(&uri, Some(1), diagnostics);
541
542 let stored = cache.get_diagnostics(uri.as_str()).unwrap();
543 assert_eq!(stored.diagnostics.len(), 100);
544 }
545
546 #[test]
547 fn test_logs_exact_capacity_boundary() {
548 let mut cache = NotificationCache::new();
549
550 for i in 0..MAX_LOG_ENTRIES {
551 cache.store_log(LogLevel::Info, format!("message {i}"));
552 }
553 assert_eq!(cache.logs_count(), MAX_LOG_ENTRIES);
554
555 cache.store_log(LogLevel::Info, "overflow".to_string());
556 assert_eq!(cache.logs_count(), MAX_LOG_ENTRIES);
557 assert_eq!(cache.get_logs().front().unwrap().message, "message 1");
558 }
559
560 #[test]
561 fn test_messages_exact_capacity_boundary() {
562 let mut cache = NotificationCache::new();
563
564 for i in 0..MAX_SERVER_MESSAGES {
565 cache.store_message(MessageType::Info, format!("message {i}"));
566 }
567 assert_eq!(cache.messages_count(), MAX_SERVER_MESSAGES);
568
569 cache.store_message(MessageType::Info, "overflow".to_string());
570 assert_eq!(cache.messages_count(), MAX_SERVER_MESSAGES);
571 assert_eq!(cache.get_messages().front().unwrap().message, "message 1");
572 }
573
574 #[test]
575 fn test_clear_diagnostics_nonexistent() {
576 let mut cache = NotificationCache::new();
577 let result = cache.clear_diagnostics("file:///nonexistent.rs");
578 assert!(result.is_none());
579 }
580
581 #[test]
582 fn test_store_diagnostics_no_version() {
583 let mut cache = NotificationCache::new();
584 let uri: Uri = "file:///test.rs".parse().unwrap();
585
586 cache.store_diagnostics(&uri, None, vec![]);
587 let stored = cache.get_diagnostics(uri.as_str()).unwrap();
588 assert_eq!(stored.version, None);
589 }
590}