Add Axum binding
Signed-off-by: andrew webber (personal) <andrewvwebber@googlemail.com>
This commit is contained in:
parent
b540542040
commit
6074b4db7e
|
@ -129,3 +129,11 @@ jobs:
|
||||||
command: build
|
command: build
|
||||||
toolchain: ${{ matrix.toolchain }}
|
toolchain: ${{ matrix.toolchain }}
|
||||||
args: --target ${{ matrix.target }} --manifest-path ./example-projects/warp-example/Cargo.toml
|
args: --target ${{ matrix.target }} --manifest-path ./example-projects/warp-example/Cargo.toml
|
||||||
|
|
||||||
|
- uses: actions-rs/cargo@v1
|
||||||
|
name: "Build axum-example"
|
||||||
|
if: matrix.target == 'x86_64-unknown-linux-gnu' && matrix.toolchain == 'stable'
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
toolchain: ${{ matrix.toolchain }}
|
||||||
|
args: --target ${{ matrix.target }} --manifest-path ./example-projects/axum-example/Cargo.toml
|
||||||
|
|
|
@ -21,6 +21,7 @@ actix = ["actix-web", "async-trait", "bytes", "futures", "http"]
|
||||||
reqwest = ["reqwest-lib", "async-trait", "bytes", "http"]
|
reqwest = ["reqwest-lib", "async-trait", "bytes", "http"]
|
||||||
rdkafka = ["rdkafka-lib", "bytes", "futures"]
|
rdkafka = ["rdkafka-lib", "bytes", "futures"]
|
||||||
warp = ["warp-lib", "bytes", "http", "hyper"]
|
warp = ["warp-lib", "bytes", "http", "hyper"]
|
||||||
|
axum = ["bytes", "http", "hyper", "axum-lib", "http-body", "async-trait"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
|
@ -42,6 +43,8 @@ bytes = { version = "^1.0", optional = true }
|
||||||
futures = { version = "^0.3", optional = true }
|
futures = { version = "^0.3", optional = true }
|
||||||
http = { version = "0.2", optional = true }
|
http = { version = "0.2", optional = true }
|
||||||
hyper = { version = "^0.14", optional = true }
|
hyper = { version = "^0.14", optional = true }
|
||||||
|
axum-lib = { version = "^0.2", optional = true , package="axum"}
|
||||||
|
http-body = { version = "^0.4", optional = true}
|
||||||
|
|
||||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||||
hostname = "^0.3"
|
hostname = "^0.3"
|
||||||
|
@ -65,3 +68,4 @@ chrono = { version = "^0.4", features = ["serde"] }
|
||||||
mockito = "0.25.1"
|
mockito = "0.25.1"
|
||||||
tokio = { version = "^1.0", features = ["full"] }
|
tokio = { version = "^1.0", features = ["full"] }
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
tower = { version = "0.4", features = ["util"] }
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "axum-example"
|
||||||
|
version = "0.3.0"
|
||||||
|
authors = ["Andrew Webber <andrewvwebber@googlemail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cloudevents-sdk = { path = "../..", features = ["axum"] }
|
||||||
|
axum = "^0.2"
|
||||||
|
http = "^0.2"
|
||||||
|
tokio = { version = "^1", features = ["full"] }
|
||||||
|
tracing = "^0.1"
|
||||||
|
tracing-subscriber = "^0.2"
|
||||||
|
tower-http = { version = "^0.1", features = ["trace"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tower = { version = "^0.4", features = ["util"] }
|
||||||
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
|
serde_json = "^1.0"
|
||||||
|
chrono = { version = "^0.4", features = ["serde"] }
|
||||||
|
hyper = { version = "^0.14" }
|
|
@ -0,0 +1,115 @@
|
||||||
|
use axum::{
|
||||||
|
handler::{get, post},
|
||||||
|
routing::BoxRoute,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use cloudevents::Event;
|
||||||
|
use http::StatusCode;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
|
fn echo_app() -> Router<BoxRoute> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(|| async { "hello from cloudevents server" }))
|
||||||
|
.route(
|
||||||
|
"/",
|
||||||
|
post(|event: Event| async move {
|
||||||
|
tracing::debug!("received cloudevent {}", &event);
|
||||||
|
(StatusCode::OK, event)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.layer(TraceLayer::new_for_http())
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
if std::env::var("RUST_LOG").is_err() {
|
||||||
|
std::env::set_var("RUST_LOG", "axum_example=debug,tower_http=debug")
|
||||||
|
}
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
let service = echo_app();
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 8080));
|
||||||
|
tracing::debug!("listening on {}", addr);
|
||||||
|
axum::Server::bind(&addr)
|
||||||
|
.serve(service.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use super::echo_app;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{self, Request},
|
||||||
|
};
|
||||||
|
use chrono::Utc;
|
||||||
|
use hyper;
|
||||||
|
use serde_json::json;
|
||||||
|
use tower::ServiceExt; // for `app.oneshot()`
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn axum_mod_test() {
|
||||||
|
if std::env::var("RUST_LOG").is_err() {
|
||||||
|
std::env::set_var("RUST_LOG", "axum_example=debug,tower_http=debug")
|
||||||
|
}
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let app = echo_app();
|
||||||
|
let time = Utc::now();
|
||||||
|
let j = json!({"hello": "world"});
|
||||||
|
let request = Request::builder()
|
||||||
|
.method(http::Method::POST)
|
||||||
|
.header("ce-specversion", "1.0")
|
||||||
|
.header("ce-id", "0001")
|
||||||
|
.header("ce-type", "example.test")
|
||||||
|
.header("ce-source", "http://localhost/")
|
||||||
|
.header("ce-someint", "10")
|
||||||
|
.header("ce-time", time.to_rfc3339())
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.body(Body::from(serde_json::to_vec(&j).unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp = app.oneshot(request).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers()
|
||||||
|
.get("ce-specversion")
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"1.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-id").unwrap().to_str().unwrap(),
|
||||||
|
"0001"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-type").unwrap().to_str().unwrap(),
|
||||||
|
"example.test"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-source").unwrap().to_str().unwrap(),
|
||||||
|
"http://localhost/"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers()
|
||||||
|
.get("content-type")
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-someint").unwrap().to_str().unwrap(),
|
||||||
|
"10"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, body) = resp.into_parts();
|
||||||
|
let body = hyper::body::to_bytes(body).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(j.to_string().as_bytes(), body);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,143 @@
|
||||||
|
use axum_lib as axum;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use axum::extract::{FromRequest, RequestParts};
|
||||||
|
use http::StatusCode;
|
||||||
|
use http_body::Body;
|
||||||
|
use hyper::body;
|
||||||
|
|
||||||
|
use crate::binding::http::to_event;
|
||||||
|
use crate::event::Event;
|
||||||
|
|
||||||
|
type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl<B> FromRequest<B> for Event
|
||||||
|
where
|
||||||
|
B: Body + Send,
|
||||||
|
B::Data: Send,
|
||||||
|
B::Error: Into<BoxError>,
|
||||||
|
{
|
||||||
|
type Rejection = (StatusCode, String);
|
||||||
|
|
||||||
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
|
let headers = req.headers().cloned().ok_or(0).map_err(|_| {
|
||||||
|
(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"unexpected empty headers".to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let req_body = req
|
||||||
|
.take_body()
|
||||||
|
.ok_or(0)
|
||||||
|
.map_err(|_| (StatusCode::BAD_REQUEST, "unexpected empty body".to_string()))?;
|
||||||
|
|
||||||
|
let buf = body::to_bytes(req_body)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_REQUEST, format!("{}", e.into())))?;
|
||||||
|
to_event(&headers, buf.to_vec()).map_err(|e| (StatusCode::BAD_REQUEST, format!("{}", e)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use axum_lib as axum;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use axum::body::Body;
|
||||||
|
use axum::http::{self, Request, StatusCode};
|
||||||
|
use chrono::Utc;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{EventBuilder, EventBuilderV10};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn axum_test_request() {
|
||||||
|
let time = Utc::now();
|
||||||
|
let expected = EventBuilderV10::new()
|
||||||
|
.id("0001")
|
||||||
|
.ty("example.test")
|
||||||
|
.source("http://localhost/")
|
||||||
|
.time(time)
|
||||||
|
.extension("someint", "10")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut request = RequestParts::new(
|
||||||
|
Request::builder()
|
||||||
|
.method(http::Method::POST)
|
||||||
|
.header("ce-specversion", "1.0")
|
||||||
|
.header("ce-id", "0001")
|
||||||
|
.header("ce-type", "example.test")
|
||||||
|
.header("ce-source", "http://localhost/")
|
||||||
|
.header("ce-someint", "10")
|
||||||
|
.header("ce-time", time.to_rfc3339())
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = Event::from_request(&mut request).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn axum_test_bad_request() {
|
||||||
|
let time = Utc::now();
|
||||||
|
|
||||||
|
let mut request = RequestParts::new(
|
||||||
|
Request::builder()
|
||||||
|
.method(http::Method::POST)
|
||||||
|
.header("ce-specversion", "BAD SPECIFICATION")
|
||||||
|
.header("ce-id", "0001")
|
||||||
|
.header("ce-type", "example.test")
|
||||||
|
.header("ce-source", "http://localhost/")
|
||||||
|
.header("ce-someint", "10")
|
||||||
|
.header("ce-time", time.to_rfc3339())
|
||||||
|
.body(Body::empty())
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = Event::from_request(&mut request).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
let rejection = result.unwrap_err();
|
||||||
|
|
||||||
|
let reason = rejection.0;
|
||||||
|
assert_eq!(reason, StatusCode::BAD_REQUEST)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn axum_test_request_with_full_data() {
|
||||||
|
let time = Utc::now();
|
||||||
|
let j = json!({"hello": "world"});
|
||||||
|
|
||||||
|
let expected = EventBuilderV10::new()
|
||||||
|
.id("0001")
|
||||||
|
.ty("example.test")
|
||||||
|
.source("http://localhost")
|
||||||
|
.time(time)
|
||||||
|
.data("application/json", j.to_string().into_bytes())
|
||||||
|
.extension("someint", "10")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut request = RequestParts::new(
|
||||||
|
Request::builder()
|
||||||
|
.method(http::Method::POST)
|
||||||
|
.header("ce-specversion", "1.0")
|
||||||
|
.header("ce-id", "0001")
|
||||||
|
.header("ce-type", "example.test")
|
||||||
|
.header("ce-source", "http://localhost")
|
||||||
|
.header("ce-someint", "10")
|
||||||
|
.header("ce-time", time.to_rfc3339())
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.body(Body::from(serde_json::to_vec(&j).unwrap()))
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = Event::from_request(&mut request).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(expected, result);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
//! This module integrates the [cloudevents-sdk](https://docs.rs/cloudevents-sdk) with [Axum web service framework](https://docs.rs/axum/)
|
||||||
|
//! to easily send and receive CloudEvents.
|
||||||
|
//!
|
||||||
|
//! To deserialize an HTTP request as CloudEvent
|
||||||
|
//!
|
||||||
|
//! To echo events:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use axum_lib as axum;
|
||||||
|
//! use axum::{
|
||||||
|
//! handler::{get, post},
|
||||||
|
//! routing::BoxRoute,
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//! use cloudevents::Event;
|
||||||
|
//! use http::StatusCode;
|
||||||
|
//!
|
||||||
|
//! fn app() -> Router<BoxRoute> {
|
||||||
|
//! Router::new()
|
||||||
|
//! .route("/", get(|| async { "hello from cloudevents server" }))
|
||||||
|
//! .route(
|
||||||
|
//! "/",
|
||||||
|
//! post(|event: Event| async move {
|
||||||
|
//! println!("received cloudevent {}", &event);
|
||||||
|
//! (StatusCode::OK, event)
|
||||||
|
//! }),
|
||||||
|
//! )
|
||||||
|
//! .boxed()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! To create event inside request handlers and send them as responses:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use axum_lib as axum;
|
||||||
|
//! use axum::{
|
||||||
|
//! handler::{get, post},
|
||||||
|
//! routing::BoxRoute,
|
||||||
|
//! Router,
|
||||||
|
//! };
|
||||||
|
//! use cloudevents::{Event, EventBuilder, EventBuilderV10};
|
||||||
|
//! use http::StatusCode;
|
||||||
|
//! use serde_json::json;
|
||||||
|
//!
|
||||||
|
//! fn app() -> Router<BoxRoute> {
|
||||||
|
//! Router::new()
|
||||||
|
//! .route("/", get(|| async { "hello from cloudevents server" }))
|
||||||
|
//! .route(
|
||||||
|
//! "/",
|
||||||
|
//! post(|| async move {
|
||||||
|
//! let event = EventBuilderV10::new()
|
||||||
|
//! .id("1")
|
||||||
|
//! .source("url://example_response/")
|
||||||
|
//! .ty("example.ce")
|
||||||
|
//! .data(
|
||||||
|
//! mime::APPLICATION_JSON.to_string(),
|
||||||
|
//! json!({
|
||||||
|
//! "name": "John Doe",
|
||||||
|
//! "age": 43,
|
||||||
|
//! "phones": [
|
||||||
|
//! "+44 1234567",
|
||||||
|
//! "+44 2345678"
|
||||||
|
//! ]
|
||||||
|
//! }),
|
||||||
|
//! )
|
||||||
|
//! .build()
|
||||||
|
//! .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
//!
|
||||||
|
//! Ok::<Event, (StatusCode, String)>(event)
|
||||||
|
//! }),
|
||||||
|
//! )
|
||||||
|
//! .boxed()
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
pub mod extract;
|
||||||
|
pub mod response;
|
||||||
|
mod server_response;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use axum_lib as axum;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
handler::{get, post},
|
||||||
|
http::{self, Request, StatusCode},
|
||||||
|
routing::BoxRoute,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use chrono::Utc;
|
||||||
|
use serde_json::json;
|
||||||
|
use tower::ServiceExt; // for `app.oneshot()`
|
||||||
|
|
||||||
|
use crate::Event;
|
||||||
|
|
||||||
|
fn echo_app() -> Router<BoxRoute> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(|| async { "hello from cloudevents server" }))
|
||||||
|
.route(
|
||||||
|
"/",
|
||||||
|
post(|event: Event| async move {
|
||||||
|
println!("received cloudevent {}", &event);
|
||||||
|
(StatusCode::OK, event)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn axum_mod_test() {
|
||||||
|
let app = echo_app();
|
||||||
|
let time = Utc::now();
|
||||||
|
let j = json!({"hello": "world"});
|
||||||
|
let request = Request::builder()
|
||||||
|
.method(http::Method::POST)
|
||||||
|
.header("ce-specversion", "1.0")
|
||||||
|
.header("ce-id", "0001")
|
||||||
|
.header("ce-type", "example.test")
|
||||||
|
.header("ce-source", "http://localhost/")
|
||||||
|
.header("ce-someint", "10")
|
||||||
|
.header("ce-time", time.to_rfc3339())
|
||||||
|
.header("content-type", "application/json")
|
||||||
|
.body(Body::from(serde_json::to_vec(&j).unwrap()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp = app.oneshot(request).await.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers()
|
||||||
|
.get("ce-specversion")
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"1.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-id").unwrap().to_str().unwrap(),
|
||||||
|
"0001"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-type").unwrap().to_str().unwrap(),
|
||||||
|
"example.test"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-source").unwrap().to_str().unwrap(),
|
||||||
|
"http://localhost/"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers()
|
||||||
|
.get("content-type")
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-someint").unwrap().to_str().unwrap(),
|
||||||
|
"10"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, body) = resp.into_parts();
|
||||||
|
let body = hyper::body::to_bytes(body).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(j.to_string().as_bytes(), body);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,123 @@
|
||||||
|
use axum_lib as axum;
|
||||||
|
|
||||||
|
use axum::{body::Body, http::Response, response::IntoResponse};
|
||||||
|
use http::{header, StatusCode};
|
||||||
|
|
||||||
|
use super::server_response::event_to_response;
|
||||||
|
use crate::event::Event;
|
||||||
|
|
||||||
|
impl IntoResponse for Event {
|
||||||
|
type Body = Body;
|
||||||
|
type BodyError = <Self::Body as axum::body::HttpBody>::Error;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Body> {
|
||||||
|
match event_to_response(self) {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(err) => Response::builder()
|
||||||
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.header(header::CONTENT_TYPE, "text/plain")
|
||||||
|
.body(err.to_string().into())
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{EventBuilder, EventBuilderV10};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn axum_test_response() {
|
||||||
|
let input = EventBuilderV10::new()
|
||||||
|
.id("0001")
|
||||||
|
.ty("example.test")
|
||||||
|
.source("http://localhost/")
|
||||||
|
.extension("someint", "10")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp = input.into_response();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers()
|
||||||
|
.get("ce-specversion")
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"1.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-id").unwrap().to_str().unwrap(),
|
||||||
|
"0001"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-type").unwrap().to_str().unwrap(),
|
||||||
|
"example.test"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-source").unwrap().to_str().unwrap(),
|
||||||
|
"http://localhost/"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-someint").unwrap().to_str().unwrap(),
|
||||||
|
"10"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn axum_test_response_with_full_data() {
|
||||||
|
let j = json!({"hello": "world"});
|
||||||
|
|
||||||
|
let input = EventBuilderV10::new()
|
||||||
|
.id("0001")
|
||||||
|
.ty("example.test")
|
||||||
|
.source("http://localhost")
|
||||||
|
.data("application/json", j.clone())
|
||||||
|
.extension("someint", "10")
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let resp = input.into_response();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers()
|
||||||
|
.get("ce-specversion")
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"1.0"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-id").unwrap().to_str().unwrap(),
|
||||||
|
"0001"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-type").unwrap().to_str().unwrap(),
|
||||||
|
"example.test"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-source").unwrap().to_str().unwrap(),
|
||||||
|
"http://localhost"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers()
|
||||||
|
.get("content-type")
|
||||||
|
.unwrap()
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"application/json"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resp.headers().get("ce-someint").unwrap().to_str().unwrap(),
|
||||||
|
"10"
|
||||||
|
);
|
||||||
|
|
||||||
|
let (_, body) = resp.into_parts();
|
||||||
|
let body = hyper::body::to_bytes(body).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(j.to_string().as_bytes(), body);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
use axum_lib as axum;
|
||||||
|
|
||||||
|
use axum::{body::Body, http::Response};
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use crate::binding::http::{Builder, Serializer};
|
||||||
|
use crate::message::{BinaryDeserializer, Error, Result};
|
||||||
|
use crate::Event;
|
||||||
|
|
||||||
|
struct Adapter {
|
||||||
|
builder: Cell<http::response::Builder>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder<Response<Body>> for Adapter {
|
||||||
|
fn header(&mut self, key: &str, value: http::header::HeaderValue) {
|
||||||
|
self.builder.set(self.builder.take().header(key, value));
|
||||||
|
}
|
||||||
|
fn body(&mut self, bytes: Vec<u8>) -> Result<Response<Body>> {
|
||||||
|
self.builder
|
||||||
|
.take()
|
||||||
|
.body(Body::from(bytes))
|
||||||
|
.map_err(|e| crate::message::Error::Other {
|
||||||
|
source: Box::new(e),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn finish(&mut self) -> Result<Response<Body>> {
|
||||||
|
self.body(Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn event_to_response(event: Event) -> std::result::Result<Response<Body>, Error> {
|
||||||
|
BinaryDeserializer::deserialize_binary(
|
||||||
|
event,
|
||||||
|
Serializer::new(Adapter {
|
||||||
|
builder: Cell::new(http::Response::builder()),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
|
@ -2,7 +2,14 @@
|
||||||
|
|
||||||
#[cfg(feature = "actix")]
|
#[cfg(feature = "actix")]
|
||||||
pub mod actix;
|
pub mod actix;
|
||||||
#[cfg(any(feature = "actix", feature = "warp", feature = "reqwest"))]
|
#[cfg(feature = "axum")]
|
||||||
|
pub mod axum;
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "actix",
|
||||||
|
feature = "warp",
|
||||||
|
feature = "reqwest",
|
||||||
|
feature = "axum"
|
||||||
|
))]
|
||||||
pub mod http;
|
pub mod http;
|
||||||
#[cfg(feature = "rdkafka")]
|
#[cfg(feature = "rdkafka")]
|
||||||
pub mod rdkafka;
|
pub mod rdkafka;
|
||||||
|
|
|
@ -242,7 +242,7 @@ mod tests {
|
||||||
.id("0001")
|
.id("0001")
|
||||||
.ty("example.test")
|
.ty("example.test")
|
||||||
.source("http://localhost")
|
.source("http://localhost")
|
||||||
.data("application/json", j.clone())
|
.data("application/json", j)
|
||||||
.extension("someint", "10")
|
.extension("someint", "10")
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -35,8 +35,8 @@ impl fmt::Display for AttributeValue<'_> {
|
||||||
match self {
|
match self {
|
||||||
AttributeValue::SpecVersion(s) => s.fmt(f),
|
AttributeValue::SpecVersion(s) => s.fmt(f),
|
||||||
AttributeValue::String(s) => f.write_str(s),
|
AttributeValue::String(s) => f.write_str(s),
|
||||||
AttributeValue::URI(s) => f.write_str(&s.as_str()),
|
AttributeValue::URI(s) => f.write_str(s.as_str()),
|
||||||
AttributeValue::URIRef(s) => f.write_str(&s.as_str()),
|
AttributeValue::URIRef(s) => f.write_str(s.as_str()),
|
||||||
AttributeValue::Time(s) => f.write_str(&s.to_rfc3339()),
|
AttributeValue::Time(s) => f.write_str(&s.to_rfc3339()),
|
||||||
AttributeValue::Boolean(b) => f.serialize_bool(**b),
|
AttributeValue::Boolean(b) => f.serialize_bool(**b),
|
||||||
AttributeValue::Integer(i) => f.serialize_i64(**i),
|
AttributeValue::Integer(i) => f.serialize_i64(**i),
|
||||||
|
|
|
@ -235,7 +235,7 @@ mod tests {
|
||||||
|
|
||||||
let mut event = EventBuilderV03::new()
|
let mut event = EventBuilderV03::new()
|
||||||
.id(id)
|
.id(id)
|
||||||
.source(source.clone())
|
.source(source.to_string())
|
||||||
.ty(ty)
|
.ty(ty)
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
.time(time)
|
.time(time)
|
||||||
|
|
|
@ -235,7 +235,7 @@ mod tests {
|
||||||
|
|
||||||
let mut event = EventBuilderV10::new()
|
let mut event = EventBuilderV10::new()
|
||||||
.id(id)
|
.id(id)
|
||||||
.source(source.clone())
|
.source(source.to_string())
|
||||||
.ty(ty)
|
.ty(ty)
|
||||||
.subject(subject)
|
.subject(subject)
|
||||||
.time(time)
|
.time(time)
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
//! [Extractors] and [Responders]
|
//! [Extractors] and [Responders]
|
||||||
//! - `reqwest`: Enables the [`binding::reqwest`] protocol binding module.
|
//! - `reqwest`: Enables the [`binding::reqwest`] protocol binding module.
|
||||||
//! - `warp`: Enables the [`binding::warp`] protocol binding module.
|
//! - `warp`: Enables the [`binding::warp`] protocol binding module.
|
||||||
|
//! - `axum`: Enables the [`binding::axum`] protocol binding module.
|
||||||
//! - `rdkafka`: Enables the [`binding::rdkafka`] protocol binding module to
|
//! - `rdkafka`: Enables the [`binding::rdkafka`] protocol binding module to
|
||||||
//! seamlessly consume/produce cloudevents within Kafka messages.
|
//! seamlessly consume/produce cloudevents within Kafka messages.
|
||||||
//!
|
//!
|
||||||
|
|
Loading…
Reference in New Issue