doido_controller/
context.rs1use axum::{
2 body::Body,
3 extract::{FromRequestParts, RawPathParams, Request},
4 http::{header, HeaderValue, StatusCode},
5 response::Response,
6};
7use doido_model::sea_orm::DatabaseConnection;
8use serde::de::DeserializeOwned;
9use serde::Serialize;
10
11const MAX_BODY_BYTES: usize = 2 * 1024 * 1024;
13
14pub struct Context {
16 pub(crate) parts: http::request::Parts,
17 pub(crate) body: Option<Body>,
19 pub(crate) path_params: Vec<(String, String)>,
21}
22
23impl Context {
24 pub async fn build(req: Request) -> Self {
27 let (mut parts, body) = req.into_parts();
28 let path_params = Self::extract_path_params(&mut parts).await;
29 Self {
30 parts,
31 body: Some(body),
32 path_params,
33 }
34 }
35
36 async fn extract_path_params(parts: &mut http::request::Parts) -> Vec<(String, String)> {
37 match RawPathParams::from_request_parts(parts, &()).await {
38 Ok(params) => params
39 .iter()
40 .map(|(k, v)| (k.to_string(), v.to_string()))
41 .collect(),
42 Err(_) => Vec::new(),
43 }
44 }
45
46 pub fn from_request_parts(parts: http::request::Parts) -> Self {
47 Self {
48 parts,
49 body: None,
50 path_params: Vec::new(),
51 }
52 }
53
54 pub fn from_request(parts: http::request::Parts, body: Body) -> Self {
55 Self {
56 parts,
57 body: Some(body),
58 path_params: Vec::new(),
59 }
60 }
61
62 pub fn db(&self) -> &'static DatabaseConnection {
64 doido_model::pool::pool()
65 }
66
67 pub fn param(&self, name: &str) -> Option<&str> {
69 self.path_params
70 .iter()
71 .find(|(k, _)| k == name)
72 .map(|(_, v)| v.as_str())
73 }
74
75 pub async fn form<T: DeserializeOwned>(&mut self) -> doido_core::Result<T> {
77 let bytes = self.read_body().await?;
78 serde_urlencoded::from_bytes(&bytes)
79 .map_err(|e| doido_core::anyhow::anyhow!("form deserialization failed: {e}"))
80 }
81
82 pub async fn body_json<T: DeserializeOwned>(&mut self) -> doido_core::Result<T> {
84 let bytes = self.read_body().await?;
85 serde_json::from_slice(&bytes)
86 .map_err(|e| doido_core::anyhow::anyhow!("JSON body deserialization failed: {e}"))
87 }
88
89 async fn read_body(&mut self) -> doido_core::Result<Vec<u8>> {
90 let body = self
91 .body
92 .take()
93 .ok_or_else(|| doido_core::anyhow::anyhow!("request body already consumed"))?;
94 let bytes = axum::body::to_bytes(body, MAX_BODY_BYTES)
95 .await
96 .map_err(|e| doido_core::anyhow::anyhow!("failed to read request body: {e}"))?;
97 Ok(bytes.to_vec())
98 }
99
100 pub fn params<T: serde::de::DeserializeOwned>(&self) -> doido_core::Result<T> {
102 let query = self.parts.uri.query().unwrap_or("");
103 serde_urlencoded::from_str(query)
104 .map_err(|e| doido_core::anyhow::anyhow!("params deserialization failed: {e}"))
105 }
106
107 pub fn render(&self, template: &str, data: serde_json::Value) -> Response {
114 match doido_view::render(template, &data) {
115 Ok(html) => Response::builder()
116 .status(StatusCode::OK)
117 .header(header::CONTENT_TYPE, "text/html; charset=utf-8")
118 .body(Body::from(html))
119 .expect("valid html response"),
120 Err(error) => {
121 tracing::error!(%error, template, "view render failed");
122 Response::builder()
123 .status(StatusCode::INTERNAL_SERVER_ERROR)
124 .body(Body::from("Internal Server Error"))
125 .expect("valid 500 response")
126 }
127 }
128 }
129
130 pub fn json<T: Serialize>(&self, data: T) -> Response {
132 let body = serde_json::to_vec(&data).unwrap_or_default();
133 Response::builder()
134 .status(StatusCode::OK)
135 .header(header::CONTENT_TYPE, "application/json")
136 .body(Body::from(body))
137 .unwrap()
138 }
139
140 pub fn redirect_to(&self, location: impl AsRef<str>) -> Response {
142 Response::builder()
143 .status(StatusCode::FOUND)
144 .header(
145 header::LOCATION,
146 HeaderValue::from_str(location.as_ref()).unwrap(),
147 )
148 .body(Body::empty())
149 .unwrap()
150 }
151
152 pub fn status(&self, code: u16) -> Response {
155 Response::builder()
156 .status(code)
157 .body(Body::empty())
158 .unwrap()
159 }
160
161 pub fn header(&self, name: &str) -> Option<&http::HeaderValue> {
163 self.parts.headers.get(name)
164 }
165}
166
167pub trait IntoActionResponse {
172 fn into_action_response(self) -> Response;
173}
174
175impl IntoActionResponse for Response {
176 fn into_action_response(self) -> Response {
177 self
178 }
179}
180
181impl<E: std::fmt::Display> IntoActionResponse for Result<Response, E> {
182 fn into_action_response(self) -> Response {
183 match self {
184 Ok(response) => response,
185 Err(error) => {
186 tracing::error!(%error, "action returned an error");
187 Response::builder()
188 .status(StatusCode::INTERNAL_SERVER_ERROR)
189 .body(Body::from("Internal Server Error"))
190 .expect("static 500 response is valid")
191 }
192 }
193 }
194}
195
196const _: fn() = || {
199 fn assert_send<T: Send>() {}
200 assert_send::<Context>();
201};