Skip to main content

adk_managed/types/
session.rs

1//! Session lifecycle status types.
2
3use serde::{Deserialize, Serialize};
4
5/// Session lifecycle state. Entry state is `Queued`.
6///
7/// State machine:
8/// ```text
9/// Queued → Running → Idle (per turn) → Running (next turn)
10///                  → Rescheduling → Running (on retry success)
11///                  → Rescheduling → Failed (on retry exhaust)
12///                  → Paused → Running (on resume)
13///                  → Completed / Failed / Archived
14/// ```
15///
16/// # Example
17///
18/// ```
19/// use adk_managed::types::SessionStatus;
20///
21/// let status = SessionStatus::Queued;
22/// let json = serde_json::to_string(&status).unwrap();
23/// assert_eq!(json, r#""queued""#);
24/// ```
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26#[serde(rename_all = "lowercase")]
27#[non_exhaustive]
28pub enum SessionStatus {
29    /// Session created, waiting for first event to process.
30    Queued,
31    /// Actively processing a turn.
32    Running,
33    /// Turn complete, awaiting next input.
34    Idle,
35    /// Transient error encountered; auto-retrying with backoff.
36    /// Transitions: Running → Rescheduling → Running (success) | Failed (exhaust).
37    Rescheduling,
38    /// Explicitly paused by caller; checkpoint saved.
39    Paused,
40    /// Session completed successfully (terminal).
41    Completed,
42    /// Session failed (terminal).
43    Failed,
44    /// Session archived (terminal, data retained for read).
45    Archived,
46}
47
48impl SessionStatus {
49    /// Returns `true` if this is a terminal state (no further transitions allowed).
50    pub fn is_terminal(self) -> bool {
51        matches!(self, Self::Completed | Self::Failed | Self::Archived)
52    }
53}
54
55impl std::fmt::Display for SessionStatus {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        let s = match self {
58            Self::Queued => "queued",
59            Self::Running => "running",
60            Self::Idle => "idle",
61            Self::Rescheduling => "rescheduling",
62            Self::Paused => "paused",
63            Self::Completed => "completed",
64            Self::Failed => "failed",
65            Self::Archived => "archived",
66        };
67        f.write_str(s)
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn test_session_status_serializes_lowercase() {
77        let cases = [
78            (SessionStatus::Queued, "\"queued\""),
79            (SessionStatus::Running, "\"running\""),
80            (SessionStatus::Idle, "\"idle\""),
81            (SessionStatus::Rescheduling, "\"rescheduling\""),
82            (SessionStatus::Paused, "\"paused\""),
83            (SessionStatus::Completed, "\"completed\""),
84            (SessionStatus::Failed, "\"failed\""),
85            (SessionStatus::Archived, "\"archived\""),
86        ];
87
88        for (status, expected) in cases {
89            let json = serde_json::to_string(&status).unwrap();
90            assert_eq!(json, expected, "serialization failed for {status:?}");
91        }
92    }
93
94    #[test]
95    fn test_session_status_deserializes_lowercase() {
96        let cases = [
97            ("\"queued\"", SessionStatus::Queued),
98            ("\"running\"", SessionStatus::Running),
99            ("\"idle\"", SessionStatus::Idle),
100            ("\"rescheduling\"", SessionStatus::Rescheduling),
101            ("\"paused\"", SessionStatus::Paused),
102            ("\"completed\"", SessionStatus::Completed),
103            ("\"failed\"", SessionStatus::Failed),
104            ("\"archived\"", SessionStatus::Archived),
105        ];
106
107        for (json, expected) in cases {
108            let status: SessionStatus = serde_json::from_str(json).unwrap();
109            assert_eq!(status, expected, "deserialization failed for {json}");
110        }
111    }
112
113    #[test]
114    fn test_session_status_round_trip() {
115        let all_statuses = [
116            SessionStatus::Queued,
117            SessionStatus::Running,
118            SessionStatus::Idle,
119            SessionStatus::Rescheduling,
120            SessionStatus::Paused,
121            SessionStatus::Completed,
122            SessionStatus::Failed,
123            SessionStatus::Archived,
124        ];
125
126        for status in all_statuses {
127            let json = serde_json::to_string(&status).unwrap();
128            let deserialized: SessionStatus = serde_json::from_str(&json).unwrap();
129            assert_eq!(status, deserialized, "round-trip failed for {status:?}");
130        }
131    }
132
133    #[test]
134    fn test_session_status_rejects_unknown() {
135        let result: Result<SessionStatus, _> = serde_json::from_str("\"unknown_status\"");
136        assert!(result.is_err());
137    }
138
139    #[test]
140    fn test_session_status_is_terminal() {
141        assert!(!SessionStatus::Queued.is_terminal());
142        assert!(!SessionStatus::Running.is_terminal());
143        assert!(!SessionStatus::Idle.is_terminal());
144        assert!(!SessionStatus::Rescheduling.is_terminal());
145        assert!(!SessionStatus::Paused.is_terminal());
146        assert!(SessionStatus::Completed.is_terminal());
147        assert!(SessionStatus::Failed.is_terminal());
148        assert!(SessionStatus::Archived.is_terminal());
149    }
150
151    #[test]
152    fn test_session_status_display() {
153        assert_eq!(SessionStatus::Queued.to_string(), "queued");
154        assert_eq!(SessionStatus::Running.to_string(), "running");
155        assert_eq!(SessionStatus::Idle.to_string(), "idle");
156        assert_eq!(SessionStatus::Rescheduling.to_string(), "rescheduling");
157        assert_eq!(SessionStatus::Paused.to_string(), "paused");
158        assert_eq!(SessionStatus::Completed.to_string(), "completed");
159        assert_eq!(SessionStatus::Failed.to_string(), "failed");
160        assert_eq!(SessionStatus::Archived.to_string(), "archived");
161    }
162}