use std::future::Future;
use std::sync::Arc;
use std::time::SystemTime;
use serde::{Deserialize, Serialize};
use crate::PortError;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContextUpdatedEvent {
pub root_node_id: String,
pub role: String,
pub revision: u64,
pub content_hash: String,
pub changes: Vec<ContextEventChange>,
pub idempotency_key: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub logical_digest: Option<String>,
pub requested_by: Option<String>,
#[serde(with = "system_time_serde")]
pub occurred_at: SystemTime,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContextEventChange {
pub operation: String,
pub entity_kind: String,
pub entity_id: String,
pub payload_json: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub scopes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IdempotentOutcome {
pub revision: u64,
pub content_hash: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub logical_digest: Option<String>,
}
pub trait ContextEventStore {
fn append(
&self,
event: ContextUpdatedEvent,
expected_revision: u64,
) -> impl Future<Output = Result<u64, PortError>> + Send;
fn current_revision(
&self,
root_node_id: &str,
role: &str,
) -> impl Future<Output = Result<u64, PortError>> + Send;
fn current_content_hash(
&self,
root_node_id: &str,
role: &str,
) -> impl Future<Output = Result<Option<String>, PortError>> + Send;
fn find_by_idempotency_key(
&self,
key: &str,
) -> impl Future<Output = Result<Option<IdempotentOutcome>, PortError>> + Send;
}
impl<T> ContextEventStore for Arc<T>
where
T: ContextEventStore + Send + Sync + ?Sized,
{
async fn append(
&self,
event: ContextUpdatedEvent,
expected_revision: u64,
) -> Result<u64, PortError> {
self.as_ref().append(event, expected_revision).await
}
async fn current_revision(&self, root_node_id: &str, role: &str) -> Result<u64, PortError> {
self.as_ref().current_revision(root_node_id, role).await
}
async fn current_content_hash(
&self,
root_node_id: &str,
role: &str,
) -> Result<Option<String>, PortError> {
self.as_ref().current_content_hash(root_node_id, role).await
}
async fn find_by_idempotency_key(
&self,
key: &str,
) -> Result<Option<IdempotentOutcome>, PortError> {
self.as_ref().find_by_idempotency_key(key).await
}
}
mod system_time_serde {
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S>(time: &SystemTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let millis = time
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_millis() as u64;
millis.serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<SystemTime, D::Error>
where
D: Deserializer<'de>,
{
let millis = u64::deserialize(deserializer)?;
Ok(UNIX_EPOCH + Duration::from_millis(millis))
}
}