use std::collections::BTreeMap;
use std::time::SystemTime;
use async_trait::async_trait;
use super::discovery::{
DiscoveryCompleteness, DiscoveryObservation, DiscoveryResult, DiscoverySource,
};
use super::index::{AvailabilityIndex, AvailabilityRecord};
use super::refs::{AvailabilityKey, ScopeRef};
use crate::backends::control_plane::ControlPlaneError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum ObservationSlot {
Current,
LastKnownGood,
}
impl ObservationSlot {
pub const ALL: &'static [Self] = &[Self::Current, Self::LastKnownGood];
pub const fn as_str(self) -> &'static str {
match self {
Self::Current => "current",
Self::LastKnownGood => "last_known_good",
}
}
pub fn parse(input: &str) -> Option<Self> {
Self::ALL
.iter()
.copied()
.find(|slot| slot.as_str() == input)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoredObservation {
pub key: AvailabilityKey,
pub slot: ObservationSlot,
pub observation: DiscoveryObservation,
pub definitive_at: Option<SystemTime>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EvidenceClear {
pub key: AvailabilityKey,
pub before: SystemTime,
}
impl EvidenceClear {
pub const fn new(key: AvailabilityKey, before: SystemTime) -> Self {
Self { key, before }
}
}
impl StoredObservation {
pub fn of_index(index: &AvailabilityIndex) -> Vec<Self> {
let mut rows = Vec::new();
for (key, record) in index.records() {
for (slot, held) in [
(ObservationSlot::Current, &record.discovery),
(ObservationSlot::LastKnownGood, &record.last_known_good),
] {
if let Some(observation) = held {
rows.push(Self {
key: key.clone(),
slot,
observation: observation.clone().without_detail(),
definitive_at: record.definitive_at,
});
}
}
}
rows
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct EvidenceWrite {
rows: Vec<StoredObservation>,
cleared: Vec<EvidenceClear>,
}
impl EvidenceWrite {
pub fn of_index(index: &AvailabilityIndex) -> Self {
Self {
rows: StoredObservation::of_index(index),
cleared: index
.records()
.filter(|(_, record)| {
record.definitive_at.is_some()
&& record.discovery.is_none()
&& record.last_known_good.is_none()
})
.map(|(key, record)| {
EvidenceClear::new(key.clone(), record.definitive_at.expect("filtered"))
})
.collect(),
}
}
pub fn of_rows(rows: Vec<StoredObservation>) -> Self {
Self {
rows,
cleared: Vec::new(),
}
}
#[must_use]
pub fn clearing(mut self, keys: impl IntoIterator<Item = EvidenceClear>) -> Self {
self.cleared.extend(keys);
self.cleared.sort_by(|left, right| left.key.cmp(&right.key));
self.cleared.dedup_by(|left, right| {
if left.key == right.key {
right.before = right.before.max(left.before);
true
} else {
false
}
});
self
}
pub fn rows(&self) -> &[StoredObservation] {
&self.rows
}
pub fn cleared(&self) -> &[EvidenceClear] {
&self.cleared
}
pub fn is_empty(&self) -> bool {
self.rows.is_empty() && self.cleared.is_empty()
}
}
#[async_trait]
pub trait ObservationStore: Send + Sync {
async fn load(
&self,
scope: Option<ScopeRef>,
) -> Result<Vec<StoredObservation>, ControlPlaneError>;
async fn save(&self, write: &EvidenceWrite) -> Result<(), ControlPlaneError>;
}
pub(super) fn restored_records(
rows: impl IntoIterator<Item = StoredObservation>,
) -> BTreeMap<AvailabilityKey, AvailabilityRecord> {
let mut records: BTreeMap<AvailabilityKey, AvailabilityRecord> = BTreeMap::new();
for row in rows {
let record = records.entry(row.key).or_default();
match row.slot {
ObservationSlot::Current => record.discovery = Some(row.observation),
ObservationSlot::LastKnownGood => record.last_known_good = Some(row.observation),
}
record.definitive_at = match (record.definitive_at, row.definitive_at) {
(Some(held), Some(stored)) => Some(held.max(stored)),
(held, stored) => held.or(stored),
};
}
records
}
pub(crate) fn parse_result(text: &str) -> Option<DiscoveryResult> {
DiscoveryResult::ALL
.iter()
.copied()
.find(|value| value.as_str() == text)
}
pub(crate) fn parse_completeness(text: &str) -> Option<DiscoveryCompleteness> {
DiscoveryCompleteness::ALL
.iter()
.copied()
.find(|value| value.as_str() == text)
}
pub(crate) fn parse_source(text: &str) -> Option<DiscoverySource> {
DiscoverySource::ALL
.iter()
.copied()
.find(|value| value.as_str() == text)
}