Skip to main content

rustolio_utils/http/response/
mod.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11#[cfg(not(target_arch = "wasm32"))]
12mod builder;
13
14use bytes::Bytes;
15
16use crate::bytes::IntoUtf8Bytes;
17#[cfg(not(target_arch = "wasm32"))]
18use crate::threadsafe;
19
20use super::{Error, HeaderName, HeaderValue, Incoming, Outgoing, Result, StatusCode, Version};
21
22#[cfg(not(target_arch = "wasm32"))]
23pub use builder::Builder;
24
25pub struct Response<B = Outgoing>(
26    #[cfg(target_arch = "wasm32")] web_sys::Response,
27    #[cfg(target_arch = "wasm32")] B,
28    #[cfg(not(target_arch = "wasm32"))] http::Response<B>,
29);
30
31impl Response<Incoming> {
32    #[cfg(not(target_arch = "wasm32"))]
33    pub fn from_inner(res: http::Response<Incoming>) -> Self {
34        Self(res)
35    }
36    #[cfg(target_arch = "wasm32")]
37    pub fn from_inner(res: web_sys::Response) -> Self {
38        Self(res, Incoming)
39    }
40}
41
42impl Response<Outgoing> {
43    #[cfg(not(target_arch = "wasm32"))]
44    pub fn into_inner(self) -> http::Response<Outgoing> {
45        self.0
46    }
47}
48
49impl Response<()> {
50    #[cfg(not(target_arch = "wasm32"))]
51    pub fn builder() -> Builder<()> {
52        Builder::new()
53    }
54}
55
56impl<B> Response<B> {
57    pub fn status(&self) -> StatusCode {
58        #[cfg(target_arch = "wasm32")]
59        {
60            StatusCode::try_from(self.0.status())
61                .expect("Response must contain a valid status code")
62        }
63        #[cfg(not(target_arch = "wasm32"))]
64        {
65            self.0.status()
66        }
67    }
68
69    #[cfg(not(target_arch = "wasm32"))]
70    pub fn header(&self, key: impl IntoUtf8Bytes) -> Option<&HeaderValue> {
71        let key = key.into();
72        let key: http::HeaderName = key.to_vec().try_into().ok()?;
73        self.0.headers().get(&key)
74    }
75
76    pub fn is_success(&self) -> bool {
77        self.status().is_success()
78    }
79
80    pub fn body(&self) -> &B {
81        #[cfg(target_arch = "wasm32")]
82        {
83            &self.1
84        }
85        #[cfg(not(target_arch = "wasm32"))]
86        {
87            self.0.body()
88        }
89    }
90
91    pub fn into_body(self) -> B {
92        #[cfg(target_arch = "wasm32")]
93        {
94            self.1
95        }
96        #[cfg(not(target_arch = "wasm32"))]
97        {
98            self.0.into_body()
99        }
100    }
101}
102
103#[cfg(target_arch = "wasm32")]
104impl Response<Incoming> {
105    pub async fn text(self) -> Result<Response<String>> {
106        let text = wasm_bindgen_futures::JsFuture::from(self.0.text().map_err(|_| Error::Recv)?)
107            .await
108            .map_err(|_| Error::Recv)?;
109        let text = text.try_into().map_err(|_| Error::Parse)?;
110        Ok(Response(self.0, text))
111    }
112
113    pub async fn json<T: serde::de::DeserializeOwned>(self) -> Result<Response<T>> {
114        let json = wasm_bindgen_futures::JsFuture::from(self.0.json().map_err(|_| Error::Recv)?)
115            .await
116            .map_err(|_| Error::Recv)?;
117        let json = serde_wasm_bindgen::from_value(json).map_err(|_| Error::Parse)?;
118        Ok(Response(self.0, json))
119    }
120
121    // TODO: Decode using the stream instead of collecting all bytes first
122    pub async fn encoded<T: crate::prelude::Decode>(self) -> Result<Response<T>> {
123        self.bytes().await.and_then(|b| {
124            let decoded: T =
125                crate::bytes::encoding::decode_from_bytes(b.1).map_err(|_| Error::Parse)?;
126            Ok(Response(b.0, decoded))
127        })
128    }
129
130    pub async fn bytes(self) -> Result<Response<Bytes>> {
131        let bytes =
132            wasm_bindgen_futures::JsFuture::from(self.0.array_buffer().map_err(|_| Error::Recv)?)
133                .await
134                .map_err(|_| Error::Recv)?;
135        let bytes = Bytes::from_owner(js_sys::Uint8Array::new(&bytes).to_vec());
136        Ok(Response(self.0, bytes))
137    }
138}
139
140#[cfg(not(target_arch = "wasm32"))]
141impl<B> Response<B>
142where
143    B: hyper::body::Body<Error: threadsafe::Error>,
144{
145    pub async fn text(self) -> Result<Response<String>> {
146        use http_body_util::BodyExt;
147
148        let (parts, body) = self.0.into_parts();
149
150        let body = body.collect().await.map_err(Error::body)?;
151        let Ok(text) = String::from_utf8(body.to_bytes().to_vec()) else {
152            return Err(Error::InvalidType);
153        };
154
155        Ok(Response(http::Response::from_parts(parts, text)))
156    }
157
158    pub async fn json<T: serde::de::DeserializeOwned>(self) -> Result<Response<T>> {
159        use http_body_util::BodyExt;
160
161        let Some(ty) = self.header(HeaderName::CONTENT_TYPE) else {
162            return Err(Error::InvalidType);
163        };
164        if !ty
165            .to_str()
166            .map_err(|_| Error::InvalidType)?
167            .starts_with("application/json")
168        {
169            return Err(Error::InvalidType);
170        }
171
172        let (parts, body) = self.0.into_parts();
173
174        let body = body.collect().await.map_err(Error::body)?;
175        let Ok(json) = serde_json::from_slice(&body.to_bytes()) else {
176            return Err(Error::InvalidType);
177        };
178
179        Ok(Response(http::Response::from_parts(parts, json)))
180    }
181
182    // TODO: Decode using the stream instead of collecting all bytes first
183    pub async fn encoded<T: crate::prelude::Decode>(self) -> Result<Response<T>> {
184        self.bytes().await.and_then(|b| {
185            let (parts, body) = b.0.into_parts();
186
187            let decoded: T =
188                crate::bytes::encoding::decode_from_bytes(body).map_err(Error::body)?;
189
190            Ok(Response(http::Response::from_parts(parts, decoded)))
191        })
192    }
193
194    pub async fn bytes(self) -> Result<Response<Bytes>> {
195        use http_body_util::BodyExt;
196
197        let (parts, body) = self.0.into_parts();
198
199        let body = body.collect().await.map_err(Error::body)?;
200        let bytes = body.to_bytes();
201
202        Ok(Response(http::Response::from_parts(parts, bytes)))
203    }
204}