use batpak::coordinate::Coordinate;
use batpak::event::{EventKind, EventPayload};
use batpak::store::{AppendReceipt, Open, Store, StoreError};
type StoreTypedAppend<'event> =
Box<dyn FnOnce(&Store<Open>, &Coordinate) -> Result<AppendReceipt, StoreError> + 'event>;
pub struct TypedEffectEvent<'event> {
kind: EventKind,
payload_version: u16,
payload_bytes: Vec<u8>,
append_to_store: StoreTypedAppend<'event>,
}
impl<'event> TypedEffectEvent<'event> {
pub fn new<T: EventPayload>(payload: &'event T) -> Result<Self, EffectError> {
let payload_bytes = batpak::canonical::to_bytes(payload).map_err(|error| {
EffectError::new(format!("typed event payload encoding failed: {error}"))
})?;
Ok(Self {
kind: T::KIND,
payload_version: T::PAYLOAD_VERSION,
payload_bytes,
append_to_store: Box::new(move |store, coordinate| {
store.append_typed(coordinate, payload)
}),
})
}
#[must_use]
pub const fn kind(&self) -> EventKind {
self.kind
}
#[must_use]
pub const fn payload_version(&self) -> u16 {
self.payload_version
}
#[must_use]
pub fn payload_bytes(&self) -> &[u8] {
&self.payload_bytes
}
pub(crate) fn append_to_store(
self,
store: &Store<Open>,
coordinate: &Coordinate,
) -> Result<AppendReceipt, StoreError> {
(self.append_to_store)(store, coordinate)
}
}
pub trait EffectBackend {
fn append_event(&mut self, kind: EventKind, payload: &[u8]) -> Result<(), EffectError>;
fn append_typed_event<'event>(
&mut self,
_coordinate: &Coordinate,
_event: TypedEffectEvent<'event>,
) -> Result<AppendReceipt, EffectError> {
Err(EffectError::new(
"typed event appends are not supported by this effect backend",
))
}
fn read_event(&mut self, event_category: &str) -> Result<(), EffectError> {
let _ = event_category;
Err(EffectError::new(
"event reads are not supported by this effect backend",
))
}
fn query_projection(&mut self, projection_id: &str) -> Result<(), EffectError> {
let _ = projection_id;
Err(EffectError::new(
"projection queries are not supported by this effect backend",
))
}
fn emit_receipt(&mut self, receipt_kind: &str) -> Result<(), EffectError> {
let _ = receipt_kind;
Err(EffectError::new(
"receipt emission is not supported by this effect backend",
))
}
fn use_host_control(&mut self, control: &str) -> Result<(), EffectError> {
let _ = control;
Err(EffectError::new(
"host controls are not supported by this effect backend",
))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EffectError {
message: String,
}
impl EffectError {
#[must_use]
pub fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
#[must_use]
pub fn message(&self) -> &str {
&self.message
}
}
impl std::fmt::Display for EffectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "effect backend failure: {}", self.message)
}
}
impl std::error::Error for EffectError {}