Skip to main content

anvil_core/
response.rs

1//! Response builders. View rendering, redirects, JSON helpers.
2
3use axum::http::{HeaderMap, HeaderValue, StatusCode};
4use axum::response::{IntoResponse, Response};
5use serde::Serialize;
6
7/// A response that carries a Forge-rendered view body.
8pub struct ViewResponse {
9    pub status: StatusCode,
10    pub body: String,
11    pub headers: HeaderMap,
12}
13
14impl ViewResponse {
15    pub fn new(body: impl Into<String>) -> Self {
16        let mut headers = HeaderMap::new();
17        headers.insert(
18            axum::http::header::CONTENT_TYPE,
19            HeaderValue::from_static("text/html; charset=utf-8"),
20        );
21        Self {
22            status: StatusCode::OK,
23            body: body.into(),
24            headers,
25        }
26    }
27
28    pub fn status(mut self, s: StatusCode) -> Self {
29        self.status = s;
30        self
31    }
32}
33
34impl IntoResponse for ViewResponse {
35    fn into_response(self) -> Response {
36        (self.status, self.headers, self.body).into_response()
37    }
38}
39
40/// A redirect response.
41pub struct Redirect {
42    pub status: StatusCode,
43    pub location: String,
44    pub flash: Option<(String, String)>,
45}
46
47impl Redirect {
48    pub fn to(location: impl Into<String>) -> Self {
49        Self {
50            status: StatusCode::SEE_OTHER,
51            location: location.into(),
52            flash: None,
53        }
54    }
55
56    pub fn back() -> Self {
57        Self::to("/")
58    }
59
60    pub fn permanent(location: impl Into<String>) -> Self {
61        Self {
62            status: StatusCode::MOVED_PERMANENTLY,
63            location: location.into(),
64            flash: None,
65        }
66    }
67
68    pub fn with(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
69        self.flash = Some((key.into(), value.into()));
70        self
71    }
72}
73
74impl IntoResponse for Redirect {
75    fn into_response(self) -> Response {
76        let mut headers = HeaderMap::new();
77        headers.insert(
78            axum::http::header::LOCATION,
79            HeaderValue::from_str(&self.location)
80                .unwrap_or_else(|_| HeaderValue::from_static("/")),
81        );
82        (self.status, headers).into_response()
83    }
84}
85
86/// Trait for "smart" responders — types that know how to render themselves.
87/// Implemented for the common types so handlers can `Ok(json(...))` etc.
88pub trait Responder {
89    fn respond(self) -> Response;
90}
91
92impl<T: IntoResponse> Responder for T {
93    fn respond(self) -> Response {
94        self.into_response()
95    }
96}
97
98pub fn json<T: Serialize>(value: T) -> axum::Json<T> {
99    axum::Json(value)
100}
101
102pub fn no_content() -> Response {
103    (StatusCode::NO_CONTENT).into_response()
104}