use axum::extract::Request;
use axum::http::header::{ACCESS_CONTROL_REQUEST_METHOD, ORIGIN};
use axum::http::Method;
use axum::middleware::{self, Next};
use axum::response::Response;
use axum::Router;
use tower_http::cors::CorsLayer;
#[derive(Clone, Copy)]
struct OrdinaryOptions;
fn stand_in() -> Method {
Method::from_bytes(b"X-SP-OPTIONS").expect("a valid method token")
}
pub(crate) fn layer<S>(router: Router<S>, cors: CorsLayer) -> Router<S>
where
S: Clone + Send + Sync + 'static,
{
router
.layer(middleware::from_fn(restore_options))
.layer(cors)
.layer(middleware::from_fn(disguise_options))
}
async fn disguise_options(mut request: Request, next: Next) -> Response {
let headers = request.headers();
let preflight =
headers.contains_key(ORIGIN) && headers.contains_key(ACCESS_CONTROL_REQUEST_METHOD);
if request.method() == Method::OPTIONS && !preflight {
*request.method_mut() = stand_in();
request.extensions_mut().insert(OrdinaryOptions);
}
next.run(request).await
}
async fn restore_options(mut request: Request, next: Next) -> Response {
if request
.extensions_mut()
.remove::<OrdinaryOptions>()
.is_some()
{
*request.method_mut() = Method::OPTIONS;
}
next.run(request).await
}
#[cfg(test)]
mod tests;