Skip to content

Commit 02146fc

Browse files
dglsparsonsecklf
andauthored
Add adapter for Axum -> Vercel (#164)
* Add adapter for Axum -> Vercel * Apply suggestions from code review Co-authored-by: Florentin / 珞辰 <[email protected]> --------- Co-authored-by: Florentin / 珞辰 <[email protected]>
1 parent e712b1f commit 02146fc

File tree

6 files changed

+216
-20
lines changed

6 files changed

+216
-20
lines changed

Diff for: Cargo.lock

+100-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ members = [
44
"crates/vercel_runtime",
55
"crates/vercel_runtime_macro",
66
"crates/vercel_runtime_router",
7+
"crates/vercel_axum",
78
"examples/cron",
89
"examples/nextjs",
910
"examples/simple",

Diff for: crates/vercel_axum/Cargo.toml

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "vercel_axum"
3+
version = "1.1.3"
4+
edition = "2021"
5+
authors = ["Vercel <[email protected]>"]
6+
description = "Vercel Rust Axum Adapter"
7+
keywords = ["Vercel", "Rust", "Serverless", "Functions", "Axum"]
8+
license = "MIT"
9+
homepage = "https://github.com/vercel-community/rust"
10+
repository = "https://github.com/vercel-community/rust"
11+
documentation = "https://docs.rs/vercel_lambda"
12+
include = ["src/*.rs", "Cargo.toml"]
13+
exclude = ["tests/*"]
14+
15+
[dependencies]
16+
axum = "0.7"
17+
base64 = "0.22"
18+
http-body-util = "0.1"
19+
tower = "0.4"
20+
tower-service = "0.3"
21+
serde_json = "1.0"
22+
vercel_runtime = "1.1.3"
23+
# vercel_runtime = { version = "1.1.3", path = "../vercel_runtime" }

Diff for: crates/vercel_axum/src/lib.rs

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use axum::response::IntoResponse;
2+
use base64::prelude::*;
3+
use http_body_util::BodyExt;
4+
use std::{future::Future, pin::Pin};
5+
use tower::Layer;
6+
use tower_service::Service;
7+
8+
use vercel_runtime::request::{Event, VercelRequest};
9+
use vercel_runtime::response::EventResponse;
10+
11+
#[derive(Clone, Copy)]
12+
pub struct VercelLayer;
13+
14+
impl<S> Layer<S> for VercelLayer {
15+
type Service = VercelService<S>;
16+
17+
fn layer(&self, inner: S) -> Self::Service {
18+
VercelService { inner }
19+
}
20+
}
21+
22+
pub struct VercelService<S> {
23+
inner: S,
24+
}
25+
26+
impl<S> Service<Event<'_>> for VercelService<S>
27+
where
28+
S: Service<axum::http::Request<axum::body::Body>>,
29+
S::Response: axum::response::IntoResponse + Send + 'static,
30+
S::Error: std::error::Error + Send + Sync + 'static,
31+
S::Future: Send + 'static,
32+
{
33+
type Response = EventResponse;
34+
type Error = vercel_runtime::Error;
35+
type Future =
36+
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
37+
38+
fn poll_ready(
39+
&mut self,
40+
cx: &mut std::task::Context<'_>,
41+
) -> std::task::Poll<Result<(), Self::Error>> {
42+
self.inner.poll_ready(cx).map_err(Into::into)
43+
}
44+
45+
fn call(&mut self, event: Event) -> Self::Future {
46+
let (event, _context) = event.into_parts();
47+
let request = serde_json::from_str::<VercelRequest>(&event.body).unwrap_or_default();
48+
49+
let mut builder = axum::http::request::Builder::new()
50+
.method(request.method)
51+
.uri(format!("https://{}{}", request.host, request.path));
52+
for (key, value) in request.headers {
53+
if let Some(k) = key {
54+
builder = builder.header(k, value);
55+
}
56+
}
57+
58+
let request: axum::http::Request<axum::body::Body> = match (request.body, request.encoding)
59+
{
60+
(Some(b), Some(encoding)) if encoding == "base64" => {
61+
let engine = base64::prelude::BASE64_STANDARD;
62+
let body = axum::body::Body::from(engine.decode(b.as_ref()).unwrap_or_default());
63+
builder.body(body).unwrap_or_default()
64+
}
65+
(Some(b), _) => builder.body(axum::body::Body::from(b)).unwrap_or_default(),
66+
(None, _) => builder.body(axum::body::Body::default()).unwrap_or_default(),
67+
};
68+
69+
let fut = self.inner.call(request);
70+
let fut = async move {
71+
let resp = fut.await?;
72+
let (parts, body) = resp.into_response().into_parts();
73+
let bytes = body.into_data_stream().collect().await?.to_bytes();
74+
let bytes: &[u8] = &bytes;
75+
let body = std::str::from_utf8(bytes).unwrap_or_default();
76+
let body: Option<vercel_runtime::Body> = match body {
77+
"" => None,
78+
_ => Some(body.into()),
79+
};
80+
Ok(EventResponse {
81+
status_code: parts.status.as_u16(),
82+
body,
83+
headers: parts.headers,
84+
encoding: None,
85+
})
86+
};
87+
88+
Box::pin(fut)
89+
}
90+
}

Diff for: crates/vercel_runtime/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ serde = { version = "1.0.188", features = ["derive"] }
2626
serde_json = { version = "1.0.106", features = ["raw_value"] }
2727
tower-http = { version = "0.5", features = ["cors"] }
2828
tower-service = "0.3.2"
29+
http-serde = "2.1.1"
2930
base64 = "0.22"
3031
bytes = "1.5.0"
3132
async-trait = "0.1.73"

Diff for: crates/vercel_runtime/src/response.rs

+1-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use lambda_http::http::{
33
Response,
44
};
55
use lambda_http::Body;
6-
use serde::ser::{Error as SerError, SerializeMap, Serializer};
76
use serde::Serialize;
87

98
#[derive(Serialize, Debug)]
@@ -12,7 +11,7 @@ pub struct EventResponse {
1211
pub status_code: u16,
1312
#[serde(
1413
skip_serializing_if = "HeaderMap::is_empty",
15-
serialize_with = "serialize_headers"
14+
with = "http_serde::header_map"
1615
)]
1716
pub headers: HeaderMap<HeaderValue>,
1817
#[serde(skip_serializing_if = "Option::is_none")]
@@ -32,18 +31,6 @@ impl Default for EventResponse {
3231
}
3332
}
3433

35-
fn serialize_headers<S>(headers: &HeaderMap<HeaderValue>, serializer: S) -> Result<S::Ok, S::Error>
36-
where
37-
S: Serializer,
38-
{
39-
let mut map = serializer.serialize_map(Some(headers.keys_len()))?;
40-
for key in headers.keys() {
41-
let map_value = headers[key].to_str().map_err(S::Error::custom)?;
42-
map.serialize_entry(key.as_str(), map_value)?;
43-
}
44-
map.end()
45-
}
46-
4734
impl<T> From<Response<T>> for EventResponse
4835
where
4936
T: Into<Body>,

0 commit comments

Comments
 (0)