#![allow(clippy::result_large_err)]
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use axum::body::Body;
use axum::extract::{FromRequestParts, Path, Request};
use axum::http::request::Parts;
use axum::response::{IntoResponse, Response};
use axum::routing::{Router, get, head, put};
use crate::backend::{ProxyReq, ProxyVerb, Securable};
use crate::handler::{StorageProxyHandler, parse_if_match, parse_range};
pub type ContextExtractor<Cx> = Arc<
dyn for<'a> Fn(&'a mut Parts) -> Pin<Box<dyn Future<Output = Result<Cx, Response>> + Send + 'a>>
+ Send
+ Sync
+ 'static,
>;
pub fn router_with_context_at<S, Cx>(
base: &str,
handler: Arc<dyn StorageProxyHandler<Cx>>,
extract_cx: ContextExtractor<Cx>,
) -> Router<S>
where
S: Clone + Send + Sync + 'static,
Cx: Send + 'static,
{
macro_rules! route {
($verb:expr, $method:ident) => {{
let handler = handler.clone();
let extract_cx = extract_cx.clone();
$method(move |req: Request| {
let handler = handler.clone();
let extract_cx = extract_cx.clone();
async move {
let (mut parts, body) = req.into_parts();
let cx = match extract_cx(&mut parts).await {
Ok(cx) => cx,
Err(resp) => return resp,
};
dispatch(&*handler, $verb, &mut parts, body, cx).await
}
})
}};
}
let p = |suffix: &str| format!("{base}{suffix}");
Router::new().route(
&p("/{securable}/{*key}"),
route!(ProxyVerb::Get, get)
.head(route!(ProxyVerb::Head, head))
.put(route!(ProxyVerb::Put, put)),
)
}
pub fn router_with_context<S, Cx>(
handler: Arc<dyn StorageProxyHandler<Cx>>,
extract_cx: ContextExtractor<Cx>,
) -> Router<S>
where
S: Clone + Send + Sync + 'static,
Cx: Send + 'static,
{
router_with_context_at("/storage-proxy", handler, extract_cx)
}
pub fn router_from_extension_at<S, Cx>(
base: &str,
handler: Arc<dyn StorageProxyHandler<Cx>>,
) -> Router<S>
where
S: Clone + Send + Sync + 'static,
Cx: Clone + Send + Sync + 'static,
{
let extract_cx: ContextExtractor<Cx> = Arc::new(|parts: &mut Parts| {
let cx = parts.extensions.get::<Cx>().cloned().ok_or_else(|| {
crate::error::ProxyError::Unauthenticated("missing request context".into())
.into_response()
});
Box::pin(async move { cx })
});
router_with_context_at(base, handler, extract_cx)
}
pub fn router_from_extension<S, Cx>(handler: Arc<dyn StorageProxyHandler<Cx>>) -> Router<S>
where
S: Clone + Send + Sync + 'static,
Cx: Clone + Send + Sync + 'static,
{
router_from_extension_at("/storage-proxy", handler)
}
async fn dispatch<Cx>(
handler: &dyn StorageProxyHandler<Cx>,
verb: ProxyVerb,
parts: &mut Parts,
body: Body,
cx: Cx,
) -> Response
where
Cx: Send + 'static,
{
let Path((securable_seg, key)) =
match Path::<(String, String)>::from_request_parts(parts, &()).await {
Ok(p) => p,
Err(rej) => return rej.into_response(),
};
let securable = match Securable::parse(&securable_seg) {
Ok(s) => s,
Err(e) => return e.into_response(),
};
let (range, if_match) = match verb {
ProxyVerb::Get => match parse_range(&parts.headers) {
Ok(r) => (r, None),
Err(e) => return e.into_response(),
},
ProxyVerb::Head => (None, None),
ProxyVerb::Put => match parse_if_match(&parts.headers) {
Ok(m) => (None, m),
Err(e) => return e.into_response(),
},
};
let req = ProxyReq {
verb,
securable,
key,
range,
if_match,
};
let result = match verb {
ProxyVerb::Get => handler.get(req, cx).await,
ProxyVerb::Head => handler.head(req, cx).await,
ProxyVerb::Put => handler.put(req, body, cx).await,
};
match result {
Ok(resp) => resp,
Err(e) => e.into_response(),
}
}