use std::collections::BTreeMap;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::model::{ActorRef, KeepsakeId, RelationId, SubjectRef};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CommandContext {
pub actor: ActorRef,
pub idempotency_key: Option<String>,
pub metadata: BTreeMap<String, String>,
}
impl CommandContext {
#[must_use]
pub const fn new(actor: ActorRef) -> Self {
Self {
actor,
idempotency_key: None,
metadata: BTreeMap::new(),
}
}
#[must_use]
pub fn with_idempotency_key(mut self, key: impl Into<String>) -> Self {
self.idempotency_key = Some(key.into());
self
}
#[must_use]
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ApplyKeepsake {
pub id: KeepsakeId,
pub subject: SubjectRef,
pub relation_id: RelationId,
pub at: DateTime<Utc>,
pub metadata: BTreeMap<String, String>,
pub context: CommandContext,
}
impl ApplyKeepsake {
#[must_use]
pub fn new(
subject: SubjectRef,
relation_id: RelationId,
at: DateTime<Utc>,
context: CommandContext,
) -> Self {
Self {
id: Uuid::now_v7(),
subject,
relation_id,
at,
metadata: BTreeMap::new(),
context,
}
}
#[must_use]
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RevokeKeepsake {
pub keepsake_id: KeepsakeId,
pub at: DateTime<Utc>,
pub context: CommandContext,
}
#[cfg(test)]
mod tests {
use chrono::Utc;
use uuid::Uuid;
use super::*;
use crate::model::{ActorRef, SubjectRef};
#[test]
fn command_context_builder_sets_idempotency_and_metadata() -> crate::Result<()> {
let context = CommandContext::new(ActorRef::new("user", "admin")?)
.with_idempotency_key("request-1")
.with_metadata("request_id", "req_123");
assert_eq!(context.actor, ActorRef::new("user", "admin")?);
assert_eq!(context.idempotency_key.as_deref(), Some("request-1"));
assert_eq!(
context.metadata.get("request_id").map(String::as_str),
Some("req_123")
);
Ok(())
}
#[test]
fn apply_builder_attaches_metadata() -> crate::Result<()> {
let command = ApplyKeepsake::new(
SubjectRef::new("account", "acct_123")?,
Uuid::nil(),
Utc::now(),
CommandContext::new(ActorRef::new("system", "worker")?),
)
.with_metadata("source", "support");
assert_eq!(
command.metadata.get("source").map(String::as_str),
Some("support")
);
Ok(())
}
}