use std::time::SystemTime;
use super::discovery::DiscoverySource;
use crate::status::StatusScope;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AvailabilityState {
Available,
Unknown,
Stale,
Denied,
Unavailable,
}
impl AvailabilityState {
pub const ALL: &'static [Self] = &[
Self::Available,
Self::Unknown,
Self::Stale,
Self::Denied,
Self::Unavailable,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::Available => "available",
Self::Unknown => "unknown",
Self::Stale => "stale",
Self::Denied => "denied",
Self::Unavailable => "unavailable",
}
}
pub const fn permits_attempt(self) -> bool {
matches!(self, Self::Available | Self::Unknown | Self::Stale)
}
pub const fn is_definitive(self) -> bool {
matches!(self, Self::Available | Self::Denied | Self::Unavailable)
}
pub const fn certainty(self) -> u8 {
match self {
Self::Unknown => 0,
Self::Stale => 1,
Self::Available | Self::Denied | Self::Unavailable => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum DecidedBy {
Catalogue,
Enablement,
Entitlement,
Policy,
Discovery,
Runtime,
NoRecord,
Undisclosed,
}
impl DecidedBy {
pub const ALL: &'static [Self] = &[
Self::Catalogue,
Self::Enablement,
Self::Entitlement,
Self::Policy,
Self::Discovery,
Self::Runtime,
Self::NoRecord,
Self::Undisclosed,
];
pub const fn as_str(self) -> &'static str {
match self {
Self::Catalogue => "catalogue",
Self::Enablement => "enablement",
Self::Entitlement => "entitlement",
Self::Policy => "policy",
Self::Discovery => "discovery",
Self::Runtime => "runtime",
Self::NoRecord => "no_record",
Self::Undisclosed => "undisclosed",
}
}
pub const fn is_tenant_visible(self) -> bool {
matches!(
self,
Self::Catalogue
| Self::Enablement
| Self::Entitlement
| Self::Policy
| Self::NoRecord
| Self::Undisclosed
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum AvailabilityReason {
Observed,
LastKnownGood,
NotInCatalogue,
WithdrawnFromCatalogue,
NotEnabled,
EntitlementMissing,
EntitlementRevoked,
EntitlementUnknown,
PolicyDenied,
PolicyIndeterminate,
DiscoveryAbsent,
DiscoveryIncomplete,
DiscoveryUnsupported,
DiscoveryUnreliable,
EvidenceExpired,
NoEvidence,
RuntimeImpaired,
RuntimeUnavailable,
Unspecified,
}
impl AvailabilityReason {
pub const ALL: &'static [Self] = &[
Self::Observed,
Self::LastKnownGood,
Self::NotInCatalogue,
Self::WithdrawnFromCatalogue,
Self::NotEnabled,
Self::EntitlementMissing,
Self::EntitlementRevoked,
Self::EntitlementUnknown,
Self::PolicyDenied,
Self::PolicyIndeterminate,
Self::DiscoveryAbsent,
Self::DiscoveryIncomplete,
Self::DiscoveryUnsupported,
Self::DiscoveryUnreliable,
Self::EvidenceExpired,
Self::NoEvidence,
Self::RuntimeImpaired,
Self::RuntimeUnavailable,
Self::Unspecified,
];
pub const fn code(self) -> &'static str {
match self {
Self::Observed => "observed",
Self::LastKnownGood => "last_known_good",
Self::NotInCatalogue => "not_in_catalogue",
Self::WithdrawnFromCatalogue => "withdrawn_from_catalogue",
Self::NotEnabled => "not_enabled",
Self::EntitlementMissing => "entitlement_missing",
Self::EntitlementRevoked => "entitlement_revoked",
Self::EntitlementUnknown => "entitlement_unknown",
Self::PolicyDenied => "policy_denied",
Self::PolicyIndeterminate => "policy_indeterminate",
Self::DiscoveryAbsent => "discovery_absent",
Self::DiscoveryIncomplete => "discovery_incomplete",
Self::DiscoveryUnsupported => "discovery_unsupported",
Self::DiscoveryUnreliable => "discovery_unreliable",
Self::EvidenceExpired => "evidence_expired",
Self::NoEvidence => "no_evidence",
Self::RuntimeImpaired => "runtime_impaired",
Self::RuntimeUnavailable => "runtime_unavailable",
Self::Unspecified => "unspecified",
}
}
pub const fn is_tenant_safe(self) -> bool {
matches!(
self,
Self::Observed
| Self::LastKnownGood
| Self::NotInCatalogue
| Self::WithdrawnFromCatalogue
| Self::NotEnabled
| Self::EntitlementMissing
| Self::EntitlementRevoked
| Self::EntitlementUnknown
| Self::PolicyDenied
| Self::EvidenceExpired
| Self::NoEvidence
| Self::Unspecified
)
}
pub const fn for_scope(self, scope: StatusScope) -> Self {
match scope {
StatusScope::Deployment => self,
StatusScope::Namespace if self.is_tenant_safe() => self,
StatusScope::Namespace => Self::Unspecified,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Availability {
pub state: AvailabilityState,
pub reason: AvailabilityReason,
pub decided_by: DecidedBy,
pub observed_at: Option<SystemTime>,
pub expires_at: Option<SystemTime>,
pub source: Option<DiscoverySource>,
pub last_known_good: bool,
}
impl Availability {
pub const fn decided(
state: AvailabilityState,
reason: AvailabilityReason,
decided_by: DecidedBy,
) -> Self {
Self {
state,
reason,
decided_by,
observed_at: None,
expires_at: None,
source: None,
last_known_good: false,
}
}
pub const fn no_record() -> Self {
Self::decided(
AvailabilityState::Unknown,
AvailabilityReason::NoEvidence,
DecidedBy::NoRecord,
)
}
pub const fn with_evidence(
mut self,
observed_at: SystemTime,
expires_at: Option<SystemTime>,
source: DiscoverySource,
last_known_good: bool,
) -> Self {
self.observed_at = Some(observed_at);
self.expires_at = expires_at;
self.source = Some(source);
self.last_known_good = last_known_good;
self
}
pub const fn lowered_to_unknown(
mut self,
reason: AvailabilityReason,
decided_by: DecidedBy,
) -> Self {
self.state = AvailabilityState::Unknown;
self.reason = reason;
self.decided_by = decided_by;
self
}
pub const fn permits_attempt(&self) -> bool {
self.state.permits_attempt() && !matches!(self.decided_by, DecidedBy::NoRecord)
}
pub const fn for_scope(self, scope: StatusScope) -> Self {
match scope {
StatusScope::Deployment => self,
StatusScope::Namespace => Self {
state: self.state,
reason: self.reason.for_scope(scope),
decided_by: if self.decided_by.is_tenant_visible() && self.reason.is_tenant_safe() {
self.decided_by
} else {
DecidedBy::Undisclosed
},
observed_at: self.observed_at,
expires_at: self.expires_at,
source: None,
last_known_good: self.last_known_good,
},
}
}
}