use alloc::string::ToString;
use crate::{AimDb, DbError, DbResult};
use futures_core::Stream;
use futures_util::stream::unfold;
pub(crate) fn stream_record_updates(
db: &AimDb,
record_key: &str,
) -> DbResult<impl Stream<Item = (crate::session::Payload, u64)> + Send + 'static> {
let inner = db.inner();
let id = inner
.resolve_str(record_key)
.ok_or_else(|| DbError::record_key_not_found(record_key.to_string()))?;
let record = inner
.storage(id)
.ok_or(DbError::InvalidRecordId { id: id.raw() })?;
let reader = crate::buffer::JsonReader::new(
record
.json_access()
.ok_or_else(|| {
DbError::runtime_error(alloc::format!(
"Record '{record_key}' does not support JSON remote access"
))
})?
.subscribe_json()?,
);
let state = (reader, record_key.to_string(), 0_u64);
Ok(unfold(state, |(mut reader, key, mut skipped)| async move {
loop {
match reader.recv_json_bytes().await {
Ok(bytes) => {
let data = crate::session::Payload::from(bytes);
return Some(((data, skipped), (reader, key, 0)));
}
Err(DbError::BufferLagged { lag_count, .. }) => {
log_warn!(
"stream_record_updates: record '{}' subscription lagged by {} messages",
key,
lag_count
);
skipped = skipped.saturating_add(lag_count);
continue;
}
Err(DbError::BufferClosed { .. }) => return None,
Err(e) => {
log_error!(
"stream_record_updates: record '{}' terminating on error: {:?}",
key,
e
);
return None;
}
}
}
}))
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::buffer::{JsonBufferReader, JsonReader};
use core::task::{Context, Poll};
use futures_util::StreamExt;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
struct FakeReader {
step: Arc<AtomicUsize>,
}
impl JsonBufferReader for FakeReader {
fn poll_recv_json(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<Result<serde_json::Value, DbError>> {
let s = self.step.fetch_add(1, Ordering::SeqCst);
Poll::Ready(match s {
0 => Ok(serde_json::json!({"v": 1})),
1 => Err(DbError::BufferLagged {
buffer_name: "test".to_string(),
lag_count: 7,
}),
2 => Ok(serde_json::json!({"v": 2})),
_ => Err(DbError::BufferClosed {
buffer_name: "test".to_string(),
}),
})
}
fn try_recv_json(&mut self) -> Result<serde_json::Value, DbError> {
Err(DbError::BufferEmpty)
}
}
#[tokio::test]
async fn unfold_carries_lag_count_and_terminates_on_closed() {
let reader = JsonReader::new(Box::new(FakeReader {
step: Arc::new(AtomicUsize::new(0)),
}));
let stream = unfold((reader, 0_u64), |(mut reader, mut skipped)| async move {
loop {
match reader.recv_json_bytes().await {
Ok(bytes) => return Some(((bytes, skipped), (reader, 0))),
Err(DbError::BufferLagged { lag_count, .. }) => {
skipped = skipped.saturating_add(lag_count);
continue;
}
Err(DbError::BufferClosed { .. }) => return None,
Err(_) => return None,
}
}
});
let values: Vec<(Vec<u8>, u64)> = stream.collect().await;
assert_eq!(values.len(), 2);
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&values[0].0).unwrap(),
serde_json::json!({"v": 1})
);
assert_eq!(values[0].1, 0);
assert_eq!(
serde_json::from_slice::<serde_json::Value>(&values[1].0).unwrap(),
serde_json::json!({"v": 2})
);
assert_eq!(values[1].1, 7);
}
}