use std::collections::BTreeSet;
use serde::{Deserialize, Serialize, de::Error as DeError};
use thiserror::Error;
use time::OffsetDateTime;
use crate::{
FactId, FactResolution, FactResolutionError, FactResolutionMetadata, ObservationFacts, Presence,
};
pub const MAX_FACT_OBSERVATIONS: usize = 256;
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct FactObservation {
fact: FactId,
value: bool,
metadata: Option<FactResolutionMetadata>,
observed_at: OffsetDateTime,
}
#[derive(Deserialize)]
struct ObservationWire {
fact: FactId,
value: bool,
metadata: Option<FactResolutionMetadata>,
observed_at: OffsetDateTime,
}
impl<'de> Deserialize<'de> for FactObservation {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let wire = ObservationWire::deserialize(deserializer)?;
Self::new(wire.fact, wire.value, wire.metadata, wire.observed_at).map_err(D::Error::custom)
}
}
impl FactObservation {
pub fn new(
fact: FactId,
value: bool,
metadata: Option<FactResolutionMetadata>,
observed_at: OffsetDateTime,
) -> Result<Self, ObservationError> {
if fact.as_str().len() > 128 || fact.as_str().chars().any(char::is_control) {
return Err(ObservationError::InvalidIdentity);
}
FactResolution::new((), metadata.clone(), observed_at)?;
Ok(Self {
fact,
value,
metadata,
observed_at,
})
}
#[must_use]
pub const fn fact(&self) -> &FactId {
&self.fact
}
#[must_use]
pub const fn value(&self) -> bool {
self.value
}
#[must_use]
pub const fn metadata(&self) -> Option<&FactResolutionMetadata> {
self.metadata.as_ref()
}
#[must_use]
pub const fn observed_at(&self) -> OffsetDateTime {
self.observed_at
}
pub fn validate_at(&self, now: OffsetDateTime) -> Result<(), FactResolutionError> {
FactResolution::new((), self.metadata.clone(), self.observed_at)?.validate_at(now)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Error)]
pub enum ObservationError {
#[error("invalid observation identity")]
InvalidIdentity,
#[error("too many selected fact observations")]
TooMany,
#[error("duplicate selected fact observation")]
Duplicate,
#[error("selected observation disagrees with resolved facts")]
Mismatch,
#[error(transparent)]
Freshness(#[from] FactResolutionError),
}
impl FactObservation {
pub(crate) fn validate_all(observations: &[Self]) -> Result<(), ObservationError> {
if observations.len() > MAX_FACT_OBSERVATIONS {
return Err(ObservationError::TooMany);
}
let mut seen = BTreeSet::new();
for observation in observations {
if !seen.insert(observation.fact()) {
return Err(ObservationError::Duplicate);
}
}
Ok(())
}
pub(crate) fn match_all(
observations: &[Self],
facts: &impl ObservationFacts,
) -> Result<(), ObservationError> {
Self::validate_all(observations)?;
for observation in observations {
let expected = if observation.value() {
Presence::Present
} else {
Presence::Absent
};
if facts.observation(observation.fact()) != Some(expected) {
return Err(ObservationError::Mismatch);
}
}
Ok(())
}
}