cratestack_axum/rpc/
model_feed.rs1use std::future::Future;
21use std::pin::Pin;
22
23use cratestack_core::{
24 BackplaneMessage, BackplaneReceiver, CratestackError, CratestackEventEnvelope, ModelEvent,
25 SubscriptionStream,
26};
27use futures_util::Stream;
28use futures_util::stream::{self, StreamExt};
29use serde::Serialize;
30use serde::de::DeserializeOwned;
31
32pub(crate) fn lagged() -> CratestackError {
34 CratestackError::Unavailable("subscription lagged".to_owned())
35}
36
37fn interrupted() -> CratestackError {
38 CratestackError::Unavailable("subscription interrupted".to_owned())
39}
40
41pub struct FeedEvent<T> {
46 pub event: ModelEvent<T>,
47 pub before: Option<T>,
48}
49
50impl<T: DeserializeOwned> TryFrom<CratestackEventEnvelope> for FeedEvent<T> {
51 type Error = CratestackError;
52
53 fn try_from(mut envelope: CratestackEventEnvelope) -> Result<Self, Self::Error> {
54 let before = envelope
55 .before
56 .take()
57 .map(serde_json::from_value)
58 .transpose()
59 .map_err(|error| {
60 CratestackError::Codec(format!("failed to decode event pre-image: {error}"))
61 })?;
62 Ok(Self {
63 event: envelope.try_into()?,
64 before,
65 })
66 }
67}
68
69pub fn model_events<T>(
73 receiver: BackplaneReceiver,
74) -> impl Stream<Item = Result<FeedEvent<T>, CratestackError>> + Send + 'static
75where
76 T: DeserializeOwned + Send + 'static,
77{
78 receiver.map(|message| match message {
79 BackplaneMessage::Payload(json) => serde_json::from_str::<CratestackEventEnvelope>(&json)
80 .map_err(|error| CratestackError::Codec(format!("unreadable model event: {error}")))
81 .and_then(FeedEvent::try_from),
82 BackplaneMessage::Lagged => Err(lagged()),
83 BackplaneMessage::Resync => Err(interrupted()),
84 })
85}
86
87pub async fn route_event<T, M, R, Fut>(
100 feed: FeedEvent<T>,
101 wanted: bool,
102 matches: M,
103 readable: R,
104) -> Result<Option<ModelEvent<T>>, CratestackError>
105where
106 M: Fn(&T) -> bool,
107 R: Fn(&T) -> Fut,
108 Fut: Future<Output = Result<bool, CratestackError>>,
109{
110 let FeedEvent { mut event, before } = feed;
111 if !wanted {
112 return Ok(None);
113 }
114 if matches(&event.data) && readable(&event.data).await? {
116 return Ok(Some(event));
117 }
118 let Some(before) = before else {
119 return Ok(None);
120 };
121 if !(matches(&before) && readable(&before).await?) {
122 return Ok(None);
123 }
124 event.data = before;
125 event.matches = false;
126 Ok(Some(event))
127}
128
129pub fn model_feed<I, O, S, F, Fut>(items: S, route: F) -> SubscriptionStream<serde_json::Value>
133where
134 O: Serialize + Send + 'static,
135 I: Send + 'static,
136 S: Stream<Item = Result<I, CratestackError>> + Send + 'static,
137 F: FnMut(I) -> Fut + Send + 'static,
138 Fut: Future<Output = Result<Option<O>, CratestackError>> + Send + 'static,
139{
140 let state: (Pin<Box<S>>, F, bool) = (Box::pin(items), route, false);
141 Box::pin(stream::unfold(
142 state,
143 |(mut items, mut route, ended)| async move {
144 if ended {
145 return None;
146 }
147 loop {
148 let item = match items.next().await {
149 Some(Ok(item)) => item,
150 Some(Err(error)) => return Some((Err(error), (items, route, true))),
151 None => return Some((Err(interrupted()), (items, route, true))),
152 };
153 let verdict = match route(item).await {
154 Ok(Some(event)) => serde_json::to_value(&event).map_err(|error| {
155 CratestackError::Codec(format!("failed to encode a model event: {error}"))
156 }),
157 Ok(None) => continue,
158 Err(error) => {
159 tracing::warn!(
160 target: "cratestack",
161 %error,
162 "subscription read policy undecided; ending the stream",
163 );
164 Err(error)
165 }
166 };
167 let ended = verdict.is_err();
168 return Some((verdict, (items, route, ended)));
169 }
170 },
171 ))
172}
173
174#[cfg(test)]
175mod tests;