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    fn from_request(req: &mut OxiditeRequest) -> impl std::future::Future<Output = Result<Self>> + Send;
58}
59
60impl<T: DeserializeOwned + Send> 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 + Send> 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 + Send> 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    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
124        req.extensions()
125            .get::<T>()
126            .cloned()
127            .map(State)
128            .ok_or_else(|| Error::InternalServerError("Application state not found in request extensions".to_string()))
129    }
130}
131
132/// Extract form data from the request body
133///
134/// # Example
135/// ```ignore
136/// #[derive(Deserialize)]
137/// struct LoginForm {
138///     username: String,
139///     password: String,
140/// }
141///
142/// async fn login(Form(data): Form<LoginForm>) -> Result<Json<Session>> {
143///     // process login
144/// }
145/// ```
146pub struct Form<T>(pub T);
147
148impl<T: DeserializeOwned + Send> FromRequest for Form<T> {
149    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
150        use http_body_util::BodyExt;
151        use bytes::Buf;
152        
153        // Check content type
154        let content_type = req.headers()
155            .get("content-type")
156            .and_then(|ct| ct.to_str().ok())
157            .unwrap_or("");
158            
159        if !content_type.starts_with("application/x-www-form-urlencoded") {
160            return Err(Error::BadRequest(
161                "Expected application/x-www-form-urlencoded content type".to_string()
162            ));
163        }
164        
165        let body = req.body_mut();
166        let bytes = body.collect().await
167            .map_err(|e| Error::InternalServerError(format!("Failed to read body: {}", e)))?
168            .aggregate();
169        
170        let body_str = std::str::from_utf8(bytes.chunk())
171            .map_err(|e| Error::BadRequest(format!("Invalid UTF-8 in form data: {}", e)))?;
172        
173        serde_urlencoded::from_str(body_str)
174            .map(Form)
175            .map_err(|e| Error::BadRequest(format!("Invalid form data: {}", e)))
176    }
177}
178
179/// Extract cookies from the request
180///
181/// # Example
182/// ```ignore
183/// async fn handler(cookies: Cookies) -> Result<Response> {
184///     if let Some(token) = cookies.get("auth_token") {
185///         // use token
186///     }
187///     Ok(Response::text("OK"))
188/// }
189/// ```
190pub struct Cookies {
191    cookies: std::collections::HashMap<String, String>,
192}
193
194impl Cookies {
195    pub fn get(&self, name: &str) -> Option<&String> {
196        self.cookies.get(name)
197    }
198    
199    pub fn contains_key(&self, name: &str) -> bool {
200        self.cookies.contains_key(name)
201    }
202    
203    pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
204        self.cookies.iter()
205    }
206}
207
208impl FromRequest for Cookies {
209    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
210        let mut cookies_map = std::collections::HashMap::new();
211        
212        if let Some(cookie_header) = req.headers().get(http::header::COOKIE) {
213            if let Ok(cookie_str) = cookie_header.to_str() {
214                for cookie_pair in cookie_str.split(';') {
215                    let trimmed = cookie_pair.trim();
216                    if let Some((name, value)) = trimmed.split_once('=') {
217                        cookies_map.insert(name.trim().to_string(), value.trim().to_string());
218                    }
219                }
220            }
221        }
222        
223        Ok(Cookies { cookies: cookies_map })
224    }
225}
226
227/// Extract raw request body as string
228///
229/// # Example
230/// ```ignore
231/// async fn webhook_handler(Body(raw): Body<String>) -> Result<Response> {
232///     // process raw body
233///     Ok(Response::text("Received"))
234/// }
235/// ```
236pub struct Body<T>(pub T);
237
238impl FromRequest for Body<String> {
239    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
240        use http_body_util::BodyExt;
241        use bytes::Buf;
242        
243        let body = req.body_mut();
244        let bytes = body.collect().await
245            .map_err(|e| Error::InternalServerError(format!("Failed to read body: {}", e)))?
246            .aggregate();
247        
248        let body_str = std::str::from_utf8(bytes.chunk())
249            .map_err(|e| Error::InternalServerError(format!("Invalid UTF-8 in body: {}", e)))?
250            .to_string();
251        
252        Ok(Body(body_str))
253    }
254}
255
256impl FromRequest for Body<Vec<u8>> {
257    async fn from_request(req: &mut OxiditeRequest) -> Result<Self> {
258        use http_body_util::BodyExt;
259        
260        let body = req.body_mut();
261        let bytes = body.collect().await
262            .map_err(|e| Error::InternalServerError(format!("Failed to read body: {}", e)))?
263            .to_bytes();
264        
265        Ok(Body(bytes.to_vec()))
266    }
267}