Skip to main content

rustbasic_core/
requests.rs

1use axum::{
2    extract::{FromRequest, FromRequestParts, Query, Form, Json, Request as AxumRequest},
3    http::Method,
4    response::{IntoResponse, Response},
5};
6use serde_json::{json, Value};
7use std::collections::HashMap;
8use validator::Validate;
9use axum_session::Session;
10use crate::session_manager::RustBasicSessionStore;
11
12pub struct Request {
13    pub inputs: Value,
14    pub method: Method,
15    pub path: String,
16    pub headers: HashMap<String, String>,
17    pub session: Session<RustBasicSessionStore>,
18}
19
20impl Request {
21    #[allow(dead_code)]
22    pub fn input(&self, key: &str) -> Option<&Value> {
23        self.inputs.get(key)
24    }
25
26    pub fn input_as_str(&self, key: &str) -> Option<&str> {
27        self.inputs.get(key).and_then(|v| v.as_str())
28    }
29
30    pub fn query(&self, key: &str) -> Option<&str> {
31        self.input_as_str(key)
32    }
33
34    pub fn all(&self) -> &Value {
35        &self.inputs
36    }
37
38    pub fn validate<T: Validate + serde::de::DeserializeOwned>(&self) -> Result<T, Box<(axum::http::StatusCode, Response)>> {
39        let data: T = serde_json::from_value(self.inputs.clone()).map_err(|e| {
40            Box::new((axum::http::StatusCode::UNPROCESSABLE_ENTITY, 
41             axum::response::Json(json!({ "error": "Invalid format", "detail": e.to_string() })).into_response()))
42        })?;
43
44        data.validate().map_err(|e| {
45            // Simpan input lama ke session untuk repopulasi form (Flash Input)
46            self.session.set("old", self.inputs.clone());
47            
48            // Simpan error di session untuk keperluan Inertia/Redirect
49            let mut formatted_errors = HashMap::new();
50            for (field, field_errors) in e.field_errors() {
51                if let Some(err) = field_errors.first() {
52                    let msg = err.message.clone().unwrap_or_else(|| std::borrow::Cow::Borrowed("Invalid field"));
53                    formatted_errors.insert(field.to_string(), msg.to_string());
54                }
55            }
56            self.session.set("errors", formatted_errors);
57            
58            Box::new((axum::http::StatusCode::UNPROCESSABLE_ENTITY, 
59             axum::response::Json(json!({ "errors": e })).into_response()))
60        })?;
61
62        Ok(data)
63    }
64}
65
66impl<S> FromRequest<S> for Request
67where
68    S: Send + Sync,
69{
70    type Rejection = Response;
71
72    async fn from_request(req: AxumRequest, state: &S) -> Result<Self, Self::Rejection> {
73        let method = req.method().clone();
74        let mut inputs = json!({});
75
76        // 1. Ambil Query Params (?id=1)
77        let (mut parts, body) = req.into_parts();
78        if let Ok(Query(query_params)) = Query::<HashMap<String, String>>::from_request_parts(&mut parts, state).await {
79            for (k, v) in query_params {
80                inputs[k] = json!(v);
81            }
82        }
83
84        // 2. Ambil Form Data atau JSON Data berdasarkan Content-Type (POST/PUT/PATCH)
85        let parts_copy = parts.clone();
86        let content_type = parts.headers.get(axum::http::header::CONTENT_TYPE)
87            .and_then(|v| v.to_str().ok())
88            .unwrap_or("");
89
90        if method == Method::POST || method == Method::PUT || method == Method::PATCH {
91            if content_type.starts_with("application/json") {
92                if let Ok(Json(json_data)) = Json::<Value>::from_request(axum::http::Request::from_parts(parts_copy, body), state).await {
93                    if let Value::Object(obj) = json_data {
94                        for (k, v) in obj {
95                            inputs[k] = v;
96                        }
97                    }
98                }
99            } else if let Ok(Form(form_data)) = Form::<HashMap<String, String>>::from_request(axum::http::Request::from_parts(parts_copy, body), state).await {
100                for (k, v) in form_data {
101                    inputs[k] = json!(v);
102                }
103            }
104        }
105        
106        // Ambil Session dari extensions
107        let session = parts.extensions
108            .get::<Session<RustBasicSessionStore>>()
109            .cloned()
110            .ok_or_else(|| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Session tidak ditemukan").into_response())?;
111
112        let path = parts.uri.path().to_string();
113        let mut headers = HashMap::new();
114        for (name, value) in parts.headers.iter() {
115            if let Ok(val_str) = value.to_str() {
116                headers.insert(name.as_str().to_lowercase(), val_str.to_string());
117            }
118        }
119
120        Ok(Request {
121            inputs,
122            method,
123            path,
124            headers,
125            session,
126        })
127    }
128}