mini_serve/response.rs
1use hyper::header::HeaderValue;
2use hyper::{Response, StatusCode};
3use http_body_util::combinators::BoxBody;
4use http_body_util::{BodyExt, Full};
5use hyper::body::Bytes;
6use serde::Serialize;
7
8use crate::error::ServeError;
9use crate::handler::ResponseBody;
10
11/// Build a JSON response with the given status code and serializable value.
12///
13/// Sets `Content-Type: application/json` and `Content-Length` headers.
14/// Returns `500 Internal Server Error` if serialization fails.
15pub fn json<T: Serialize>(status: StatusCode, value: &T) -> Result<Response<ResponseBody>, ServeError> {
16 let body = serde_json::to_string(value)
17 .map_err(|_| ServeError::new(500, "failed to serialize response"))?;
18 let len = body.len();
19 let mut resp = Response::new(BoxBody::new(
20 Full::new(Bytes::from(body)).map_err(|never: std::convert::Infallible| match never {}),
21 ));
22 *resp.status_mut() = status;
23
24 // Const `HeaderName`s and a static value: a `&str` here would be re-parsed
25 // into a `HeaderName` on every response, which showed up in the profile as
26 // `header::name::parse_hdr`.
27 //
28 // `insert`, not `Response::builder().header(…)`, which routes to `try_append` and
29 // scans the map for an existing entry of that name — 4.58% of this crate's profile
30 // against axum's 1.82%, for a map we just created and know to be empty.
31 //
32 // This is safe here and is *not* a technique to apply elsewhere: `insert` replaces
33 // where `append` adds, which is invisible for `Content-Type` and `Content-Length`
34 // (single-valued by definition) and destructive for headers that may legitimately
35 // repeat — `Set-Cookie`, and above all `Vary`, where dropping a value is a
36 // cache-poisoning vector. `cors.rs` already emits `vary: origin`. Any other call
37 // site needs its own analysis of whether that header can repeat.
38 let headers = resp.headers_mut();
39 headers.insert(hyper::header::CONTENT_TYPE, HeaderValue::from_static("application/json"));
40 headers.insert(hyper::header::CONTENT_LENGTH, len.into());
41 Ok(resp)
42}