use chrono::{DateTime, TimeZone, Utc};
use klieo_core::error::MemoryError;
use serde::{Deserialize, Serialize};
const RESERVED_ENTITY_LABELS: &[&str] = &[
"Ticket",
"Policy",
"Agent",
"Member",
"Concept",
"Article",
"Obligation",
"Deadline",
];
pub fn known_epoch() -> DateTime<Utc> {
Utc.with_ymd_and_hms(1970, 1, 1, 0, 0, 0)
.single()
.expect("Unix epoch is a valid UTC datetime")
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum EntityType {
Ticket,
Policy,
Agent,
Member,
Concept,
Article,
Obligation,
Deadline,
Other {
hint: String,
},
}
impl EntityType {
pub fn as_str(&self) -> &str {
match self {
Self::Ticket => "Ticket",
Self::Policy => "Policy",
Self::Agent => "Agent",
Self::Member => "Member",
Self::Concept => "Concept",
Self::Article => "Article",
Self::Obligation => "Obligation",
Self::Deadline => "Deadline",
Self::Other { hint } => hint.as_str(),
}
}
pub fn from_label(label: &str) -> Self {
match label {
"Ticket" => Self::Ticket,
"Policy" => Self::Policy,
"Agent" => Self::Agent,
"Member" => Self::Member,
"Concept" => Self::Concept,
"Article" => Self::Article,
"Obligation" => Self::Obligation,
"Deadline" => Self::Deadline,
other => Self::Other {
hint: other.to_string(),
},
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub struct EntityRef {
pub entity_type: EntityType,
pub name: String,
}
impl EntityRef {
pub fn try_new(entity_type: EntityType, name: impl Into<String>) -> Result<Self, MemoryError> {
if let EntityType::Other { hint } = &entity_type {
if RESERVED_ENTITY_LABELS.iter().any(|label| label == hint) {
return Err(MemoryError::Store(format!(
"EntityType::Other {{ hint: {hint:?} }} collides with reserved typed label; use the typed variant"
)));
}
}
Ok(Self {
entity_type,
name: name.into().to_ascii_lowercase(),
})
}
pub fn new(entity_type: EntityType, name: impl Into<String>) -> Self {
Self::try_new(entity_type, name).expect("EntityRef::new violated reserved-label constraint")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_label_covers_every_reserved_label() {
for label in RESERVED_ENTITY_LABELS {
let resolved = EntityType::from_label(label);
assert!(
!matches!(resolved, EntityType::Other { .. }),
"reserved label {label:?} routed to Other — \
add an arm to EntityType::from_label",
);
assert_eq!(resolved.as_str(), *label);
}
}
}