docbox_http/middleware/
action_user.rs1use crate::error::{DynHttpError, HttpCommonError, HttpError};
4use axum::{
5 extract::FromRequestParts,
6 http::{StatusCode, request::Parts},
7};
8use docbox_core::database::{DbExecutor, models::user::User};
9use thiserror::Error;
10use utoipa::IntoParams;
11
12pub struct ActionUser(pub Option<ActionUserData>);
13
14impl ActionUser {
15 pub async fn store_user(self, db: impl DbExecutor<'_>) -> Result<Option<User>, DynHttpError> {
17 let user_data = match self.0 {
18 Some(value) => value,
19 None => return Ok(None),
20 };
21
22 let user = match User::store(db, user_data.id, user_data.name, user_data.image_id).await {
23 Ok(value) => value,
24 Err(error) => {
25 tracing::error!(?error, "failed to store user");
26 return Err(HttpCommonError::ServerError.into());
27 }
28 };
29
30 Ok(Some(user))
31 }
32}
33
34pub struct ActionUserData {
35 pub id: String,
36 pub name: Option<String>,
37 pub image_id: Option<String>,
38}
39
40pub const USER_ID_HEADER: &str = "x-user-id";
41pub const USER_NAME_HEADER: &str = "x-user-name";
42pub const USER_IMAGE_ID_HEADER: &str = "x-user-image-id";
43
44#[derive(IntoParams)]
46#[into_params(parameter_in = Header)]
47#[allow(unused)]
48pub struct UserParams {
49 #[param(rename = "x-user-id")]
51 pub user_id: Option<String>,
52 #[param(rename = "x-user-name")]
54 pub user_name: Option<String>,
55 #[param(rename = "x-user-image-id")]
57 pub user_image_id: Option<String>,
58}
59
60#[derive(Debug, Error)]
61#[error("user id was not a valid utf8 string")]
62struct InvalidUserId;
63
64impl HttpError for InvalidUserId {
65 fn status(&self) -> axum::http::StatusCode {
66 StatusCode::BAD_REQUEST
67 }
68}
69
70impl<S> FromRequestParts<S> for ActionUser
71where
72 S: Send + Sync,
73{
74 type Rejection = DynHttpError;
75
76 async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
77 let id = match parts.headers.get(USER_ID_HEADER) {
78 Some(value) => {
79 let value_str = value.to_str().map_err(|_| InvalidUserId)?;
80 value_str.to_string()
81 }
82
83 None => return Ok(ActionUser(None)),
85 };
86
87 let name = parts
88 .headers
89 .get(USER_NAME_HEADER)
90 .and_then(|value| value.to_str().ok())
91 .map(|value| value.to_string());
92
93 let image_id = parts
94 .headers
95 .get(USER_IMAGE_ID_HEADER)
96 .and_then(|value| value.to_str().ok())
97 .map(|value| value.to_string());
98
99 Ok(ActionUser(Some(ActionUserData { id, name, image_id })))
100 }
101}