1use std::{
2 error::Error,
3 future::Future,
4 io,
5 net::SocketAddr,
6 pin::Pin,
7 task::{Context, Poll},
8 time::Duration,
9};
10
11type ResponseFuture = Pin<
12 Box<
13 dyn Future<Output = Result<hyper::Response<Incoming>, hyper_util::client::legacy::Error>>
14 + Send
15 + Sync,
16 >,
17>;
18
19use bytes::{Buf, Bytes, BytesMut};
20use futures::{Stream, ready};
21use http_body_util::Full;
22use hyper::body::{Body, Incoming};
23use hyper_util::client::legacy::{Client, connect::HttpConnector};
24use klukai_types::api::{ChangeId, QueryEvent, TypedNotifyEvent, TypedQueryEvent};
25use pin_project_lite::pin_project;
26use serde::de::DeserializeOwned;
27use tokio::time::{Sleep, sleep};
28use tokio_util::{
29 codec::{Decoder, FramedRead, LinesCodecError},
30 io::StreamReader,
31};
32use tracing::error;
33use uuid::Uuid;
34
35pin_project! {
36 pub struct IoBodyStream {
37 #[pin]
38 body: Incoming
39 }
40}
41
42impl Stream for IoBodyStream {
43 type Item = io::Result<Bytes>;
44
45 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
46 let this = self.project();
47
48 match ready!(this.body.poll_frame(cx)) {
49 Some(Ok(frame)) => {
50 if let Ok(data) = frame.into_data() {
51 Poll::Ready(Some(Ok(data)))
52 } else {
53 Poll::Pending
55 }
56 }
57 Some(Err(e)) => {
58 let io_err = match e
59 .source()
60 .and_then(|source| source.downcast_ref::<io::Error>())
61 {
62 Some(io_err) => io::Error::from(io_err.kind()),
63 None => io::Error::other(e),
64 };
65 Poll::Ready(Some(Err(io_err)))
66 }
67 None => Poll::Ready(None),
68 }
69 }
70}
71
72type IoBodyStreamReader = StreamReader<IoBodyStream, Bytes>;
73type FramedBody = FramedRead<IoBodyStreamReader, LinesBytesCodec>;
74
75pub struct SubscriptionStream<T> {
76 id: Uuid,
77 hash: Option<String>,
78 client: Client<HttpConnector, Full<Bytes>>,
79 api_addr: SocketAddr,
80 observed_eoq: bool,
81 last_change_id: Option<ChangeId>,
82 stream: Option<FramedBody>,
83 backoff: Option<Pin<Box<Sleep>>>,
84 backoff_count: u32,
85 response: Option<ResponseFuture>,
86 _deser: std::marker::PhantomData<T>,
87}
88
89#[derive(Debug, thiserror::Error)]
90pub enum SubscriptionError {
91 #[error(transparent)]
92 Io(#[from] io::Error),
93 #[error(transparent)]
94 Http(#[from] hyper::http::Error),
95 #[error(transparent)]
96 Deserialize(#[from] serde_json::Error),
97 #[error("missed a change (expected: {expected}, got: {got}), inconsistent state")]
98 MissedChange { expected: ChangeId, got: ChangeId },
99 #[error("max line length exceeded")]
100 MaxLineLengthExceeded,
101 #[error("initial query never finished")]
102 UnfinishedQuery,
103 #[error("max retry attempts exceeded")]
104 MaxRetryAttempts,
105}
106
107impl<T> SubscriptionStream<T>
108where
109 T: DeserializeOwned + Unpin,
110{
111 pub fn new(
112 id: Uuid,
113 hash: Option<String>,
114 client: Client<HttpConnector, Full<Bytes>>,
115 api_addr: SocketAddr,
116 body: Incoming,
117 change_id: Option<ChangeId>,
118 ) -> Self {
119 Self {
120 id,
121 hash,
122 client,
123 api_addr,
124 observed_eoq: change_id.is_some(),
125 last_change_id: change_id,
126 stream: Some(FramedRead::new(
127 StreamReader::new(IoBodyStream { body }),
128 LinesBytesCodec::default(),
129 )),
130 backoff: None,
131 backoff_count: 0,
132 response: None,
133 _deser: Default::default(),
134 }
135 }
136
137 pub fn id(&self) -> Uuid {
138 self.id
139 }
140
141 pub fn hash(&self) -> Option<&str> {
142 self.hash.as_deref()
143 }
144
145 pub fn api_addr(&self) -> SocketAddr {
146 self.api_addr
147 }
148
149 fn poll_stream(
150 mut self: Pin<&mut Self>,
151 cx: &mut Context<'_>,
152 ) -> Poll<Option<Result<TypedQueryEvent<T>, SubscriptionError>>> {
153 let stream = loop {
154 match self.stream.as_mut() {
155 None => match ready!(self.as_mut().poll_request(cx)) {
156 Ok(stream) => {
157 self.stream = Some(stream);
158 }
159 Err(e) => return Poll::Ready(Some(Err(e))),
160 },
161 Some(stream) => {
162 break stream;
163 }
164 }
165 };
166
167 let res = ready!(Pin::new(stream).poll_next(cx));
168 match res {
169 Some(Ok(b)) => match serde_json::from_slice(&b) {
170 Ok(evt) => {
171 if let TypedQueryEvent::EndOfQuery { change_id, .. } = &evt {
172 self.handle_eoq(*change_id);
173 }
174
175 if let TypedQueryEvent::Change(_, _, _, change_id) = &evt
176 && let Err(e) = self.handle_change(*change_id)
177 {
178 return Poll::Ready(Some(Err(e)));
179 }
180
181 Poll::Ready(Some(Ok(evt)))
182 }
183 Err(deser_err) => {
184 if let Ok(evt) = serde_json::from_slice::<QueryEvent>(&b) {
186 if let TypedQueryEvent::EndOfQuery { change_id, .. } = &evt {
187 self.handle_eoq(*change_id);
188 }
189
190 if let TypedQueryEvent::Change(_, _, _, change_id) = &evt
191 && let Err(e) = self.handle_change(*change_id)
192 {
193 return Poll::Ready(Some(Err(e)));
194 }
195 }
196
197 Poll::Ready(Some(Err(deser_err.into())))
199 }
200 },
201 Some(Err(e)) => match e {
202 LinesCodecError::MaxLineLengthExceeded => {
203 Poll::Ready(Some(Err(SubscriptionError::MaxLineLengthExceeded)))
204 }
205 LinesCodecError::Io(io_err) => Poll::Ready(Some(Err(io_err.into()))),
206 },
207 None => Poll::Ready(None),
208 }
209 }
210
211 fn handle_eoq(&mut self, change_id: Option<ChangeId>) {
212 self.observed_eoq = true;
213 self.last_change_id = change_id;
214 }
215
216 fn handle_change(&mut self, change_id: ChangeId) -> Result<(), SubscriptionError> {
217 match self.last_change_id {
218 Some(id) if id + 1 != change_id => {
219 return Err(SubscriptionError::MissedChange {
220 expected: id + 1,
221 got: change_id,
222 });
223 }
224 _ => (),
225 }
226
227 self.last_change_id = Some(change_id);
228
229 Ok(())
230 }
231
232 fn poll_request(
233 mut self: Pin<&mut Self>,
234 cx: &mut Context<'_>,
235 ) -> Poll<Result<FramedBody, SubscriptionError>> {
236 loop {
237 if let Some(res_fut) = self.response.as_mut() {
238 let res = ready!(Pin::new(res_fut).poll(cx));
240
241 self.response = None;
243
244 return match res {
245 Ok(res) => Poll::Ready(Ok(FramedRead::new(
246 StreamReader::new(IoBodyStream {
247 body: res.into_body(),
248 }),
249 LinesBytesCodec::default(),
250 ))),
251 Err(e) => {
252 let io_err = match e
253 .source()
254 .and_then(|source| source.downcast_ref::<io::Error>())
255 {
256 Some(io_err) => io::Error::from(io_err.kind()),
257 None => io::Error::other(e),
258 };
259 Poll::Ready(Err(io_err.into()))
260 }
261 };
262 } else if self.observed_eoq {
263 let req = hyper::Request::builder()
264 .method(hyper::Method::GET)
265 .uri(format!(
266 "http://{}/v1/subscriptions/{}?from={}",
267 self.api_addr,
268 self.id,
269 self.last_change_id.unwrap_or_default()
270 ))
271 .header(hyper::header::ACCEPT, "application/json")
272 .body(Full::new(Bytes::new()))?;
273
274 let response = Box::pin(self.client.request(req));
275 self.response = Some(response);
276 } else {
278 return Poll::Ready(Err(SubscriptionError::UnfinishedQuery));
279 }
280 }
281 }
282}
283
284impl<T> Stream for SubscriptionStream<T>
285where
286 T: DeserializeOwned + Unpin,
287{
288 type Item = Result<TypedQueryEvent<T>, SubscriptionError>;
289
290 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
291 if let Some(backoff) = self.backoff.as_mut() {
293 ready!(backoff.as_mut().poll(cx));
294 self.backoff = None;
295 }
296
297 let io_err = match ready!(self.as_mut().poll_stream(cx)) {
298 Some(Err(SubscriptionError::Io(io_err))) => io_err,
299 other => {
300 self.backoff_count = 0;
301 return Poll::Ready(other);
302 }
303 };
304
305 self.stream = None;
307
308 if self.backoff_count >= 10 {
309 return Poll::Ready(Some(Err(SubscriptionError::MaxRetryAttempts)));
310 }
311
312 error!("encountered a stream IO error: {io_err}, retrying in a bit");
313
314 let mut backoff = Box::pin(sleep(Duration::from_secs(1)));
315
316 _ = backoff.as_mut().poll(cx);
318
319 self.backoff = Some(backoff);
321
322 self.backoff_count += 1;
323
324 Poll::Pending
325 }
326}
327
328pub struct UpdatesStream<T> {
329 id: Uuid,
330 stream: FramedBody,
331 _deser: std::marker::PhantomData<T>,
332}
333
334#[derive(Debug, thiserror::Error)]
335pub enum UpdatesError {
336 #[error(transparent)]
337 Io(#[from] io::Error),
338 #[error(transparent)]
339 Deserialize(#[from] serde_json::Error),
340 #[error("max line length exceeded")]
341 MaxLineLengthExceeded,
342}
343
344impl<T> UpdatesStream<T>
345where
346 T: DeserializeOwned + Unpin,
347{
348 pub fn new(id: Uuid, body: Incoming) -> Self {
349 Self {
350 id,
351 stream: FramedRead::new(
352 StreamReader::new(IoBodyStream { body }),
353 LinesBytesCodec::default(),
354 ),
355 _deser: Default::default(),
356 }
357 }
358
359 pub fn id(&self) -> Uuid {
360 self.id
361 }
362}
363
364impl<T> Stream for UpdatesStream<T>
365where
366 T: DeserializeOwned + Unpin,
367{
368 type Item = Result<TypedNotifyEvent<T>, UpdatesError>;
369
370 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
371 let res = ready!(Pin::new(&mut self.stream).poll_next(cx));
372 match res {
373 Some(Ok(b)) => match serde_json::from_slice(&b) {
374 Ok(evt) => Poll::Ready(Some(Ok(evt))),
375 Err(e) => Poll::Ready(Some(Err(e.into()))),
376 },
377 Some(Err(e)) => match e {
378 LinesCodecError::MaxLineLengthExceeded => {
379 Poll::Ready(Some(Err(UpdatesError::MaxLineLengthExceeded)))
380 }
381 LinesCodecError::Io(io_err) => Poll::Ready(Some(Err(io_err.into()))),
382 },
383 None => Poll::Ready(None),
384 }
385 }
386}
387
388pub struct QueryStream<T> {
389 stream: FramedBody,
390 _deser: std::marker::PhantomData<T>,
391}
392
393#[derive(Debug, thiserror::Error)]
394pub enum QueryError {
395 #[error(transparent)]
396 Io(#[from] io::Error),
397 #[error(transparent)]
398 Deserialize(#[from] serde_json::Error),
399 #[error("max line length exceeded")]
400 MaxLineLengthExceeded,
401}
402
403impl<T> QueryStream<T>
404where
405 T: DeserializeOwned + Unpin,
406{
407 pub fn new(body: Incoming) -> Self {
408 Self {
409 stream: FramedRead::new(
410 StreamReader::new(IoBodyStream { body }),
411 LinesBytesCodec::default(),
412 ),
413 _deser: Default::default(),
414 }
415 }
416}
417
418impl<T> Stream for QueryStream<T>
419where
420 T: DeserializeOwned + Unpin,
421{
422 type Item = Result<TypedQueryEvent<T>, QueryError>;
423
424 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
425 match ready!(Pin::new(&mut self.stream).poll_next(cx)) {
426 Some(Ok(b)) => match serde_json::from_slice(&b) {
427 Ok(evt) => Poll::Ready(Some(Ok(evt))),
428 Err(e) => Poll::Ready(Some(Err(e.into()))),
429 },
430 Some(Err(e)) => match e {
431 LinesCodecError::MaxLineLengthExceeded => {
432 Poll::Ready(Some(Err(QueryError::MaxLineLengthExceeded)))
433 }
434 LinesCodecError::Io(io_err) => Poll::Ready(Some(Err(io_err.into()))),
435 },
436 None => Poll::Ready(None),
437 }
438 }
439}
440
441pub struct LinesBytesCodec {
442 next_index: usize,
449
450 max_length: usize,
453
454 is_discarding: bool,
457}
458
459impl Default for LinesBytesCodec {
460 fn default() -> Self {
470 LinesBytesCodec {
471 next_index: 0,
472 max_length: usize::MAX,
473 is_discarding: false,
474 }
475 }
476}
477
478impl Decoder for LinesBytesCodec {
479 type Item = BytesMut;
480 type Error = LinesCodecError;
481
482 fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, LinesCodecError> {
483 loop {
484 let read_to = std::cmp::min(self.max_length.saturating_add(1), buf.len());
487
488 let newline_offset = buf[self.next_index..read_to]
489 .iter()
490 .position(|b| *b == b'\n');
491
492 match (self.is_discarding, newline_offset) {
493 (true, Some(offset)) => {
494 buf.advance(offset + self.next_index + 1);
498 self.is_discarding = false;
499 self.next_index = 0;
500 }
501 (true, None) => {
502 buf.advance(read_to);
506 self.next_index = 0;
507 if buf.is_empty() {
508 return Ok(None);
509 }
510 }
511 (false, Some(offset)) => {
512 let newline_index = offset + self.next_index;
514 self.next_index = 0;
515 let mut line = buf.split_to(newline_index + 1);
516 line.truncate(line.len() - 1);
517 without_carriage_return(&mut line);
518 return Ok(Some(line));
519 }
520 (false, None) if buf.len() > self.max_length => {
521 self.is_discarding = true;
525 return Err(LinesCodecError::MaxLineLengthExceeded);
526 }
527 (false, None) => {
528 self.next_index = read_to;
531 return Ok(None);
532 }
533 }
534 }
535 }
536
537 fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<BytesMut>, LinesCodecError> {
538 Ok(match self.decode(buf)? {
539 Some(frame) => Some(frame),
540 None => {
541 if buf.is_empty() || buf == &b"\r"[..] {
543 None
544 } else {
545 let mut line = buf.split_to(buf.len());
546 line.truncate(line.len() - 1);
547 without_carriage_return(&mut line);
548 self.next_index = 0;
549 Some(line)
550 }
551 }
552 })
553 }
554}
555
556fn without_carriage_return(s: &mut BytesMut) {
557 if let Some(&b'\r') = s.last() {
558 s.truncate(s.len() - 1);
559 }
560}