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, Eq, Serialize, Deserialize)]
pub struct LegalHold {
#[serde(with = "time::serde::rfc3339")]
pub placed_at: Timestamp,
pub reason: String,
}
#[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 parse(s: &str) -> Option<Self> {
Self::ALL.iter().copied().find(|c| c.as_str() == s)
}
pub const ALL: [Self; 5] = [
Self::Pending,
Self::Warned,
Self::Breached,
Self::Met,
Self::Cancelled,
];
#[must_use]
pub fn is_open(self) -> bool {
matches!(self, Self::Pending | Self::Warned)
}
#[must_use]
pub const fn is_unaccounted(self, accounted: bool) -> bool {
matches!(self, Self::Breached) && !accounted
}
#[must_use]
pub const fn may_become(self, to: Self) -> bool {
matches!(
(self, to),
(Self::Pending, Self::Pending | Self::Warned)
| (Self::Warned, Self::Warned)
| (Self::Met, Self::Met)
| (Self::Breached, Self::Breached)
| (Self::Cancelled, Self::Cancelled)
| (
Self::Pending | Self::Warned,
Self::Met | Self::Breached | Self::Cancelled
)
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct BreachNote {
pub by: String,
pub note: String,
#[serde(with = "time::serde::rfc3339")]
pub at: Timestamp,
}
#[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,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acknowledged: Option<BreachNote>,
}
impl Deadline {
#[must_use]
pub fn is_due(&self, now: Timestamp) -> bool {
self.state.is_open() && now >= self.resolved_at
}
#[must_use]
pub const fn is_unaccounted(&self) -> bool {
self.state.is_unaccounted(self.acknowledged.is_some())
}
#[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,
}