actix_web_lab/
display_stream.rs1use std::{error::Error as StdError, fmt, io::Write as _};
2
3use actix_web::{
4 HttpResponse, Responder,
5 body::{BodyStream, MessageBody},
6};
7use bytes::{Bytes, BytesMut};
8use futures_util::{Stream, TryStreamExt as _};
9use pin_project_lite::pin_project;
10
11use crate::util::{InfallibleStream, MutWriter};
12
13pin_project! {
14 pub struct DisplayStream<S> {
40 #[pin]
42 stream: S,
43 }
44}
45
46impl<S> DisplayStream<S> {
47 pub fn new(stream: S) -> Self {
49 Self { stream }
50 }
51}
52
53impl<S> DisplayStream<S> {
54 pub fn new_infallible(stream: S) -> DisplayStream<InfallibleStream<S>> {
56 DisplayStream::new(InfallibleStream::new(stream))
57 }
58}
59
60impl<S, T, E> DisplayStream<S>
61where
62 S: Stream<Item = Result<T, E>>,
63 T: fmt::Display,
64 E: Into<Box<dyn StdError>> + 'static,
65{
66 pub fn into_body_stream(self) -> impl MessageBody {
68 BodyStream::new(self.into_chunk_stream())
69 }
70
71 pub fn into_responder(self) -> impl Responder
74 where
75 S: 'static,
76 T: 'static,
77 E: 'static,
78 {
79 HttpResponse::Ok()
80 .content_type(mime::TEXT_PLAIN_UTF_8)
81 .message_body(self.into_body_stream())
82 .unwrap()
83 }
84
85 pub fn into_chunk_stream(self) -> impl Stream<Item = Result<Bytes, E>> {
87 self.stream.map_ok(write_display)
88 }
89}
90
91fn write_display(item: impl fmt::Display) -> Bytes {
92 let mut buf = BytesMut::new();
93 let mut wrt = MutWriter(&mut buf);
94
95 writeln!(wrt, "{item}").unwrap();
96
97 buf.freeze()
98}
99
100#[cfg(test)]
101mod tests {
102 use std::error::Error as StdError;
103
104 use actix_web::body;
105 use futures_util::stream;
106
107 use super::*;
108
109 #[actix_web::test]
110 async fn serializes_into_body() {
111 let ndjson_body = DisplayStream::new_infallible(stream::iter([123, 789, 345, 901, 456]))
112 .into_body_stream();
113
114 let body_bytes = body::to_bytes(ndjson_body)
115 .await
116 .map_err(Into::<Box<dyn StdError>>::into)
117 .unwrap();
118
119 const EXP_BYTES: &str = "123\n\
120 789\n\
121 345\n\
122 901\n\
123 456\n";
124
125 assert_eq!(body_bytes, EXP_BYTES);
126 }
127}