use axum::body::{to_bytes, Body};
use axum::extract::Request;
use axum::middleware::Next;
use axum::response::Response;
use serde_json::Value as Json;
use crate::auth::headers;
#[derive(Debug, Clone)]
pub struct MethodOverride(pub http::Method);
const CREDENTIALS: [(&str, &str); 6] = [
("_ApplicationId", headers::APP_ID),
("_JavaScriptKey", headers::JAVASCRIPT_KEY),
("_MasterKey", headers::MASTER_KEY),
("_MaintenanceKey", headers::MAINTENANCE_KEY),
("_SessionToken", headers::SESSION_TOKEN),
("_InstallationId", headers::INSTALLATION_ID),
];
const DISCARDED: [&str; 4] = [
"_ClientVersion",
"_RevocableSession",
"_noBody",
"_ContentType",
];
const MAX_BODY: usize = 20 * 1024 * 1024;
pub async fn extract(request: Request, next: Next) -> Response {
let (mut parts, body) = request.into_parts();
let bytes = match to_bytes(body, MAX_BODY).await {
Ok(b) => b,
Err(_) => return next.run(Request::from_parts(parts, Body::empty())).await,
};
let Ok(Json::Object(mut map)) = serde_json::from_slice::<Json>(&bytes) else {
return next
.run(Request::from_parts(parts, Body::from(bytes)))
.await;
};
for (body_key, header_name) in CREDENTIALS {
let Some(Json::String(value)) = map.shift_remove(body_key) else {
continue;
};
if parts.headers.contains_key(header_name) {
continue;
}
if let (Ok(name), Ok(val)) = (
http::HeaderName::from_bytes(header_name.as_bytes()),
http::HeaderValue::from_str(&value),
) {
parts.headers.insert(name, val);
}
}
for key in DISCARDED {
map.shift_remove(key);
}
let overridden = match map.shift_remove("_method") {
Some(Json::String(m)) => m.parse::<http::Method>().ok(),
_ => None,
};
if let Some(method) = overridden.clone() {
parts.extensions.insert(MethodOverride(method));
}
let effective = overridden.clone().unwrap_or_else(|| parts.method.clone());
if effective == http::Method::GET || effective == http::Method::DELETE {
let mut pairs: Vec<(String, String)> = Vec::new();
for (k, v) in &map {
let value = match v {
Json::String(s) => s.clone(),
other => other.to_string(),
};
pairs.push((k.clone(), value));
}
if !pairs.is_empty() {
let existing = parts.uri.query().unwrap_or("").to_string();
let mut serializer = form_urlencoded::Serializer::new(String::new());
for (k, v) in pairs {
serializer.append_pair(&k, &v);
}
let merged = if existing.is_empty() {
serializer.finish()
} else {
format!("{existing}&{}", serializer.finish())
};
let path = parts.uri.path().to_string();
if let Ok(uri) = format!("{path}?{merged}").parse::<http::Uri>() {
parts.uri = uri;
}
}
let trace = std::env::var("PARSE_RUST_TRACE").is_ok();
let (m, u) = (parts.method.clone(), parts.uri.clone());
let res = next.run(Request::from_parts(parts, Body::empty())).await;
if trace {
eprintln!("[trace] {m} {u} -> {}", res.status());
}
return res;
}
parts.headers.insert(
http::header::CONTENT_TYPE,
http::HeaderValue::from_static("application/json"),
);
let body = match serde_json::to_vec(&Json::Object(map)) {
Ok(v) => Body::from(v),
Err(_) => Body::from(bytes),
};
let trace = std::env::var("PARSE_RUST_TRACE").is_ok();
let (m, u) = (parts.method.clone(), parts.uri.clone());
let res = next.run(Request::from_parts(parts, body)).await;
if trace {
eprintln!("[trace] {m} {u} -> {}", res.status());
}
res
}