leptos_spin/
response.rs

1use bytes::Bytes;
2use futures::{Stream, StreamExt};
3use leptos::server_fn::error::{
4    ServerFnError, ServerFnErrorErr, ServerFnErrorSerde, SERVER_FN_ERROR_HEADER,
5};
6use leptos::server_fn::response::Res;
7use spin_sdk::http::Headers;
8use std::pin::Pin;
9use std::{
10    fmt::{Debug, Display},
11    str::FromStr,
12};
13use typed_builder::TypedBuilder;
14/// This is here because the orphan rule does not allow us to implement it on IncomingRequest with
15/// the generic error. So we have to wrap it to make it happy
16pub struct SpinResponse(pub SpinResponseParts);
17
18#[derive(TypedBuilder)]
19pub struct SpinResponseParts {
20    pub status_code: u16,
21    pub headers: Headers,
22    pub body: SpinBody,
23}
24
25/// We either can return a fairly simple Box type for normal bodies or a Stream for Streaming
26/// server functions
27pub enum SpinBody {
28    Plain(Vec<u8>),
29    Streaming(Pin<Box<dyn Stream<Item = Result<Bytes, Box<dyn std::error::Error>>> + Send>>),
30}
31impl<CustErr> Res<CustErr> for SpinResponse
32where
33    CustErr: Send + Sync + Debug + FromStr + Display + 'static,
34{
35    fn try_from_string(content_type: &str, data: String) -> Result<Self, ServerFnError<CustErr>> {
36        let headers =
37            Headers::from_list(&[("Content-Type".to_string(), content_type.as_bytes().to_vec())])
38                .expect("Failed to create Headers from String Response Input");
39        let parts = SpinResponseParts::builder()
40            .status_code(200)
41            .headers(headers)
42            .body(SpinBody::Plain(data.into()))
43            .build();
44        Ok(SpinResponse(parts))
45    }
46
47    fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, ServerFnError<CustErr>> {
48        let headers = Headers::from_list(&[("Content-Type".to_string(), content_type.into())])
49            .expect("Failed to create Headers from Bytes Response Input");
50        let parts = SpinResponseParts::builder()
51            .status_code(200)
52            .headers(headers)
53            .body(SpinBody::Plain(data.into()))
54            .build();
55        Ok(SpinResponse(parts))
56    }
57
58    fn try_from_stream(
59        content_type: &str,
60        data: impl Stream<Item = Result<Bytes, ServerFnError<CustErr>>> + Send + 'static,
61    ) -> Result<Self, ServerFnError<CustErr>> {
62        let body = data.map(|n| {
63            n.map_err(ServerFnErrorErr::from)
64                .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
65        });
66
67        let headers = Headers::from_list(&[("Content-Type".to_string(), content_type.into())])
68            .expect("Failed to create Headers from Stream Response Input");
69        let parts = SpinResponseParts::builder()
70            .status_code(200)
71            .headers(headers)
72            .body(SpinBody::Streaming(Box::pin(body)))
73            .build();
74        Ok(SpinResponse(parts))
75    }
76
77    fn error_response(path: &str, err: &ServerFnError<CustErr>) -> Self {
78        let headers = Headers::from_list(&[(SERVER_FN_ERROR_HEADER.to_string(), path.into())])
79            .expect("Failed to create Error Response. This should be impossible");
80        let parts = SpinResponseParts::builder()
81            .status_code(500)
82            .headers(headers)
83            .body(SpinBody::Plain(
84                err.ser().unwrap_or_else(|_| err.to_string()).into(),
85            ))
86            .build();
87        SpinResponse(parts)
88    }
89
90    fn redirect(&mut self, _path: &str) {
91        //TODO: Enabling these seems to override location header
92        // not sure what's causing that
93        //let res_options = expect_context::<ResponseOptions>();
94        //res_options.insert_header("Location", path);
95        //res_options.set_status(302);
96    }
97}