use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::core::{CaseId, Digest, RunId, Timestamp};
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct CorrelationKey {
pub namespace: String,
pub value: String,
}
impl CorrelationKey {
pub fn new(namespace: impl Into<String>, value: impl Into<String>) -> Self {
Self {
namespace: namespace.into(),
value: value.into(),
}
}
}
impl std::fmt::Display for CorrelationKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}={}", self.namespace, self.value)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CaseStatus {
Open,
AwaitingExternal,
AwaitingHuman,
Escalated,
Closed,
}
impl CaseStatus {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Open => "open",
Self::AwaitingExternal => "awaiting_external",
Self::AwaitingHuman => "awaiting_human",
Self::Escalated => "escalated",
Self::Closed => "closed",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|c| c.as_str() == s)
}
pub const ALL: [Self; 5] = [
Self::Open,
Self::AwaitingExternal,
Self::AwaitingHuman,
Self::Escalated,
Self::Closed,
];
#[must_use]
pub fn is_closed(self) -> bool {
self == Self::Closed
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Case {
pub id: CaseId,
pub kind: String,
pub status: CaseStatus,
pub correlation: Vec<CorrelationKey>,
pub state: Value,
pub version: CaseVersion,
#[serde(with = "time::serde::rfc3339")]
pub opened_at: Timestamp,
pub runs: Vec<RunId>,
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize, Hash,
)]
pub struct CaseVersion(pub u64);
impl CaseVersion {
pub const INITIAL: Self = Self(0);
#[must_use]
pub const fn next(self) -> Self {
Self(self.0 + 1)
}
}
impl std::fmt::Display for CaseVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "v{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct DeadlineSpec {
pub kind: String,
pub params: Value,
}
impl DeadlineSpec {
pub fn new(kind: impl Into<String>, params: Value) -> Self {
Self {
kind: kind.into(),
params,
}
}
#[must_use]
pub fn minutes(n: u32) -> Self {
Self::new("minutes", serde_json::json!({ "n": n }))
}
#[must_use]
pub fn hours(n: u32) -> Self {
Self::new("hours", serde_json::json!({ "n": n }))
}
#[must_use]
pub fn days(n: u32) -> Self {
Self::new("days", serde_json::json!({ "n": n }))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DeadlineState {
Pending,
Warned,
Breached,
Met,
Cancelled,
}
impl DeadlineState {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Pending => "pending",
Self::Warned => "warned",
Self::Breached => "breached",
Self::Met => "met",
Self::Cancelled => "cancelled",
}
}
#[must_use]
pub fn is_open(self) -> bool {
matches!(self, Self::Pending | Self::Warned)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Deadline {
pub case: CaseId,
pub name: String,
#[serde(with = "time::serde::rfc3339")]
pub resolved_at: Timestamp,
pub calendar_digest: Digest,
#[serde(default, with = "time::serde::rfc3339::option")]
pub warn_at: Option<Timestamp>,
pub state: DeadlineState,
}
impl Deadline {
#[must_use]
pub fn is_due(&self, now: Timestamp) -> bool {
self.state.is_open() && now >= self.resolved_at
}
#[must_use]
pub fn needs_warning(&self, now: Timestamp) -> bool {
self.state == DeadlineState::Pending
&& self.warn_at.is_some_and(|w| now >= w)
&& now < self.resolved_at
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SweptAction {
DeadlineWarned,
DeadlineBreached,
CaseEscalated,
TaskExpired,
TaskEscalated,
RunRecovered,
}