use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};
use std::{sync::Arc, time::Duration};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Phase {
Disconnected,
Connecting,
Initializing,
Connected,
Reconnecting,
Failed,
}
impl From<Phase> for u8 {
#[inline]
fn from(value: Phase) -> Self {
match value {
Phase::Disconnected => 0,
Phase::Connecting => 1,
Phase::Initializing => 2,
Phase::Connected => 3,
Phase::Reconnecting => 4,
Phase::Failed => 5,
}
}
}
impl From<Phase> for i64 {
#[inline]
fn from(value: Phase) -> Self {
i64::from(u8::from(value))
}
}
impl Phase {
#[inline]
pub fn is_connected(self) -> bool {
matches!(self, Phase::Connected)
}
#[inline]
pub fn is_connecting(self) -> bool {
matches!(self, Phase::Connecting | Phase::Initializing)
}
#[inline]
pub fn is_reconnecting(self) -> bool {
matches!(self, Phase::Reconnecting)
}
#[inline]
pub fn is_failed(self) -> bool {
matches!(self, Phase::Failed)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FailureKind {
Retryable,
Fatal,
Stop,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FailurePhase {
Connect,
Init,
Run,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FailureReport {
pub phase: FailurePhase,
pub kind: FailureKind,
pub summary: Arc<str>,
pub code: Option<Arc<str>>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RetryBudgetSnapshot {
pub exhausted: bool,
pub remaining_hint: Option<u32>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ConnectionState {
pub phase: Phase,
pub attempt: u64,
pub emitted_at_unix_ms: u64,
pub phase_entered_at_unix_ms: u64,
pub backoff: Option<Duration>,
pub last_failure: Option<Arc<FailureReport>>,
pub budget: RetryBudgetSnapshot,
}
impl ConnectionState {
#[inline]
pub fn new(phase: Phase, attempt: u64, now_unix_ms: u64, phase_entered_unix_ms: u64) -> Self {
Self {
phase,
attempt,
emitted_at_unix_ms: now_unix_ms,
phase_entered_at_unix_ms: phase_entered_unix_ms,
backoff: None,
last_failure: None,
budget: RetryBudgetSnapshot {
exhausted: false,
remaining_hint: None,
},
}
}
#[inline]
pub fn now(phase: Phase, attempt: u64) -> Self {
let now_ms = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis()
.min(u64::MAX as u128) as u64;
Self::new(phase, attempt, now_ms, now_ms)
}
#[inline]
pub fn arc_now(phase: Phase, attempt: u64) -> Arc<Self> {
Arc::new(Self::now(phase, attempt))
}
#[inline]
pub fn arc_now_with_failure(
phase: Phase,
attempt: u64,
last_failure: Option<Arc<FailureReport>>,
) -> Arc<Self> {
let mut st = Self::now(phase, attempt);
st.last_failure = last_failure;
Arc::new(st)
}
#[inline]
pub fn is_connected(&self) -> bool {
self.phase.is_connected()
}
#[inline]
pub fn is_reconnecting(&self) -> bool {
self.phase.is_reconnecting()
}
#[inline]
pub fn is_failed(&self) -> bool {
self.phase.is_failed()
}
#[inline]
pub fn is_disconnected(&self) -> bool {
matches!(self.phase, Phase::Disconnected)
}
#[inline]
pub fn as_value(&self) -> i64 {
i64::from(self.phase)
}
}