#![cfg(all(feature = "serverless-vercel", feature = "framework_axum", feature = "executor_tokio"))]
use std::prelude::v1::*;
use core::str::FromStr;
use axum::middleware::{self, Next};
use axum::response::Response as AxumResponse;
use product_os_router::{Body, Request, Uri};
use tower::ServiceBuilder;
use vercel_runtime::axum::VercelLayer;
use crate::ProductOSServer;
impl<S, E, X> ProductOSServer<S, E, X>
where
S: Clone + Send + Sync + 'static,
E: product_os_async_executor::Executor<X>
+ product_os_async_executor::ExecutorPerform<X>
+ product_os_async_executor::Timer
+ 'static,
{
pub fn finalize(&mut self) {
#[cfg(feature = "compression")]
self.ensure_compression();
}
pub fn into_axum_router(&mut self) -> product_os_router::Router {
self.finalize();
self.get_router().clone().into_axum_router()
}
pub async fn handle_request(
&mut self,
request: Request<Body>,
) -> product_os_router::Response<Body> {
let app = self.into_axum_router();
match tower::ServiceExt::oneshot(app, request).await {
Ok(response) => response,
Err(error) => {
tracing::error!("serverless handle_request failed: {error:?}");
product_os_router::Response::builder()
.status(product_os_router::StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::from("Internal Server Error"))
.unwrap_or_else(|_| product_os_router::Response::new(Body::empty()))
}
}
}
}
pub fn rewrite_vercel_api_path(request: Request<Body>) -> Request<Body> {
let path = request.uri().path();
if !matches!(path, "/api" | "/api/" | "/api/index") {
return request;
}
let Some(query) = request.uri().query() else {
return request;
};
for (key, value) in url::form_urlencoded::parse(query.as_bytes()) {
if key != "path" {
continue;
}
let api_path = if value.starts_with('/') {
format!("/api{value}")
} else {
format!("/api/{value}")
};
if let Ok(new_uri) = Uri::from_str(&api_path) {
let (mut parts, body) = request.into_parts();
parts.uri = new_uri;
return Request::from_parts(parts, body);
}
}
request
}
async fn vercel_path_rewrite_middleware(
request: Request<Body>,
next: Next,
) -> AxumResponse<Body> {
next.run(rewrite_vercel_api_path(request)).await
}
pub async fn run_vercel<S, E, X>(
server: &mut ProductOSServer<S, E, X>,
) -> Result<(), vercel_runtime::Error>
where
S: Clone + Send + Sync + 'static,
E: product_os_async_executor::Executor<X>
+ product_os_async_executor::ExecutorPerform<X>
+ product_os_async_executor::Timer
+ 'static,
{
let router = server
.into_axum_router()
.layer(middleware::from_fn(vercel_path_rewrite_middleware));
let app = ServiceBuilder::new()
.layer(VercelLayer::new())
.service(router);
vercel_runtime::run(app).await
}
#[cfg(test)]
mod tests {
use super::*;
use product_os_async_executor::TokioExecutor;
use product_os_router::{IntoResponse, StatusCode};
#[tokio::test]
async fn handle_request_routes_get() {
let mut server: ProductOSServer<(), TokioExecutor, ()> =
ProductOSServer::new_with_config(crate::ServerConfig::new());
server.add_get("/api/v1/health", || async {
(StatusCode::OK, "ok").into_response()
});
let response = server
.handle_request(
Request::builder()
.uri("/api/v1/health")
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(response.status(), StatusCode::OK);
}
#[test]
fn rewrite_vercel_query_path() {
let request = Request::builder()
.uri("/api?path=v1/health")
.body(Body::empty())
.unwrap();
let rewritten = rewrite_vercel_api_path(request);
assert_eq!(rewritten.uri().path(), "/api/v1/health");
}
}