use franken_snowflake_core::outcome::OutcomeKind;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResponseClass {
Completed,
Running,
StatementTimeout,
StatementFailed,
RateLimited,
Other(u16),
}
impl ResponseClass {
#[must_use]
pub const fn from_status(code: u16) -> Self {
match code {
200 => Self::Completed,
202 => Self::Running,
408 => Self::StatementTimeout,
422 => Self::StatementFailed,
429 => Self::RateLimited,
other => Self::Other(other),
}
}
#[must_use]
pub const fn should_poll(self) -> bool {
matches!(self, Self::Running)
}
#[must_use]
pub const fn is_retryable(self) -> bool {
matches!(self, Self::RateLimited | Self::Other(500..=599))
}
#[must_use]
pub const fn is_terminal(self) -> bool {
matches!(
self,
Self::Completed | Self::StatementTimeout | Self::StatementFailed
)
}
#[must_use]
pub const fn terminal_outcome(self) -> Option<OutcomeKind> {
match self {
Self::Completed => Some(OutcomeKind::Success),
Self::StatementTimeout => Some(OutcomeKind::Timeout),
Self::StatementFailed => Some(OutcomeKind::Error),
Self::Running | Self::RateLimited | Self::Other(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_codes_map_to_distinct_states() {
assert_eq!(ResponseClass::from_status(200), ResponseClass::Completed);
assert_eq!(ResponseClass::from_status(202), ResponseClass::Running);
assert_eq!(
ResponseClass::from_status(408),
ResponseClass::StatementTimeout
);
assert_eq!(
ResponseClass::from_status(422),
ResponseClass::StatementFailed
);
assert_eq!(ResponseClass::from_status(429), ResponseClass::RateLimited);
assert_eq!(ResponseClass::from_status(503), ResponseClass::Other(503));
}
#[test]
fn poll_retry_and_terminal_are_not_conflated() {
assert!(ResponseClass::Running.should_poll());
assert!(!ResponseClass::Running.is_retryable());
assert!(!ResponseClass::Running.is_terminal());
assert!(ResponseClass::RateLimited.is_retryable());
assert!(!ResponseClass::RateLimited.should_poll());
assert!(!ResponseClass::RateLimited.is_terminal());
assert!(ResponseClass::StatementTimeout.is_terminal());
assert!(!ResponseClass::StatementTimeout.is_retryable());
assert_eq!(
ResponseClass::StatementTimeout.terminal_outcome(),
Some(OutcomeKind::Timeout)
);
assert_eq!(
ResponseClass::StatementFailed.terminal_outcome(),
Some(OutcomeKind::Error)
);
}
#[test]
fn completed_is_success_and_5xx_retries() {
assert_eq!(
ResponseClass::Completed.terminal_outcome(),
Some(OutcomeKind::Success)
);
assert!(ResponseClass::from_status(500).is_retryable());
assert!(!ResponseClass::from_status(404).is_retryable());
assert_eq!(ResponseClass::Running.terminal_outcome(), None);
}
}