1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Copyright: (c) 2023, Mike 'PhiSyX' S. (https://github.com/PhiSyX)         ┃
// ┃ SPDX-License-Identifier: MPL-2.0                                          ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃                                                                           ┃
// ┃  This Source Code Form is subject to the terms of the Mozilla Public      ┃
// ┃  License, v. 2.0. If a copy of the MPL was not distributed with this      ┃
// ┃  file, You can obtain one at https://mozilla.org/MPL/2.0/.                ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

use hyper::http;

// --------- //
// Structure //
// --------- //

pub struct Form<F>(pub F);

// ----------- //
// Énumération //
// ----------- //

#[derive(Debug)]
#[derive(thiserror::Error)]
#[error("\n\t{}: {0}", std::any::type_name::<Self>())]
pub enum MissingFormError {
	FormRejection(#[from] axum::extract::rejection::FormRejection),
	JsonRejection(#[from] axum::extract::rejection::JsonRejection),
}

// -------------- //
// Implémentation // -> Interface
// -------------- //

#[axum::async_trait]
impl<F, S, B> axum::extract::FromRequest<S, B> for Form<F>
where
	F: serde::de::DeserializeOwned,
	S: Send + Sync,
	B: hyper::body::HttpBody + Send + 'static,
	B::Data: Send,
	B::Error: Into<axum::BoxError>,
{
	type Rejection = MissingFormError;

	async fn from_request(
		req: axum::http::request::Request<B>,
		state: &S,
	) -> Result<Self, Self::Rejection> {
		let f = if json_content_type(req.headers()) {
			let axum::extract::Json(form) =
				axum::extract::Json::<F>::from_request(req, state).await?;
			form
		} else {
			let axum::extract::Form(form) =
				axum::extract::Form::<F>::from_request(req, state).await?;
			form
		};
		Ok(Self(f))
	}
}

impl axum::response::IntoResponse for MissingFormError {
	fn into_response(self) -> axum::response::Response {
		let err_status = http::StatusCode::INTERNAL_SERVER_ERROR;
		let err_body = self.to_string();
		(err_status, err_body).into_response()
	}
}

fn json_content_type(headers: &hyper::HeaderMap) -> bool {
	let content_type =
		if let Some(content_type) = headers.get(hyper::header::CONTENT_TYPE) {
			content_type
		} else {
			return false;
		};

	let content_type = if let Ok(content_type) = content_type.to_str() {
		content_type
	} else {
		return false;
	};

	let mime = if let Ok(mime) = content_type.parse::<mime::Mime>() {
		mime
	} else {
		return false;
	};

	let is_json_content_type = mime.type_() == "application"
		&& (mime.subtype() == "json"
			|| mime.suffix().map_or(false, |name| name == "json"));

	is_json_content_type
}