pub trait TextWriter {
type Options;
type Id;
type Error: core::error::Error + Send + Sync + 'static;
fn write_text(
&self,
text: &str,
options: Self::Options,
) -> impl core::future::Future<Output = Result<Self::Id, Self::Error>>;
}
pub trait Committable {
type Error: core::error::Error + Send + Sync + 'static;
fn commit(&self) -> impl core::future::Future<Output = Result<(), Self::Error>>;
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic, clippy::indexing_slicing)]
mod tests {
use super::*;
use std::sync::Mutex;
#[derive(Debug, thiserror::Error)]
#[error("test backend error")]
struct TestErr;
struct CountingStore {
pending: Mutex<Vec<String>>,
committed: Mutex<Vec<String>>,
}
impl CountingStore {
fn new() -> Self {
Self {
pending: Mutex::new(Vec::new()),
committed: Mutex::new(Vec::new()),
}
}
}
impl TextWriter for CountingStore {
type Options = ();
type Id = u64;
type Error = TestErr;
async fn write_text(&self, text: &str, _: ()) -> Result<u64, TestErr> {
let mut guard = self.pending.lock().map_err(|_| TestErr)?;
guard.push(text.to_owned());
Ok((guard.len() - 1) as u64)
}
}
impl Committable for CountingStore {
type Error = TestErr;
async fn commit(&self) -> Result<(), TestErr> {
let mut pending = self.pending.lock().map_err(|_| TestErr)?;
let mut committed = self.committed.lock().map_err(|_| TestErr)?;
committed.append(&mut pending);
Ok(())
}
}
async fn persist_all<S>(
store: &S,
lines: &[&str],
) -> Result<Vec<<S as TextWriter>::Id>, <S as TextWriter>::Error>
where
S: TextWriter<Options = ()> + Committable<Error = <S as TextWriter>::Error>,
{
let mut ids = Vec::with_capacity(lines.len());
for line in lines {
ids.push(store.write_text(line, ()).await?);
}
store.commit().await?;
Ok(ids)
}
#[test]
fn write_then_commit_moves_pending_to_committed() {
let store = CountingStore::new();
let ids = pollster::block_on(persist_all(&store, &["alpha", "beta", "gamma"])).unwrap();
assert_eq!(ids, vec![0, 1, 2]);
assert!(store.pending.lock().unwrap().is_empty());
assert_eq!(
store.committed.lock().unwrap().clone(),
vec!["alpha".to_owned(), "beta".to_owned(), "gamma".to_owned()],
);
}
#[test]
fn write_without_commit_keeps_pending() {
let store = CountingStore::new();
let id = pollster::block_on(store.write_text("orphan", ())).unwrap();
assert_eq!(id, 0);
assert_eq!(store.pending.lock().unwrap().len(), 1);
assert!(store.committed.lock().unwrap().is_empty());
}
}