use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::{borrow::Cow, sync::Arc};
use crate::out::lane::{CommitOrder, InsertOrder, Lane};
use crate::out::subscription::StreamPosition;
use crate::sequence::*;
es_entity::entity_id! { OutboxEventId }
pub trait OutboxEventMarker<E>:
serde::de::DeserializeOwned + serde::Serialize + Send + Sync + 'static + Unpin + From<E>
{
fn as_event(&self) -> Option<&E>;
}
pub trait OutboxPayload {}
impl<T> OutboxEventMarker<T> for T
where
T: OutboxPayload
+ serde::de::DeserializeOwned
+ serde::Serialize
+ Send
+ Sync
+ 'static
+ Unpin
+ From<T>,
{
fn as_event(&self) -> Option<&T> {
Some(self)
}
}
pub enum OutboxEvent<P>
where
P: Serialize + DeserializeOwned + Send,
{
Persistent(EventDelivery<P>),
Ephemeral(Arc<EphemeralOutboxEvent<P>>),
}
impl<P> Clone for OutboxEvent<P>
where
P: Serialize + DeserializeOwned + Send,
{
fn clone(&self) -> Self {
match self {
Self::Persistent(event) => Self::Persistent(event.clone()),
Self::Ephemeral(event) => Self::Ephemeral(Arc::clone(event)),
}
}
}
impl<P> OutboxEvent<P>
where
P: Serialize + DeserializeOwned + Send,
{
pub fn as_event<E>(&self) -> Option<&E>
where
P: OutboxEventMarker<E>,
{
match self {
Self::Persistent(e) => (**e).as_event::<E>(),
Self::Ephemeral(e) => (**e).as_event::<E>(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(transparent)]
pub struct EphemeralEventType(Cow<'static, str>);
impl EphemeralEventType {
pub const fn new(name: &'static str) -> Self {
Self(Cow::Borrowed(name))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for EphemeralEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(bound(deserialize = "T: DeserializeOwned"))]
pub struct EphemeralOutboxEvent<T>
where
T: Serialize + DeserializeOwned + Send,
{
pub event_type: EphemeralEventType,
pub payload: T,
pub tracing_context: Option<es_entity::context::TracingContext>,
pub recorded_at: chrono::DateTime<chrono::Utc>,
}
impl<T> EphemeralOutboxEvent<T>
where
T: Serialize + DeserializeOwned + Send,
{
pub fn as_event<E>(&self) -> Option<&E>
where
T: OutboxEventMarker<E>,
{
self.payload.as_event()
}
#[cfg(feature = "tracing")]
pub fn inject_trace_parent(&self) {
if let Some(context) = &self.tracing_context {
context.inject_as_parent();
}
}
}
impl<P> From<EphemeralOutboxEvent<P>> for OutboxEvent<P>
where
P: Serialize + DeserializeOwned + Send,
{
fn from(event: EphemeralOutboxEvent<P>) -> Self {
Self::Ephemeral(Arc::new(event))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DecodeFailure {
pub raw: serde_json::Value,
pub error: String,
}
#[derive(Debug, Clone, thiserror::Error)]
#[error("undecodable persistent outbox event {id} at sequence {sequence}: {}", failure.error)]
pub struct UndecodableEventError {
pub id: OutboxEventId,
pub sequence: EventSequence,
pub recorded_at: chrono::DateTime<chrono::Utc>,
pub failure: DecodeFailure,
pub commit_group: CommitGroupId,
}
pub(crate) struct PersistentDelivery<P>(
Result<Arc<PersistentOutboxEvent<P>>, Arc<UndecodableEventError>>,
)
where
P: Serialize + DeserializeOwned + Send;
impl<P> PersistentDelivery<P>
where
P: Serialize + DeserializeOwned + Send,
{
pub(crate) fn sequence(&self) -> EventSequence {
match &self.0 {
Ok(event) => event.sequence,
Err(error) => error.sequence,
}
}
pub(crate) fn commit_group(&self) -> CommitGroupId {
match &self.0 {
Ok(event) => event.commit_group,
Err(error) => error.commit_group,
}
}
pub(crate) fn has_payload(&self) -> bool {
match &self.0 {
Ok(event) => event.payload.is_some(),
Err(_) => true,
}
}
#[allow(clippy::result_large_err)]
pub(crate) fn into_item(self) -> Result<Arc<PersistentOutboxEvent<P>>, UndecodableEventError> {
match self.0 {
Ok(event) => Ok(event),
Err(error) => Err((*error).clone()),
}
}
}
impl<P> From<Result<PersistentOutboxEvent<P>, UndecodableEventError>> for PersistentDelivery<P>
where
P: Serialize + DeserializeOwned + Send,
{
fn from(item: Result<PersistentOutboxEvent<P>, UndecodableEventError>) -> Self {
Self(match item {
Ok(event) => Ok(Arc::new(event)),
Err(error) => Err(Arc::new(error)),
})
}
}
impl<P> Clone for PersistentDelivery<P>
where
P: Serialize + DeserializeOwned + Send,
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PersistentOutboxEvent<T>
where
T: Serialize + DeserializeOwned + Send,
{
pub id: OutboxEventId,
pub sequence: EventSequence,
#[serde(bound = "T: DeserializeOwned")]
pub payload: Option<T>,
pub tracing_context: Option<es_entity::context::TracingContext>,
pub recorded_at: chrono::DateTime<chrono::Utc>,
pub commit_group: CommitGroupId,
}
impl<T> Clone for PersistentOutboxEvent<T>
where
T: Clone + Serialize + DeserializeOwned + Send,
{
fn clone(&self) -> Self {
Self {
id: self.id,
sequence: self.sequence,
payload: self.payload.clone(),
tracing_context: self.tracing_context.clone(),
recorded_at: self.recorded_at,
commit_group: self.commit_group,
}
}
}
pub struct Delivery<L, T>
where
L: Lane,
{
position: L::Position,
boundary: bool,
inner: T,
}
impl<L, T> Delivery<L, T>
where
L: Lane,
{
pub(crate) fn new(position: L::Position, boundary: bool, inner: T) -> Self {
Self {
position,
boundary,
inner,
}
}
pub fn position(&self) -> L::Position {
self.position
}
pub fn inner(&self) -> &T {
&self.inner
}
pub fn into_inner(self) -> T {
self.inner
}
pub(crate) fn is_boundary(&self) -> bool {
self.boundary
}
pub(crate) fn map<U>(self, f: impl FnOnce(T) -> U) -> Delivery<L, U> {
Delivery {
position: self.position,
boundary: self.boundary,
inner: f(self.inner),
}
}
}
impl<L, T, E> Delivery<L, Result<T, E>>
where
L: Lane,
{
pub(crate) fn transpose(self) -> Result<Delivery<L, T>, Delivery<L, E>> {
let Self {
position,
boundary,
inner,
} = self;
match inner {
Ok(inner) => Ok(Delivery {
position,
boundary,
inner,
}),
Err(inner) => Err(Delivery {
position,
boundary,
inner,
}),
}
}
}
impl<T> Delivery<CommitOrder, T> {
pub fn is_commit_boundary(&self) -> bool {
self.boundary
}
}
impl<L, T> std::ops::Deref for Delivery<L, T>
where
L: Lane,
{
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<L, T> Clone for Delivery<L, T>
where
L: Lane,
T: Clone,
{
fn clone(&self) -> Self {
Self {
position: self.position,
boundary: self.boundary,
inner: self.inner.clone(),
}
}
}
impl<L, T> std::fmt::Debug for Delivery<L, T>
where
L: Lane,
T: std::fmt::Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let position: StreamPosition = self.position.into();
f.debug_struct("Delivery")
.field("position", &position)
.field("inner", &self.inner)
.finish_non_exhaustive()
}
}
pub type EventDelivery<P, L = InsertOrder> = Delivery<L, Arc<PersistentOutboxEvent<P>>>;
pub type UndecodableDelivery<L = InsertOrder> = Delivery<L, UndecodableEventError>;
impl<L> std::fmt::Display for UndecodableDelivery<L>
where
L: Lane,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let position: StreamPosition = self.position.into();
write!(f, "{} (at {position})", self.inner)
}
}
impl<L> std::error::Error for UndecodableDelivery<L>
where
L: Lane,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.inner)
}
}
pub(crate) type Transport<L, P> = Delivery<L, PersistentDelivery<P>>;
impl<P> Transport<InsertOrder, P>
where
P: Serialize + DeserializeOwned + Send,
{
pub(crate) fn insert(inner: PersistentDelivery<P>) -> Self {
Delivery::new(inner.sequence(), true, inner)
}
}
impl<L, P> Transport<L, P>
where
L: Lane,
P: Serialize + DeserializeOwned + Send,
{
#[allow(clippy::result_large_err)]
pub(crate) fn into_item(self) -> Result<EventDelivery<P, L>, UndecodableDelivery<L>> {
self.map(PersistentDelivery::into_item).transpose()
}
}
impl<P> From<PersistentOutboxEvent<P>> for OutboxEvent<P>
where
P: Serialize + DeserializeOwned + Send,
{
fn from(event: PersistentOutboxEvent<P>) -> Self {
Self::Persistent(Delivery::new(event.sequence, true, Arc::new(event)))
}
}
impl<T> PersistentOutboxEvent<T>
where
T: Serialize + DeserializeOwned + Send,
{
pub fn as_event<E>(&self) -> Option<&E>
where
T: OutboxEventMarker<E>,
{
if let Some(payload) = &self.payload {
payload.as_event()
} else {
None
}
}
#[cfg(feature = "tracing")]
pub fn inject_trace_parent(&self) {
if let Some(context) = &self.tracing_context {
context.inject_as_parent();
}
}
}