server_fn/response/
generic.rs

1//! This module uses platform-agnostic abstractions
2//! allowing users to run server functions on a wide range of
3//! platforms.
4//!
5//! The crates in use in this crate are:
6//!
7//! * `bytes`: platform-agnostic manipulation of bytes.
8//! * `http`: low-dependency HTTP abstractions' *front-end*.
9//!
10//! # Users
11//!
12//! * `wasm32-wasip*` integration crate `leptos_wasi` is using this
13//!   crate under the hood.
14
15use super::{Res, TryRes};
16use crate::error::{
17    FromServerFnError, IntoAppError, ServerFnErrorErr, ServerFnErrorWrapper,
18    SERVER_FN_ERROR_HEADER,
19};
20use bytes::Bytes;
21use futures::{Stream, TryStreamExt};
22use http::{header, HeaderValue, Response, StatusCode};
23use std::pin::Pin;
24use throw_error::Error;
25
26/// The Body of a Response whose *execution model* can be
27/// customised using the variants.
28pub enum Body {
29    /// The response body will be written synchronously.
30    Sync(Bytes),
31
32    /// The response body will be written asynchronously,
33    /// this execution model is also known as
34    /// "streaming".
35    Async(Pin<Box<dyn Stream<Item = Result<Bytes, Error>> + Send + 'static>>),
36}
37
38impl From<String> for Body {
39    fn from(value: String) -> Self {
40        Body::Sync(Bytes::from(value))
41    }
42}
43
44impl<E> TryRes<E> for Response<Body>
45where
46    E: Send + Sync + FromServerFnError,
47{
48    fn try_from_string(content_type: &str, data: String) -> Result<Self, E> {
49        let builder = http::Response::builder();
50        builder
51            .status(200)
52            .header(http::header::CONTENT_TYPE, content_type)
53            .body(data.into())
54            .map_err(|e| {
55                ServerFnErrorErr::Response(e.to_string()).into_app_error()
56            })
57    }
58
59    fn try_from_bytes(content_type: &str, data: Bytes) -> Result<Self, E> {
60        let builder = http::Response::builder();
61        builder
62            .status(200)
63            .header(http::header::CONTENT_TYPE, content_type)
64            .body(Body::Sync(data))
65            .map_err(|e| {
66                ServerFnErrorErr::Response(e.to_string()).into_app_error()
67            })
68    }
69
70    fn try_from_stream(
71        content_type: &str,
72        data: impl Stream<Item = Result<Bytes, E>> + Send + 'static,
73    ) -> Result<Self, E> {
74        let builder = http::Response::builder();
75        builder
76            .status(200)
77            .header(http::header::CONTENT_TYPE, content_type)
78            .body(Body::Async(Box::pin(
79                data.map_err(ServerFnErrorWrapper).map_err(Error::from),
80            )))
81            .map_err(|e| {
82                ServerFnErrorErr::Response(e.to_string()).into_app_error()
83            })
84    }
85}
86
87impl Res for Response<Body> {
88    fn error_response(path: &str, err: String) -> Self {
89        Response::builder()
90            .status(http::StatusCode::INTERNAL_SERVER_ERROR)
91            .header(SERVER_FN_ERROR_HEADER, path)
92            .body(err.into())
93            .unwrap()
94    }
95
96    fn redirect(&mut self, path: &str) {
97        if let Ok(path) = HeaderValue::from_str(path) {
98            self.headers_mut().insert(header::LOCATION, path);
99            *self.status_mut() = StatusCode::FOUND;
100        }
101    }
102}