Skip to main content

nestforge_core/
request.rs

1use std::ops::Deref;
2
3use axum::{
4    extract::{FromRequest, FromRequestParts, Path},
5    http::request::Parts,
6};
7use serde::de::DeserializeOwned;
8
9use crate::{HttpException, Validate};
10
11/*
12Param<T> = path param wrapper
13
14User writes:
15id: Param<u64>
16
17Instead of:
18Path(id): Path<u64>
19*/
20#[derive(Debug, Clone, Copy)]
21pub struct Param<T>(pub T);
22
23impl<T> Deref for Param<T> {
24    type Target = T;
25
26    fn deref(&self) -> &Self::Target {
27        &self.0
28    }
29}
30
31impl<T> Param<T> {
32    pub fn into_inner(self) -> T {
33        self.0
34    }
35
36    pub fn value(self) -> T {
37        self.0
38    }
39}
40
41/*
42Extract Param<T> from route path params.
43*/
44impl<S, T> FromRequestParts<S> for Param<T>
45where
46    S: Send + Sync,
47    T: DeserializeOwned + Send + 'static,
48{
49    type Rejection = HttpException;
50
51    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
52        let Path(value) = Path::<T>::from_request_parts(parts, state)
53            .await
54            .map_err(|_| HttpException::bad_request("Invalid route parameter"))?;
55
56        Ok(Self(value))
57    }
58}
59
60/*
61Body<T> = JSON request body wrapper
62
63User writes:
64body: Body<CreateUserDto>
65
66Instead of:
67Json(dto): Json<CreateUserDto>
68*/
69pub struct Body<T>(pub T);
70
71impl<T> Deref for Body<T> {
72    type Target = T;
73
74    fn deref(&self) -> &Self::Target {
75        &self.0
76    }
77}
78
79impl<T> Body<T> {
80    pub fn into_inner(self) -> T {
81        self.0
82    }
83
84    pub fn value(self) -> T {
85        self.0
86    }
87}
88
89/*
90Extract Body<T> from JSON request body.
91*/
92impl<S, T> FromRequest<S> for Body<T>
93where
94    S: Send + Sync,
95    T: DeserializeOwned + Send + 'static,
96{
97    type Rejection = HttpException;
98
99    async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
100        let axum::Json(value) = axum::Json::<T>::from_request(req, state)
101            .await
102            .map_err(|_| HttpException::bad_request("Invalid JSON body"))?;
103
104        Ok(Self(value))
105    }
106}
107
108pub struct ValidatedBody<T>(pub T);
109
110impl<T> Deref for ValidatedBody<T> {
111    type Target = T;
112
113    fn deref(&self) -> &Self::Target {
114        &self.0
115    }
116}
117
118impl<T> ValidatedBody<T> {
119    pub fn into_inner(self) -> T {
120        self.0
121    }
122
123    pub fn value(self) -> T {
124        self.0
125    }
126}
127
128impl<S, T> FromRequest<S> for ValidatedBody<T>
129where
130    S: Send + Sync,
131    T: DeserializeOwned + Validate + Send + 'static,
132{
133    type Rejection = HttpException;
134
135    async fn from_request(req: axum::extract::Request, state: &S) -> Result<Self, Self::Rejection> {
136        let axum::Json(value) = axum::Json::<T>::from_request(req, state)
137            .await
138            .map_err(|_| HttpException::bad_request("Invalid JSON body"))?;
139
140        value
141            .validate()
142            .map_err(HttpException::bad_request_validation)?;
143
144        Ok(Self(value))
145    }
146}