1use serde::{Deserialize, Serialize};
9use std::fmt;
10
11use crate::metadata::SystemMetadata;
12use crate::metrics::{ServerMetricsSnapshot, SessionStatsSnapshot, SubprocessMetricsSnapshot};
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
28#[serde(transparent)]
29pub struct SessionId(String);
30
31impl SessionId {
32 pub fn new(id: impl Into<String>) -> Self {
48 Self(id.into())
49 }
50
51 pub fn as_str(&self) -> &str {
62 &self.0
63 }
64
65 pub fn into_inner(self) -> String {
77 self.0
78 }
79}
80
81impl fmt::Display for SessionId {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(f, "{}", self.0)
84 }
85}
86
87impl AsRef<str> for SessionId {
88 fn as_ref(&self) -> &str {
89 &self.0
90 }
91}
92
93impl From<String> for SessionId {
94 fn from(s: String) -> Self {
95 Self(s)
96 }
97}
98
99impl From<&str> for SessionId {
100 fn from(s: &str) -> Self {
101 Self(s.to_string())
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
119#[serde(transparent)]
120pub struct MessageId(u64);
121
122impl MessageId {
123 pub fn new(id: u64) -> Self {
138 Self(id)
139 }
140
141 pub fn as_u64(&self) -> u64 {
152 self.0
153 }
154
155 pub fn increment(&mut self) {
167 self.0 += 1;
168 }
169
170 pub fn next(&self) -> Self {
183 Self(self.0 + 1)
184 }
185}
186
187impl fmt::Display for MessageId {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(f, "{}", self.0)
190 }
191}
192
193impl From<u64> for MessageId {
194 fn from(n: u64) -> Self {
195 Self(n)
196 }
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
203pub struct SourceLocation {
204 pub offset: usize,
206 pub line: usize,
208 pub column: usize,
210}
211
212impl fmt::Display for SourceLocation {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 write!(f, "line {}, column {}", self.line, self.column)
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
220pub struct Request {
221 pub id: MessageId,
223 pub session_id: SessionId,
225 pub operation: Operation,
227}
228
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231pub struct Response {
232 pub request_id: MessageId,
234 pub session_id: SessionId,
236 pub result: OperationResult,
238}
239
240#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
242#[non_exhaustive]
243pub enum Operation {
244 CreateSession {
246 mode: ReplMode,
248 },
249
250 Clone {
252 source_session_id: SessionId,
254 },
255
256 Eval {
258 code: String,
260 mode: ReplMode,
262 },
263
264 LoadFile {
266 path: String,
268 mode: ReplMode,
270 },
271
272 Interrupt,
274
275 Close,
277
278 LsSessions,
280
281 Describe {
283 symbol: String,
285 },
286
287 History {
289 limit: Option<usize>,
291 },
292
293 ClearOutput,
295
296 GetServerStats,
298
299 GetSessionStats,
301
302 GetSubprocessStats,
304
305 GetSystemInfo,
307}
308
309#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311#[non_exhaustive]
312pub enum OperationResult {
313 Success {
315 status: Status,
317 value: Option<String>,
319 stdout: Option<String>,
321 stderr: Option<String>,
323 },
324
325 Error {
327 error: ErrorInfo,
329 stdout: Option<String>,
331 stderr: Option<String>,
333 },
334
335 Sessions {
337 sessions: Vec<SessionInfo>,
339 },
340
341 HistoryEntries {
343 entries: Vec<HistoryEntry>,
345 },
346
347 ServerStats {
349 snapshot: ServerMetricsSnapshot,
351 },
352
353 SessionStats {
355 snapshot: SessionStatsSnapshot,
357 },
358
359 SubprocessStats {
361 snapshot: SubprocessMetricsSnapshot,
363 },
364
365 SystemInfo {
367 metadata: SystemMetadata,
369 },
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
374#[non_exhaustive]
375pub enum ReplMode {
376 Lisp,
378 Sexpr,
380}
381
382impl fmt::Display for ReplMode {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 match self {
385 ReplMode::Lisp => write!(f, "Lisp"),
386 ReplMode::Sexpr => write!(f, "Sexpr"),
387 }
388 }
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
393pub struct Status {
394 pub tier: u8,
396 pub cached: bool,
398 pub duration_ms: u64,
400}
401
402#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
404pub struct SessionInfo {
405 pub id: SessionId,
407 pub name: Option<String>,
409 pub mode: ReplMode,
411 pub eval_count: u64,
413 pub created_at: u64,
415 pub last_active_at: u64,
417 pub timeout_ms: u64,
419}
420
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
423pub struct ErrorInfo {
424 pub kind: ErrorKind,
426 pub message: String,
428 pub location: Option<SourceLocation>,
430 pub details: Option<String>,
432}
433
434impl fmt::Display for ErrorInfo {
435 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436 write!(f, "{}: {}", self.kind, self.message)?;
437 if let Some(loc) = &self.location {
438 write!(f, " at {}", loc)?;
439 }
440 Ok(())
441 }
442}
443
444#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
446#[non_exhaustive]
447pub enum ErrorKind {
448 SyntaxError,
450 TypeError,
452 RuntimeError,
454 CompilationError,
456 SessionNotFound,
458 SessionAlreadyExists,
460 IoError,
462 Interrupted,
464 InvalidRequest,
466 InternalError,
468}
469
470impl fmt::Display for ErrorKind {
471 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
472 match self {
473 ErrorKind::SyntaxError => write!(f, "Syntax Error"),
474 ErrorKind::TypeError => write!(f, "Type Error"),
475 ErrorKind::RuntimeError => write!(f, "Runtime Error"),
476 ErrorKind::CompilationError => write!(f, "Compilation Error"),
477 ErrorKind::SessionNotFound => write!(f, "Session Not Found"),
478 ErrorKind::SessionAlreadyExists => write!(f, "Session Already Exists"),
479 ErrorKind::IoError => write!(f, "I/O Error"),
480 ErrorKind::Interrupted => write!(f, "Interrupted"),
481 ErrorKind::InvalidRequest => write!(f, "Invalid Request"),
482 ErrorKind::InternalError => write!(f, "Internal Error"),
483 }
484 }
485}
486
487#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
489pub struct HistoryEntry {
490 pub number: usize,
492 pub code: String,
494 pub result: Option<String>,
496 pub timestamp: u64,
498}
499
500#[cfg(test)]
501mod tests {
502 use super::*;
503
504 #[test]
505 fn test_source_location_display() {
506 let loc = SourceLocation { offset: 42, line: 3, column: 15 };
507 assert_eq!(loc.to_string(), "line 3, column 15");
508 }
509
510 #[test]
511 fn test_repl_mode_display() {
512 assert_eq!(ReplMode::Lisp.to_string(), "Lisp");
513 assert_eq!(ReplMode::Sexpr.to_string(), "Sexpr");
514 }
515
516 #[test]
517 fn test_error_kind_display() {
518 assert_eq!(ErrorKind::SyntaxError.to_string(), "Syntax Error");
519 assert_eq!(ErrorKind::RuntimeError.to_string(), "Runtime Error");
520 }
521
522 #[test]
523 fn test_error_info_display() {
524 let error = ErrorInfo {
525 kind: ErrorKind::SyntaxError,
526 message: "Unexpected token".to_string(),
527 location: Some(SourceLocation { offset: 10, line: 2, column: 5 }),
528 details: None,
529 };
530 assert_eq!(error.to_string(), "Syntax Error: Unexpected token at line 2, column 5");
531 }
532
533 #[test]
534 fn test_session_id_new() {
535 let id1 = SessionId::new("test-session");
536 let id2 = SessionId::new(String::from("test-session"));
537 assert_eq!(id1, id2);
538 assert_eq!(id1.as_str(), "test-session");
539 }
540
541 #[test]
542 fn test_session_id_display() {
543 let id = SessionId::new("my-session");
544 assert_eq!(id.to_string(), "my-session");
545 }
546
547 #[test]
548 fn test_session_id_into_inner() {
549 let id = SessionId::new("test");
550 let inner: String = id.into_inner();
551 assert_eq!(inner, "test");
552 }
553
554 #[test]
555 fn test_message_id_new() {
556 let id = MessageId::new(42);
557 assert_eq!(id.as_u64(), 42);
558 }
559
560 #[test]
561 fn test_message_id_increment() {
562 let mut id = MessageId::new(1);
563 id.increment();
564 assert_eq!(id.as_u64(), 2);
565 id.increment();
566 assert_eq!(id.as_u64(), 3);
567 }
568
569 #[test]
570 fn test_message_id_next() {
571 let id = MessageId::new(5);
572 let next = id.next();
573 assert_eq!(id.as_u64(), 5); assert_eq!(next.as_u64(), 6);
575 }
576
577 #[test]
578 fn test_message_id_display() {
579 let id = MessageId::new(100);
580 assert_eq!(id.to_string(), "100");
581 }
582
583 #[test]
584 fn test_request_roundtrip() {
585 let request = Request {
586 id: MessageId::new(1),
587 session_id: SessionId::new("test-session"),
588 operation: Operation::Eval { code: "(+ 1 2)".to_string(), mode: ReplMode::Lisp },
589 };
590
591 let json = serde_json::to_string(&request).unwrap();
593 let deserialized: Request = serde_json::from_str(&json).unwrap();
594 assert_eq!(request, deserialized);
595 }
596
597 #[test]
598 fn test_response_success_roundtrip() {
599 let response = Response {
600 request_id: MessageId::new(1),
601 session_id: SessionId::new("test-session"),
602 result: OperationResult::Success {
603 status: Status { tier: 1, cached: false, duration_ms: 5 },
604 value: Some("3".to_string()),
605 stdout: None,
606 stderr: None,
607 },
608 };
609
610 let json = serde_json::to_string(&response).unwrap();
611 let deserialized: Response = serde_json::from_str(&json).unwrap();
612 assert_eq!(response, deserialized);
613 }
614
615 #[test]
616 fn test_response_error_roundtrip() {
617 let response = Response {
618 request_id: MessageId::new(2),
619 session_id: SessionId::new("test-session"),
620 result: OperationResult::Error {
621 error: ErrorInfo {
622 kind: ErrorKind::RuntimeError,
623 message: "Division by zero".to_string(),
624 location: None,
625 details: None,
626 },
627 stdout: None,
628 stderr: Some("Error occurred".to_string()),
629 },
630 };
631
632 let json = serde_json::to_string(&response).unwrap();
633 let deserialized: Response = serde_json::from_str(&json).unwrap();
634 assert_eq!(response, deserialized);
635 }
636
637 #[test]
638 fn test_all_operations_roundtrip() {
639 let operations = vec![
640 Operation::CreateSession { mode: ReplMode::Lisp },
641 Operation::CreateSession { mode: ReplMode::Sexpr },
642 Operation::Eval { code: "(+ 1 2)".to_string(), mode: ReplMode::Lisp },
643 Operation::Clone { source_session_id: SessionId::new("source") },
644 Operation::LoadFile { path: "test.lisp".to_string(), mode: ReplMode::Lisp },
645 Operation::Interrupt,
646 Operation::Close,
647 Operation::LsSessions,
648 Operation::Describe { symbol: "foo".to_string() },
649 Operation::History { limit: Some(10) },
650 Operation::ClearOutput,
651 ];
652
653 for op in operations {
654 let request = Request {
655 id: MessageId::new(1),
656 session_id: SessionId::new("test"),
657 operation: op.clone(),
658 };
659
660 let json = serde_json::to_string(&request).unwrap();
661 let deserialized: Request = serde_json::from_str(&json).unwrap();
662 assert_eq!(request, deserialized);
663 }
664 }
665
666 #[test]
667 fn test_all_error_kinds_serialization() {
668 let error_kinds = vec![
669 ErrorKind::SyntaxError,
670 ErrorKind::TypeError,
671 ErrorKind::RuntimeError,
672 ErrorKind::CompilationError,
673 ErrorKind::SessionNotFound,
674 ErrorKind::SessionAlreadyExists,
675 ErrorKind::InternalError,
676 ];
677
678 for kind in error_kinds {
679 let error_info = ErrorInfo {
680 kind,
681 message: "Test error".to_string(),
682 location: Some(SourceLocation { offset: 10, line: 5, column: 10 }),
683 details: Some("Additional details".to_string()),
684 };
685
686 let json = serde_json::to_string(&error_info).unwrap();
687 let deserialized: ErrorInfo = serde_json::from_str(&json).unwrap();
688 assert_eq!(error_info, deserialized);
689 }
690 }
691
692 #[test]
693 fn test_session_info_serialization() {
694 let info = SessionInfo {
695 id: SessionId::new("test-session"),
696 name: Some("test".to_string()),
697 mode: ReplMode::Lisp,
698 eval_count: 42,
699 created_at: 1234567890,
700 last_active_at: 1234568000,
701 timeout_ms: 3600000,
702 };
703
704 let json = serde_json::to_string(&info).unwrap();
705 let deserialized: SessionInfo = serde_json::from_str(&json).unwrap();
706 assert_eq!(info, deserialized);
707 }
708
709 #[test]
710 fn test_operation_result_sessions() {
711 let result = OperationResult::Sessions {
712 sessions: vec![
713 SessionInfo {
714 id: SessionId::new("session-1"),
715 name: None,
716 mode: ReplMode::Lisp,
717 eval_count: 10,
718 created_at: 1000,
719 last_active_at: 1100,
720 timeout_ms: 3600000,
721 },
722 SessionInfo {
723 id: SessionId::new("session-2"),
724 name: Some("work".to_string()),
725 mode: ReplMode::Sexpr,
726 eval_count: 5,
727 created_at: 2000,
728 last_active_at: 2100,
729 timeout_ms: 3600000,
730 },
731 ],
732 };
733
734 let json = serde_json::to_string(&result).unwrap();
735 let deserialized: OperationResult = serde_json::from_str(&json).unwrap();
736 assert_eq!(result, deserialized);
737 }
738
739 #[test]
740 fn test_source_location_format() {
741 let loc = SourceLocation { offset: 42, line: 10, column: 5 };
742 assert_eq!(loc.to_string(), "line 10, column 5");
743 }
744
745 #[test]
746 fn test_repl_modes_display() {
747 assert_eq!(ReplMode::Lisp.to_string(), "Lisp");
748 assert_eq!(ReplMode::Sexpr.to_string(), "Sexpr");
749 }
750
751 #[test]
752 fn test_error_kinds_display() {
753 assert_eq!(ErrorKind::SyntaxError.to_string(), "Syntax Error");
754 assert_eq!(ErrorKind::RuntimeError.to_string(), "Runtime Error");
755 assert_eq!(ErrorKind::SessionNotFound.to_string(), "Session Not Found");
756 }
757
758 #[test]
759 fn test_error_info_with_location() {
760 let error = ErrorInfo {
761 kind: ErrorKind::SyntaxError,
762 message: "Unexpected token".to_string(),
763 location: Some(SourceLocation { offset: 15, line: 3, column: 15 }),
764 details: None,
765 };
766
767 let display = error.to_string();
768 assert!(display.contains("Syntax Error"));
769 assert!(display.contains("Unexpected token"));
770 assert!(display.contains("line 3, column 15"));
771 }
772}