franken_snowflake_sqlapi/
status.rs1use franken_snowflake_core::outcome::OutcomeKind;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum ResponseClass {
14 Completed,
16 Running,
19 StatementTimeout,
22 StatementFailed,
25 RateLimited,
28 Other(u16),
30}
31
32impl ResponseClass {
33 #[must_use]
35 pub const fn from_status(code: u16) -> Self {
36 match code {
37 200 => Self::Completed,
38 202 => Self::Running,
39 408 => Self::StatementTimeout,
40 422 => Self::StatementFailed,
41 429 => Self::RateLimited,
42 other => Self::Other(other),
43 }
44 }
45
46 #[must_use]
48 pub const fn should_poll(self) -> bool {
49 matches!(self, Self::Running)
50 }
51
52 #[must_use]
55 pub const fn is_retryable(self) -> bool {
56 matches!(self, Self::RateLimited | Self::Other(500..=599))
57 }
58
59 #[must_use]
62 pub const fn is_terminal(self) -> bool {
63 matches!(
64 self,
65 Self::Completed | Self::StatementTimeout | Self::StatementFailed
66 )
67 }
68
69 #[must_use]
72 pub const fn terminal_outcome(self) -> Option<OutcomeKind> {
73 match self {
74 Self::Completed => Some(OutcomeKind::Success),
75 Self::StatementTimeout => Some(OutcomeKind::Timeout),
76 Self::StatementFailed => Some(OutcomeKind::Error),
77 Self::Running | Self::RateLimited | Self::Other(_) => None,
78 }
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn status_codes_map_to_distinct_states() {
88 assert_eq!(ResponseClass::from_status(200), ResponseClass::Completed);
89 assert_eq!(ResponseClass::from_status(202), ResponseClass::Running);
90 assert_eq!(
91 ResponseClass::from_status(408),
92 ResponseClass::StatementTimeout
93 );
94 assert_eq!(
95 ResponseClass::from_status(422),
96 ResponseClass::StatementFailed
97 );
98 assert_eq!(ResponseClass::from_status(429), ResponseClass::RateLimited);
99 assert_eq!(ResponseClass::from_status(503), ResponseClass::Other(503));
100 }
101
102 #[test]
103 fn poll_retry_and_terminal_are_not_conflated() {
104 assert!(ResponseClass::Running.should_poll());
106 assert!(!ResponseClass::Running.is_retryable());
107 assert!(!ResponseClass::Running.is_terminal());
108
109 assert!(ResponseClass::RateLimited.is_retryable());
111 assert!(!ResponseClass::RateLimited.should_poll());
112 assert!(!ResponseClass::RateLimited.is_terminal());
113
114 assert!(ResponseClass::StatementTimeout.is_terminal());
116 assert!(!ResponseClass::StatementTimeout.is_retryable());
117 assert_eq!(
118 ResponseClass::StatementTimeout.terminal_outcome(),
119 Some(OutcomeKind::Timeout)
120 );
121 assert_eq!(
122 ResponseClass::StatementFailed.terminal_outcome(),
123 Some(OutcomeKind::Error)
124 );
125 }
126
127 #[test]
128 fn completed_is_success_and_5xx_retries() {
129 assert_eq!(
130 ResponseClass::Completed.terminal_outcome(),
131 Some(OutcomeKind::Success)
132 );
133 assert!(ResponseClass::from_status(500).is_retryable());
134 assert!(!ResponseClass::from_status(404).is_retryable());
135 assert_eq!(ResponseClass::Running.terminal_outcome(), None);
136 }
137}