use std::{collections::HashMap, future::Future, pin::pin, sync::Arc};
use ed25519_dalek::VerifyingKey;
use eventsourcingdb::{
Client, EventCandidate,
error::ClientError,
request_options::{ReadEventMissingStrategy, ReadEventsOptions, ReadFromLatestEventOptions},
};
use futures_util::StreamExt;
use serde_json::Value;
use tokio::sync::oneshot;
use crate::{
Event, EventSchema, Failure, Permanent, Precondition, State, Transient, UpcastEvent,
command::write_preconditions,
state::{evolve_by_stored, from_latest_of},
};
mod error;
mod failure;
#[cfg(test)]
mod tests;
pub use error::{LoadError, RegisterSchemasError, WriteError};
use failure::{database_failure, is_schema_conflict, read_failure, schema_refusal, write_failure};
#[derive(Debug)]
pub struct Store {
client: Arc<Client>,
source: String,
verification: Verification,
}
#[derive(Debug)]
enum Verification {
Hash,
Nothing,
Signature(VerifyingKey),
}
#[derive(Debug)]
pub struct StoreBuilder {
client: Client,
source: String,
skips_hashes: bool,
verification_key: Option<VerifyingKey>,
}
impl StoreBuilder {
#[must_use]
pub fn without_hash_verification(mut self) -> Self {
self.skips_hashes = true;
self
}
#[must_use]
pub fn signature_verification(mut self, verification_key: VerifyingKey) -> Self {
self.verification_key = Some(verification_key);
self
}
#[must_use]
pub fn build(self) -> Store {
let verification = match (self.skips_hashes, self.verification_key) {
(true, Some(_)) => panic!(
"architecturekit: without_hash_verification contradicts signature_verification, which checks the hash as well"
),
(true, None) => Verification::Nothing,
(false, Some(key)) => Verification::Signature(key),
(false, None) => Verification::Hash,
};
Store {
client: Arc::new(self.client),
source: self.source,
verification,
}
}
}
#[derive(Debug)]
pub struct EventOn {
subject: String,
event_type: &'static str,
data: Result<Value, serde_json::Error>,
}
impl EventOn {
#[must_use]
pub fn new<E: Event>(subject: impl Into<String>, event: &E) -> Self {
Self {
subject: subject.into(),
event_type: E::EVENT_TYPE,
data: serde_json::to_value(event),
}
}
}
impl Store {
#[must_use]
pub fn builder(client: Client, source: impl Into<String>) -> StoreBuilder {
StoreBuilder {
client,
source: source.into(),
skips_hashes: false,
verification_key: None,
}
}
pub async fn load<S: State>(&self, subject: &str) -> Result<S, LoadError> {
Ok(self.fold::<S>(subject).await?.0)
}
pub(crate) async fn fold<S: State>(
&self,
subject: &str,
) -> Result<(S, Option<String>), LoadError> {
let doing = format!("reading {subject:?}");
let options = ReadEventsOptions {
from_latest_event: from_latest_of::<S>().map(|event_type| ReadFromLatestEventOptions {
if_event_is_missing: ReadEventMissingStrategy::ReadEverything,
subject,
ty: event_type,
}),
..ReadEventsOptions::default()
};
let events = self
.client
.read_events(subject, Some(options))
.await
.map_err(|error| LoadError::from(read_failure(error, &doing)))?;
let mut events = pin!(events);
let mut current = S::default();
let mut last_event_id = None;
while let Some(event) = events.next().await {
let event = event.map_err(|error| LoadError::from(read_failure(error, &doing)))?;
self.verify(&event)?;
last_event_id = Some(event.id().to_owned());
current = evolve_by_stored(current, UpcastEvent::from_stored(&event))?;
}
Ok((current, last_event_id))
}
fn verify(&self, event: &eventsourcingdb::Event) -> Result<(), Permanent> {
let verified = match &self.verification {
Verification::Signature(key) => event.verify_signature(key),
Verification::Hash => event.verify_hash(),
Verification::Nothing => Ok(()),
};
verified.map_err(|cause| {
Permanent::Unverified(Failure::new(
format!("event {} on {:?}", event.id(), event.subject()),
Some(Box::new(cause)),
))
})
}
pub async fn write(
&self,
events: Vec<EventOn>,
preconditions: Vec<Precondition>,
) -> Result<Vec<eventsourcingdb::Event>, WriteError> {
let preconditions = write_preconditions(preconditions)?;
let Some(first) = events.first() else {
return Ok(Vec::new());
};
let doing = format!(
"writing {} events, the first to {:?}",
events.len(),
first.subject
);
let candidates = events
.into_iter()
.enumerate()
.map(|(index, event)| self.candidate_for(index, event))
.collect::<Result<Vec<_>, _>>()?;
self.write_candidates(candidates, preconditions, doing)
.await
}
fn candidate_for(&self, index: usize, event: EventOn) -> Result<EventCandidate, Permanent> {
if event.subject.is_empty() {
return Err(Permanent::Other(Failure::new(
format!("event {index} of a write needs a subject"),
None,
)));
}
let data = event.data.map_err(|cause| {
Permanent::Other(Failure::new(
format!(
"refusing to write an event of type {:?} to {:?}, since its data can not be encoded as JSON",
event.event_type, event.subject,
),
Some(Box::new(cause)),
))
})?;
Ok(EventCandidate {
data,
source: self.source.clone(),
subject: event.subject,
ty: event.event_type.to_owned(),
traceinfo: None,
})
}
pub(crate) async fn write_candidates(
&self,
candidates: Vec<EventCandidate>,
preconditions: Vec<eventsourcingdb::Precondition>,
doing: String,
) -> Result<Vec<eventsourcingdb::Event>, WriteError> {
let client = Arc::clone(&self.client);
let written = detached(doing.clone(), async move {
client.write_events(candidates, preconditions).await
})
.await;
match written {
Some(Ok(written)) => Ok(written),
Some(Err(error)) => Err(write_failure(error, &doing).into()),
None => Err(WriteError::OutcomeUnknown(Failure::new(
format!("{doing}: the write ended without an answer"),
None,
))),
}
}
pub async fn register_schemas(
&self,
schemas: impl IntoIterator<Item = EventSchema>,
) -> Result<(), RegisterSchemasError> {
let given = collect_schemas(schemas)?;
let mut registered = self.read_registered_schemas().await?;
for schema in given {
let event_type = schema.event_type();
if !registered.contains_key(event_type) {
let doing = format!("registering schema for {event_type:?}");
let client = Arc::clone(&self.client);
let value = schema.schema().clone();
let refusal = match detached(doing.clone(), async move {
client
.register_event_schema(event_type, &value)
.await
.map(|_| ())
})
.await
{
Some(Ok(())) => continue,
Some(Err(refusal)) => refusal,
None => {
return Err(RegisterSchemasError::Transient(Transient::Other(
Failure::new(
format!("{doing}: the registration ended without an answer"),
None,
),
)));
}
};
if !is_schema_conflict(&refusal) {
return Err(database_failure(refusal, &doing).into());
}
registered = self.read_registered_schemas().await?;
if !registered.contains_key(event_type) {
return Err(schema_refusal(event_type, refusal).into());
}
}
if registered.get(event_type) != Some(schema.schema()) {
return Err(Permanent::Other(Failure::new(
format!(
"the schema of {event_type:?} differs from the registered one, which cannot change; \
introduce a new event type and an upcaster instead",
),
None,
))
.into());
}
}
Ok(())
}
async fn read_registered_schemas(
&self,
) -> Result<HashMap<String, Value>, RegisterSchemasError> {
let doing = "reading the registered schemas";
let event_types = self
.client
.read_event_types()
.await
.map_err(|error| RegisterSchemasError::from(read_failure(error, doing)))?;
let mut event_types = pin!(event_types);
let mut registered = HashMap::new();
while let Some(event_type) = event_types.next().await {
let event_type = event_type
.map_err(|error| RegisterSchemasError::from(read_failure(error, doing)))?;
if let Some(schema) = event_type.schema {
let _ = registered.insert(event_type.ty, schema);
}
}
Ok(registered)
}
}
fn collect_schemas(
schemas: impl IntoIterator<Item = EventSchema>,
) -> Result<Vec<EventSchema>, Permanent> {
let mut collected: Vec<EventSchema> = Vec::new();
for schema in schemas {
match collected
.iter()
.find(|earlier| earlier.event_type() == schema.event_type())
{
Some(earlier) if earlier.schema() == schema.schema() => {}
Some(_) => {
return Err(Permanent::Other(Failure::new(
format!(
"event type {:?} has two different schemas",
schema.event_type()
),
None,
)));
}
None => collected.push(schema),
}
}
Ok(collected)
}
async fn detached<T: Send + 'static>(
doing: String,
work: impl Future<Output = Result<T, ClientError>> + Send + 'static,
) -> Option<Result<T, ClientError>> {
let (sender, receiver) = oneshot::channel();
drop(tokio::spawn(async move {
let result = work.await;
if let Err(Err(error)) = sender.send(result) {
log::error!(
"architecturekit: {doing} failed after its caller had stopped waiting: {error}"
);
}
}));
receiver.await.ok()
}