use std::time::SystemTime;
use serde::{Deserialize, Serialize};
use super::status::HealthStatus;
use crate::runtime::lifecycle::LifecycleState;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum ReadinessState {
Ready,
NotReady,
Unknown,
}
impl ReadinessState {
#[must_use]
pub const fn is_ready(self) -> bool {
matches!(self, Self::Ready)
}
#[must_use]
pub const fn health_status(self) -> HealthStatus {
match self {
Self::Ready => HealthStatus::Healthy,
Self::NotReady => HealthStatus::Unhealthy,
Self::Unknown => HealthStatus::Unknown,
}
}
}
impl From<LifecycleState> for ReadinessState {
fn from(state: LifecycleState) -> Self {
match state {
LifecycleState::Created
| LifecycleState::Starting
| LifecycleState::Configuring
| LifecycleState::Dependencies
| LifecycleState::Capabilities
| LifecycleState::Registering
| LifecycleState::Ready
| LifecycleState::Draining
| LifecycleState::Stopped => Self::NotReady,
LifecycleState::Serving => Self::Ready,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct ReadinessReport {
state: ReadinessState,
observed_at: SystemTime,
}
impl ReadinessReport {
#[must_use]
pub fn new(state: ReadinessState) -> Self {
Self {
state,
observed_at: SystemTime::now(),
}
}
#[must_use]
pub const fn at(state: ReadinessState, observed_at: SystemTime) -> Self {
Self { state, observed_at }
}
#[must_use]
pub fn from_lifecycle(state: LifecycleState) -> Self {
Self::new(ReadinessState::from(state))
}
#[must_use]
pub fn at_lifecycle(state: LifecycleState, observed_at: SystemTime) -> Self {
Self::at(ReadinessState::from(state), observed_at)
}
#[must_use]
pub fn ready() -> Self {
Self::new(ReadinessState::Ready)
}
#[must_use]
pub fn not_ready() -> Self {
Self::new(ReadinessState::NotReady)
}
#[must_use]
pub fn unknown() -> Self {
Self::new(ReadinessState::Unknown)
}
#[must_use]
pub const fn state(&self) -> ReadinessState {
self.state
}
#[must_use]
pub const fn observed_at(&self) -> SystemTime {
self.observed_at
}
#[must_use]
pub const fn is_ready(&self) -> bool {
self.state.is_ready()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::{Duration, UNIX_EPOCH};
#[test]
fn lifecycle_mapping_matches_phase_eight_admission_boundary() {
assert_eq!(
ReadinessState::from(LifecycleState::Created),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Starting),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Configuring),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Dependencies),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Capabilities),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Registering),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Ready),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Serving),
ReadinessState::Ready
);
assert_eq!(
ReadinessState::from(LifecycleState::Draining),
ReadinessState::NotReady
);
assert_eq!(
ReadinessState::from(LifecycleState::Stopped),
ReadinessState::NotReady
);
}
#[test]
fn only_serving_is_ready_for_normal_work() {
assert!(!ReadinessReport::from_lifecycle(LifecycleState::Ready).is_ready());
assert!(ReadinessReport::from_lifecycle(LifecycleState::Serving).is_ready());
assert!(!ReadinessReport::from_lifecycle(LifecycleState::Draining).is_ready());
assert!(!ReadinessReport::from_lifecycle(LifecycleState::Stopped).is_ready());
}
#[test]
fn explicit_timestamp_is_preserved() {
let observed_at = UNIX_EPOCH + Duration::from_secs(42);
let report = ReadinessReport::at(ReadinessState::Ready, observed_at);
assert_eq!(report.state(), ReadinessState::Ready);
assert_eq!(report.observed_at(), observed_at);
}
#[test]
fn explicit_lifecycle_timestamp_is_preserved() {
let observed_at = UNIX_EPOCH + Duration::from_secs(84);
let report = ReadinessReport::at_lifecycle(LifecycleState::Serving, observed_at);
assert_eq!(report.state(), ReadinessState::Ready);
assert_eq!(report.observed_at(), observed_at);
}
#[test]
fn convenience_constructors_use_expected_states() {
assert_eq!(ReadinessReport::ready().state(), ReadinessState::Ready);
assert_eq!(
ReadinessReport::not_ready().state(),
ReadinessState::NotReady
);
assert_eq!(ReadinessReport::unknown().state(), ReadinessState::Unknown);
}
#[test]
fn new_records_a_current_observation_time() {
let before = SystemTime::now();
let report = ReadinessReport::new(ReadinessState::Ready);
let after = SystemTime::now();
assert!(report.observed_at() >= before);
assert!(report.observed_at() <= after);
}
#[test]
fn unknown_is_not_treated_as_ready() {
let report = ReadinessReport::unknown();
assert!(!report.is_ready());
assert_eq!(report.state().health_status(), HealthStatus::Unknown);
}
#[test]
fn readiness_does_not_change_runtime_lifecycle() {
let mut lifecycle = crate::runtime::lifecycle::Lifecycle::new();
lifecycle.transition(LifecycleState::Starting).unwrap();
lifecycle.transition(LifecycleState::Configuring).unwrap();
let _report = ReadinessReport::from_lifecycle(lifecycle.state());
assert_eq!(lifecycle.state(), LifecycleState::Configuring);
}
}