swadhinbiswas

[email protected]
2026-07-25 15:36:32 +0600 committed NaN years ago
d3107b1
commit d3107b1
1 file changed
+38 -8
Showing 1 changed file+38-8
▼crates/justapi-py/src/native/handlers.rs
+38-8
85
85
app: Option<Py<PyAny>>,
86
86
needs_request: bool,
87
87
http_version: String,
88
+ auth_claims: Option<String>,
88
89
) -> NativeResponse {
89
90
let helper = get_helper(py);
90
91
167
168
client,
168
169
app,
169
170
http_version,
171
+ auth_claims,
170
172
);
171
173
if let Some(pb) = parsed_body {
172
174
req.set_parsed_body(py, pb.into_bound(py));
305
307
/// Cached `orjson.dumps(obj, default=str)` (or `json.dumps` fallback) callable,
306
308
/// resolved once on first use. Mirrors `_native_helper._dumps` but lets Rust
307
309
/// invoke orjson directly, skipping the Python `wrap_result` bytecode.
310
+///
311
+/// When the `orjson` Cargo feature is enabled, `orjson` is required — the
312
+/// function will panic at first call if the Python `orjson` module is not
313
+/// available. Without the feature, it silently falls back to `json.dumps`.
308
314
pub(crate) fn fast_dumps(py: Python<'_>) -> PyResult<&Bound<'_, PyAny>> {
309
315
static DUMPS: std::sync::OnceLock<Py<PyAny>> = std::sync::OnceLock::new();
316
+
317
+ #[cfg(not(feature = "orjson"))]
310
318
let expr = if py.import("orjson").is_ok() {
311
319
"lambda o: __import__('orjson').dumps(o, default=str)"
312
320
} else {
313
321
"lambda o: __import__('json').dumps(o, default=str).encode('utf-8')"
314
322
};
323
+
324
+ #[cfg(feature = "orjson")]
325
+ let expr = {
326
+ let _ = py.import("orjson").unwrap_or_else(|e| {
327
+ panic!("orjson feature enabled but Python 'orjson' module not available: {e}")
328
+ });
329
+ "lambda o: __import__('orjson').dumps(o, default=str)"
330
+ };
331
+
315
332
// SAFETY: globals/locals are left as None, which Python fills with the
316
333
// builtins dict, so `__import__` and `str` are available. The cached
317
334
// lambda borrows no local state and is leaked for the process lifetime.
725
742
.map(|s| s.to_string());
726
743
let is_multipart =
727
744
content_type.as_deref().unwrap_or("").starts_with("multipart/form-data");
745
+
746
+ // Extract JWT claims from the Rust middleware before consuming the
747
+ // request body (which moves `req`). The claims are stored as a
748
+ // `serde_json::Value` in `req.extensions()` by `JwtAuth` middleware.
749
+ let auth_claims: Option<String> =
750
+ req.extensions().get::<serde_json::Value>().map(|v| v.to_string());
751
+
728
752
let req_body = req.into_body();
729
753
let (body_bytes, multipart_form_res) = if is_multipart {
730
754
let ct = content_type.unwrap();
913
937
if let Some(Ok(form)) = multipart_form_res {
914
938
let d = pyo3::types::PyDict::new(py);
915
939
for (k, v) in form.fields.iter() {
916
- d.set_item(k, v).unwrap();
940
+ d.set_item(k, v).ok();
917
941
}
918
942
for f in form.files.iter() {
919
943
let headers_dict = pyo3::types::PyDict::new(py);
920
944
for (k, v) in f.headers.iter() {
921
- headers_dict.set_item(k, v).unwrap();
945
+ headers_dict.set_item(k, v).ok();
922
946
}
923
947
let upload_file = crate::multipart::UploadFile::new(
924
948
f.filename.clone().unwrap_or_default(),
927
951
headers_dict.into(),
928
952
f.temp_path.clone(),
929
953
);
930
- let p = pyo3::Bound::new(py, upload_file).unwrap();
931
- d.set_item(&f.field_name, p).unwrap();
954
+ let Ok(p) = pyo3::Bound::new(py, upload_file) else {
955
+ continue;
956
+ };
957
+ d.set_item(&f.field_name, p).ok();
932
958
}
933
959
form_dict_py = Some(d.into());
934
960
}
951
977
app.as_ref().as_ref().map(|a| a.clone_ref(py)),
952
978
needs_request_clone[handler_id],
953
979
http_version.clone(),
980
+ auth_claims,
954
981
)
955
982
})
956
983
.await
1176
1203
if let Some(Ok(form)) = &multipart_form_res {
1177
1204
let d = pyo3::types::PyDict::new(py);
1178
1205
for (k, v) in form.fields.iter() {
1179
- d.set_item(k, v).unwrap();
1206
+ d.set_item(k, v).ok();
1180
1207
}
1181
1208
for f in form.files.iter() {
1182
1209
let headers_dict = pyo3::types::PyDict::new(py);
1183
1210
for (k, v) in f.headers.iter() {
1184
- headers_dict.set_item(k, v).unwrap();
1211
+ headers_dict.set_item(k, v).ok();
1185
1212
}
1186
1213
let upload_file = crate::multipart::UploadFile::new(
1187
1214
f.filename.clone().unwrap_or_default(),
1190
1217
headers_dict.into(),
1191
1218
f.temp_path.clone(),
1192
1219
);
1193
- let p = pyo3::Bound::new(py, upload_file).unwrap();
1194
- d.set_item(&f.field_name, p).unwrap();
1220
+ let Ok(p) = pyo3::Bound::new(py, upload_file) else {
1221
+ continue;
1222
+ };
1223
+ d.set_item(&f.field_name, p).ok();
1195
1224
}
1196
1225
form_dict_py = Some(d.into());
1197
1226
}
1214
1243
app.as_ref().as_ref().map(|a| a.clone_ref(py)),
1215
1244
needs_request_clone[handler_id],
1216
1245
http_version.clone(),
1246
+ None, // auth_claims: test client has no middleware
1217
1247
)
1218
1248
})
1219
1249
.await
1220
1250