afc0c6e
commit afc0c6e
1 file changed
+63 -37
Showing 1 changed file+63-37
▼crates/justapi-py/src/request.rs
+63-37
| @@ -39,12 +39,12 @@ fn split_host_port(host: &str) -> (String, Option<u16>) { | ||
39 | 39 | (host.to_string(), None) |
40 | 40 | } |
41 | 41 | |
42 | -pub(crate) fn build_headers(py: Python<'_>, conn: &Conn) -> Py<PyAny> { | |
42 | +pub(crate) fn build_headers(py: Python<'_>, conn: &Conn) -> PyResult<Py<PyAny>> { | |
43 | 43 | let raw: Vec<(Vec<u8>, Vec<u8>)> = conn.headers_raw.clone(); |
44 | - Bound::new(py, Headers { raw }).unwrap().into_any().unbind() | |
44 | + Ok(Bound::new(py, Headers { raw })?.into_any().unbind()) | |
45 | 45 | } |
46 | 46 | |
47 | -pub(crate) fn build_query_params(py: Python<'_>, conn: &Conn) -> Py<PyAny> { | |
47 | +pub(crate) fn build_query_params(py: Python<'_>, conn: &Conn) -> PyResult<Py<PyAny>> { | |
48 | 48 | let qs = String::from_utf8_lossy(&conn.query_string_raw).to_string(); |
49 | 49 | let mut items: Vec<(String, String)> = Vec::new(); |
50 | 50 | for pair in qs.split('&') { |
| @@ -57,7 +57,7 @@ pub(crate) fn build_query_params(py: Python<'_>, conn: &Conn) -> Py<PyAny> { | ||
57 | 57 | }; |
58 | 58 | items.push((percent_decode(k), percent_decode(v))); |
59 | 59 | } |
60 | - Bound::new(py, QueryParams { items }).unwrap().into_any().unbind() | |
60 | + Ok(Bound::new(py, QueryParams { items })?.into_any().unbind()) | |
61 | 61 | } |
62 | 62 | |
63 | 63 | pub(crate) fn build_cookies(py: Python<'_>, conn: &Conn) -> Py<PyAny> { |
| @@ -83,7 +83,7 @@ pub(crate) fn build_url( | ||
83 | 83 | path: &str, |
84 | 84 | query: &[u8], |
85 | 85 | fragment: &str, |
86 | -) -> Py<PyAny> { | |
86 | +) -> PyResult<Py<PyAny>> { | |
87 | 87 | let (hostname, port) = split_host_port(host); |
88 | 88 | let url = URL { |
89 | 89 | scheme: scheme.to_string(), |
| @@ -93,7 +93,7 @@ pub(crate) fn build_url( | ||
93 | 93 | query: String::from_utf8_lossy(query).to_string(), |
94 | 94 | fragment: fragment.to_string(), |
95 | 95 | }; |
96 | - Bound::new(py, url).unwrap().into_any().unbind() | |
96 | + Ok(Bound::new(py, url)?.into_any().unbind()) | |
97 | 97 | } |
98 | 98 | |
99 | 99 | fn build_scope(py: Python<'_>, conn: &Conn) -> Py<PyAny> { |
| @@ -195,8 +195,26 @@ impl Request { | ||
195 | 195 | client: Option<(String, u16)>, |
196 | 196 | app: Option<Py<PyAny>>, |
197 | 197 | http_version: String, |
198 | + auth_claims: Option<String>, | |
198 | 199 | ) -> Self { |
199 | - let state = PyDict::new(py).into(); | |
200 | + let state = PyDict::new(py); | |
201 | + // Bridge JWT claims from the Rust middleware into request.state["auth"]. | |
202 | + // The `auth_claims` is a JSON string of the decoded JWT payload, set by | |
203 | + // the middleware and forwarded through `call_python_handler`. | |
204 | + if let Some(claims_json) = &auth_claims { | |
205 | + if let Ok(serde_json::Value::Object(map)) = | |
206 | + serde_json::from_str::<serde_json::Value>(claims_json) | |
207 | + { | |
208 | + let d = PyDict::new(py); | |
209 | + for (k, v) in &map { | |
210 | + if let Ok(val) = json_value_to_request(py, v) { | |
211 | + d.set_item(k.as_str(), val).ok(); | |
212 | + } | |
213 | + } | |
214 | + state.set_item("auth", d).ok(); | |
215 | + } | |
216 | + } | |
217 | + let state = state.into(); | |
200 | 218 | let conn = Conn { |
201 | 219 | method, |
202 | 220 | path, |
| @@ -232,6 +250,34 @@ impl Request { | ||
232 | 250 | } |
233 | 251 | } |
234 | 252 | |
253 | +fn json_value_to_request<'py>( | |
254 | + py: Python<'py>, | |
255 | + v: &serde_json::Value, | |
256 | +) -> PyResult<Bound<'py, PyAny>> { | |
257 | + match v { | |
258 | + serde_json::Value::Null => Ok(py.None().into_bound(py)), | |
259 | + serde_json::Value::Bool(b) => Ok((*b).into_pyobject(py)?.as_any().clone()), | |
260 | + serde_json::Value::Number(n) => { | |
261 | + if let Some(i) = n.as_i64() { | |
262 | + Ok(i.into_pyobject(py)?.as_any().clone()) | |
263 | + } else if let Some(f) = n.as_f64() { | |
264 | + Ok(f.into_pyobject(py)?.as_any().clone()) | |
265 | + } else { | |
266 | + Ok(py.None().into_bound(py)) | |
267 | + } | |
268 | + } | |
269 | + serde_json::Value::String(s) => Ok(s.into_pyobject(py)?.as_any().clone()), | |
270 | + serde_json::Value::Array(arr) => { | |
271 | + let list = PyList::empty(py); | |
272 | + for item in arr { | |
273 | + list.append(json_value_to_request(py, item)?).ok(); | |
274 | + } | |
275 | + Ok(list.as_any().clone()) | |
276 | + } | |
277 | + serde_json::Value::Object(_) => Ok(py.None().into_bound(py)), | |
278 | + } | |
279 | +} | |
280 | + | |
235 | 281 | #[pymethods] |
236 | 282 | impl Request { |
237 | 283 | #[getter] |
| @@ -270,7 +316,7 @@ impl Request { | ||
270 | 316 | |
271 | 317 | #[getter] |
272 | 318 | fn headers(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
273 | - Ok(build_headers(py, &self.conn)) | |
319 | + build_headers(py, &self.conn) | |
274 | 320 | } |
275 | 321 | |
276 | 322 | #[getter] |
| @@ -280,7 +326,7 @@ impl Request { | ||
280 | 326 | |
281 | 327 | #[getter] |
282 | 328 | fn query_params(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
283 | - Ok(build_query_params(py, &self.conn)) | |
329 | + build_query_params(py, &self.conn) | |
284 | 330 | } |
285 | 331 | |
286 | 332 | #[getter] |
| @@ -305,20 +351,13 @@ impl Request { | ||
305 | 351 | #[getter] |
306 | 352 | fn url(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
307 | 353 | let host = host_of(&self.conn).unwrap_or_else(|| "localhost".to_string()); |
308 | - Ok(build_url( | |
309 | - py, | |
310 | - &self.conn.scheme, | |
311 | - &host, | |
312 | - &self.conn.path, | |
313 | - &self.conn.query_string_raw, | |
314 | - "", | |
315 | - )) | |
354 | + build_url(py, &self.conn.scheme, &host, &self.conn.path, &self.conn.query_string_raw, "") | |
316 | 355 | } |
317 | 356 | |
318 | 357 | #[getter] |
319 | 358 | fn base_url(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
320 | 359 | let host = host_of(&self.conn).unwrap_or_else(|| "localhost".to_string()); |
321 | - Ok(build_url(py, &self.conn.scheme, &host, "", b"", "")) | |
360 | + build_url(py, &self.conn.scheme, &host, "", b"", "") | |
322 | 361 | } |
323 | 362 | |
324 | 363 | #[getter] |
| @@ -328,10 +367,7 @@ impl Request { | ||
328 | 367 | |
329 | 368 | #[getter] |
330 | 369 | fn state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
331 | - Ok(Bound::new(py, State { dict: self.conn.state.clone_ref(py) }) | |
332 | - .unwrap() | |
333 | - .into_any() | |
334 | - .unbind()) | |
370 | + Ok(Bound::new(py, State { dict: self.conn.state.clone_ref(py) })?.into_any().unbind()) | |
335 | 371 | } |
336 | 372 | |
337 | 373 | #[getter] |
| @@ -636,7 +672,7 @@ impl HTTPConnection { | ||
636 | 672 | |
637 | 673 | #[getter] |
638 | 674 | fn headers(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
639 | - Ok(build_headers(py, &self.conn)) | |
675 | + build_headers(py, &self.conn) | |
640 | 676 | } |
641 | 677 | |
642 | 678 | #[getter] |
| @@ -646,7 +682,7 @@ impl HTTPConnection { | ||
646 | 682 | |
647 | 683 | #[getter] |
648 | 684 | fn query_params(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
649 | - Ok(build_query_params(py, &self.conn)) | |
685 | + build_query_params(py, &self.conn) | |
650 | 686 | } |
651 | 687 | |
652 | 688 | #[getter] |
| @@ -666,20 +702,13 @@ impl HTTPConnection { | ||
666 | 702 | #[getter] |
667 | 703 | fn url(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
668 | 704 | let host = host_of(&self.conn).unwrap_or_else(|| "localhost".to_string()); |
669 | - Ok(build_url( | |
670 | - py, | |
671 | - &self.conn.scheme, | |
672 | - &host, | |
673 | - &self.conn.path, | |
674 | - &self.conn.query_string_raw, | |
675 | - "", | |
676 | - )) | |
705 | + build_url(py, &self.conn.scheme, &host, &self.conn.path, &self.conn.query_string_raw, "") | |
677 | 706 | } |
678 | 707 | |
679 | 708 | #[getter] |
680 | 709 | fn base_url(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
681 | 710 | let host = host_of(&self.conn).unwrap_or_else(|| "localhost".to_string()); |
682 | - Ok(build_url(py, &self.conn.scheme, &host, "", b"", "")) | |
711 | + build_url(py, &self.conn.scheme, &host, "", b"", "") | |
683 | 712 | } |
684 | 713 | |
685 | 714 | #[getter] |
| @@ -689,10 +718,7 @@ impl HTTPConnection { | ||
689 | 718 | |
690 | 719 | #[getter] |
691 | 720 | fn state(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { |
692 | - Ok(Bound::new(py, State { dict: self.conn.state.clone_ref(py) }) | |
693 | - .unwrap() | |
694 | - .into_any() | |
695 | - .unbind()) | |
721 | + Ok(Bound::new(py, State { dict: self.conn.state.clone_ref(py) })?.into_any().unbind()) | |
696 | 722 | } |
697 | 723 | |
698 | 724 | #[getter] |
699 | 725 | |