oxidite_core/
extract.rs

1use crate::error::{Error, Result};
2use crate::types::OxiditeRequest;
3use serde::de::DeserializeOwned;
4
5/// Extract typed path parameters from the request
6///
7/// # Example
8/// ```ignore
9/// #[derive(Deserialize)]
10/// struct UserPath {
11///     id: u64,
12/// }
13///
14/// async fn get_user(Path(params): Path<UserPath>) -> Result<Json<User>> {
15///     let user = User::find(params.id).await?;
16///     Ok(Json(user))
17/// }
18/// ```
19pub struct Path<T>(pub T);
20
21/// Extract typed query parameters from the request
22///
23/// # Example
24/// ```ignore
25/// #[derive(Deserialize)]
26/// struct Pagination {
27///     page: u32,
28///     limit: u32,
29/// }
30///
31/// async fn list_users(Query(params): Query<Pagination>) -> Result<Json<Vec<User>>> {
32///     let users = User::paginate(params.page, params.limit).await?;
33///     Ok(Json(users))
34/// }
35/// ```
36pub struct Query<T>(pub T);
37
38/// Extract and deserialize JSON request body
39///
40/// # Example
41/// ```ignore
42/// #[derive(Deserialize)]
43/// struct CreateUser {
44///     name: String,
45///     email: String,
46/// }
47///
48/// async fn create_user(Json(data): Json<CreateUser>) -> Result<Json<User>> {
49///     let user = User::create(data).await?;
50///     Ok(Json(user))
51/// }
52/// ```
53pub struct Json<T>(pub T);
54
55/// Extractor trait - allows types to be extracted from requests
56pub trait FromRequest: Sized {
57    async fn from_request(req: &mut OxiditeRequest) -> Result<Self>;
58}
59
60impl<T: DeserializeOwned> FromRequest for Path<T> {
61    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
62        // Path params are stored in request extensions after routing
63        req.extensions()
64            .get::<PathParams>()
65            .ok_or_else(|| Error::BadRequest("No path parameters found".to_string()))
66            .and_then(|params| {
67                serde_json::from_value(params.0.clone())
68                    .map(Path)
69                    .map_err(|e| Error::BadRequest(format!("Invalid path parameters: {}", e)))
70            })
71    }
72}
73
74impl<T: DeserializeOwned> FromRequest for Query<T> {
75    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
76        let query = req.uri().query().unwrap_or("");
77        serde_urlencoded::from_str(query)
78            .map(Query)
79            .map_err(|e| Error::BadRequest(format!("Invalid query parameters: {}", e)))
80    }
81}
82
83impl<T: DeserializeOwned> FromRequest for Json<T> {
84    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
85        use http_body_util::BodyExt;
86        use bytes::Buf;
87
88        let body = req.body_mut();
89        let bytes = body.collect().await
90            .map_err(|e| Error::InternalServerError(format!("Failed to read body: {}", e)))?
91            .aggregate();
92
93        serde_json::from_reader(bytes.reader())
94            .map(Json)
95            .map_err(|e| Error::BadRequest(format!("Invalid JSON: {}", e)))
96    }
97}
98
99// Storage for path parameters extracted during routing
100#[derive(Clone)]
101pub struct PathParams(pub serde_json::Value);
102
103// Helper to serialize responses as JSON
104impl<T: serde::Serialize> Json<T> {
105    pub fn into_response(self) -> Result<http_body_util::Full<bytes::Bytes>> {
106        let body = serde_json::to_vec(&self.0)
107            .map_err(|e| Error::InternalServerError(format!("Failed to serialize JSON: {}", e)))?;
108        Ok(http_body_util::Full::new(bytes::Bytes::from(body)))
109    }
110}
111
112/// Extract application state from request extensions
113///
114/// # Example
115/// ```ignore
116/// async fn handler(State(state): State<Arc<AppState>>) -> Result<Response> {
117///     // use state
118/// }
119/// ```
120pub struct State<T>(pub T);
121
122impl<T: Clone + Send + Sync + 'static> FromRequest for State<T> {
123    fn from_request(req: &mut OxiditeRequest) -> impl std::future::Future<Output = Result<Self>> {
124        async {
125            req.extensions()
126                .get::<T>()
127                .cloned()
128                .map(State)
129                .ok_or_else(|| Error::InternalServerError("Application state not found in request extensions".to_string()))
130        }
131    }
132}
133
134/// Extract form data from the request body
135///
136/// # Example
137/// ```ignore
138/// #[derive(Deserialize)]
139/// struct LoginForm {
140///     username: String,
141///     password: String,
142/// }
143///
144/// async fn login(Form(data): Form<LoginForm>) -> Result<Json<Session>> {
145///     // process login
146/// }
147/// ```
148pub struct Form<T>(pub T);
149
150impl<T: DeserializeOwned> FromRequest for Form<T> {
151    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
152        use http_body_util::BodyExt;
153        use bytes::Buf;
154        
155        // Check content type
156        let content_type = req.headers()
157            .get("content-type")
158            .and_then(|ct| ct.to_str().ok())
159            .unwrap_or("");
160            
161        if !content_type.starts_with("application/x-www-form-urlencoded") {
162            return Err(Error::BadRequest(
163                "Expected application/x-www-form-urlencoded content type".to_string()
164            ));
165        }
166        
167        let body = req.body_mut();
168        let bytes = body.collect().await
169            .map_err(|e| Error::InternalServerError(format!("Failed to read body: {}", e)))?
170            .aggregate();
171        
172        let body_str = std::str::from_utf8(bytes.chunk())
173            .map_err(|e| Error::BadRequest(format!("Invalid UTF-8 in form data: {}", e)))?;
174        
175        serde_urlencoded::from_str(body_str)
176            .map(Form)
177            .map_err(|e| Error::BadRequest(format!("Invalid form data: {}", e)))
178    }
179}
180
181/// Extract cookies from the request
182///
183/// # Example
184/// ```ignore
185/// async fn handler(cookies: Cookies) -> Result<Response> {
186///     if let Some(token) = cookies.get("auth_token") {
187///         // use token
188///     }
189///     Ok(Response::text("OK"))
190/// }
191/// ```
192pub struct Cookies {
193    cookies: std::collections::HashMap<String, String>,
194}
195
196impl Cookies {
197    pub fn get(&self, name: &str) -> Option<&String> {
198        self.cookies.get(name)
199    }
200    
201    pub fn contains_key(&self, name: &str) -> bool {
202        self.cookies.contains_key(name)
203    }
204    
205    pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
206        self.cookies.iter()
207    }
208}
209
210impl FromRequest for Cookies {
211    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
212        let mut cookies_map = std::collections::HashMap::new();
213        
214        if let Some(cookie_header) = req.headers().get(http::header::COOKIE) {
215            if let Ok(cookie_str) = cookie_header.to_str() {
216                for cookie_pair in cookie_str.split(';') {
217                    let trimmed = cookie_pair.trim();
218                    if let Some((name, value)) = trimmed.split_once('=') {
219                        cookies_map.insert(name.trim().to_string(), value.trim().to_string());
220                    }
221                }
222            }
223        }
224        
225        Ok(Cookies { cookies: cookies_map })
226    }
227}
228
229/// Extract raw request body as string
230///
231/// # Example
232/// ```ignore
233/// async fn webhook_handler(Body(raw): Body<String>) -> Result<Response> {
234///     // process raw body
235///     Ok(Response::text("Received"))
236/// }
237/// ```
238pub struct Body<T>(pub T);
239
240impl FromRequest for Body<String> {
241    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
242        use http_body_util::BodyExt;
243        use bytes::Buf;
244        
245        let body = req.body_mut();
246        let bytes = body.collect().await
247            .map_err(|e| Error::InternalServerError(format!("Failed to read body: {}", e)))?
248            .aggregate();
249        
250        let body_str = std::str::from_utf8(bytes.chunk())
251            .map_err(|e| Error::InternalServerError(format!("Invalid UTF-8 in body: {}", e)))?
252            .to_string();
253        
254        Ok(Body(body_str))
255    }
256}
257
258impl FromRequest for Body<Vec<u8>> {
259    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
260        use http_body_util::BodyExt;
261        
262        let body = req.body_mut();
263        let bytes = body.collect().await
264            .map_err(|e| Error::InternalServerError(format!("Failed to read body: {}", e)))?
265            .to_bytes();
266        
267        Ok(Body(bytes.to_vec()))
268    }
269}