Skip to content

Commit 5a9f648

Browse files
authored
Update to actix-web 4 (#167)
* Compatiblity with actix-web 4 Fixes #114 Main challenge here was no longer being able to to construct web::Payload instances from dev::Payload instances. Instead, we now invoke the web::Payload FromRequest impl directly. Also adapted to the latest upstream test and body redesign. Macros are featurized now so enabled default features for the doc tests to pass. Signed-off-by: Jim Crossley <[email protected]>
1 parent 6af7d1e commit 5a9f648

File tree

7 files changed

+97
-73
lines changed

7 files changed

+97
-73
lines changed

Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ name = "cloudevents"
1818

1919
[features]
2020
http-binding = ["async-trait", "bytes", "futures", "http"]
21-
actix = ["actix-web", "async-trait", "bytes", "futures", "http"]
21+
actix = ["actix-web", "actix-http", "async-trait", "bytes", "futures", "http"]
2222
reqwest = ["reqwest-lib", "async-trait", "bytes", "http"]
2323
rdkafka = ["rdkafka-lib", "bytes", "futures"]
2424
warp = ["warp-lib", "bytes", "http", "hyper"]
@@ -36,7 +36,8 @@ snafu = "^0.6"
3636
bitflags = "^1.2"
3737

3838
# runtime optional deps
39-
actix-web = { version = "^3", default-features = false, optional = true }
39+
actix-web = { version = "4", optional = true }
40+
actix-http = { version = "3", optional = true }
4041
reqwest-lib = { version = "^0.11", default-features = false, features = ["rustls-tls"], optional = true, package = "reqwest" }
4142
rdkafka-lib = { version = "^0.28", features = ["cmake-build"], optional = true, package = "rdkafka" }
4243
warp-lib = { version = "^0.3", optional = true, package = "warp" }
@@ -67,7 +68,7 @@ version-sync = "0.9.2"
6768
serde_yaml = "0.8"
6869

6970
# runtime dev-deps
70-
actix-rt = { version = "^1" }
71+
actix-rt = { version = "^2" }
7172
url = { version = "^2.1", features = ["serde"] }
7273
serde_json = { version = "^1.0" }
7374
chrono = { version = "^0.4", features = ["serde"] }

example-projects/actix-web-example/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ edition = "2018"
66

77
[dependencies]
88
cloudevents-sdk = { path = "../..", features = ["actix"] }
9-
actix-web = "^3"
10-
actix-cors = "^0.5"
9+
actix-web = "4"
10+
actix-cors = "0.6.0-beta.8"
1111
serde_json = "^1.0"
1212
url = { version = "^2.1" }
1313
env_logger = "0.7.1"

example-projects/actix-web-example/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ async fn main() -> std::io::Result<()> {
2929

3030
HttpServer::new(|| {
3131
App::new()
32-
.wrap(actix_web::middleware::Logger::default())
3332
.wrap(actix_cors::Cors::permissive())
33+
.wrap(actix_web::middleware::Logger::default())
3434
.service(post_event)
3535
.service(get_event)
3636
})

src/binding/actix/mod.rs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
//! To deserialize an HTTP request as CloudEvent:
44
//!
55
//! ```
6+
//! use cloudevents::Event;
7+
//! use actix_web::post;
8+
//!
9+
//! #[post("/")]
10+
//! async fn post_event(event: Event) -> Result<String, actix_web::Error> {
11+
//! println!("Received Event: {:?}", event);
12+
//! Ok(format!("{:?}", event))
13+
//! }
14+
//! ```
15+
//!
16+
//! For more complex applications, access the Payload directly:
17+
//!
18+
//! ```
619
//! use cloudevents::binding::actix::HttpRequestExt;
720
//! use actix_web::{HttpRequest, web, post};
821
//!
@@ -17,14 +30,36 @@
1730
//! To serialize a CloudEvent to an HTTP response:
1831
//!
1932
//! ```
33+
//! use actix_web::get;
34+
//! use cloudevents::{Event, EventBuilderV10, EventBuilder};
35+
//! use serde_json::json;
36+
//!
37+
//! #[get("/")]
38+
//! async fn get_event() -> Event {
39+
//! let payload = json!({"hello": "world"});
40+
//!
41+
//! EventBuilderV10::new()
42+
//! .id("0001")
43+
//! .ty("example.test")
44+
//! .source("http://localhost/")
45+
//! .data("application/json", payload)
46+
//! .extension("someint", "10")
47+
//! .build()
48+
//! .unwrap()
49+
//! }
50+
//! ```
51+
//!
52+
//! For more complex applications, use the HTTP response builder extension:
53+
//!
54+
//! ```
2055
//! use cloudevents::binding::actix::HttpResponseBuilderExt;
21-
//! use actix_web::{HttpRequest, web, get, HttpResponse};
56+
//! use actix_web::{get, HttpResponse};
2257
//! use cloudevents::{EventBuilderV10, EventBuilder};
2358
//! use serde_json::json;
2459
//!
2560
//! #[get("/")]
2661
//! async fn get_event() -> Result<HttpResponse, actix_web::Error> {
27-
//! Ok(HttpResponse::Ok()
62+
//! HttpResponse::Ok()
2863
//! .event(
2964
//! EventBuilderV10::new()
3065
//! .id("0001")
@@ -34,8 +69,6 @@
3469
//! .build()
3570
//! .expect("No error while building the event"),
3671
//! )
37-
//! .await?
38-
//! )
3972
//! }
4073
//! ```
4174

src/binding/actix/server_request.rs

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use crate::binding::http::{to_event, Headers};
22
use crate::Event;
3+
use actix_web::dev::Payload;
34
use actix_web::web::BytesMut;
45
use actix_web::{web, HttpRequest};
56
use async_trait::async_trait;
6-
use futures::future::LocalBoxFuture;
7-
use futures::{FutureExt, StreamExt};
7+
use futures::{future::LocalBoxFuture, FutureExt, StreamExt};
88
use http::header::{AsHeaderName, HeaderName, HeaderValue};
99

1010
/// Implement Headers for the actix HeaderMap
11-
impl<'a> Headers<'a> for actix_web::http::HeaderMap {
11+
impl<'a> Headers<'a> for actix_http::header::HeaderMap {
1212
type Iterator = Box<dyn Iterator<Item = (&'a HeaderName, &'a HeaderValue)> + 'a>;
1313
fn get<K: AsHeaderName>(&self, key: K) -> Option<&HeaderValue> {
1414
self.get(key.as_str())
@@ -32,14 +32,18 @@ pub async fn request_to_event(
3232

3333
/// So that an actix-web handler may take an Event parameter
3434
impl actix_web::FromRequest for Event {
35-
type Config = ();
3635
type Error = actix_web::Error;
3736
type Future = LocalBoxFuture<'static, std::result::Result<Self, Self::Error>>;
3837

39-
fn from_request(r: &HttpRequest, p: &mut actix_web::dev::Payload) -> Self::Future {
40-
let payload = web::Payload(p.take());
38+
fn from_request(r: &HttpRequest, p: &mut Payload) -> Self::Future {
4139
let request = r.to_owned();
42-
async move { request_to_event(&request, payload).await }.boxed_local()
40+
bytes::Bytes::from_request(&request, p)
41+
.map(move |bytes| match bytes {
42+
Ok(b) => to_event(request.headers(), b.to_vec())
43+
.map_err(actix_web::error::ErrorBadRequest),
44+
Err(e) => Err(e),
45+
})
46+
.boxed_local()
4347
}
4448
}
4549

@@ -74,46 +78,52 @@ mod private {
7478
#[cfg(test)]
7579
mod tests {
7680
use super::*;
77-
use actix_web::test;
81+
use actix_web::{test, FromRequest};
7882

7983
use crate::test::fixtures;
8084
use serde_json::json;
85+
86+
async fn to_event(req: &HttpRequest, mut payload: Payload) -> Event {
87+
web::Payload::from_request(&req, &mut payload)
88+
.then(|p| req.to_event(p.unwrap()))
89+
.await
90+
.unwrap()
91+
}
92+
8193
#[actix_rt::test]
8294
async fn test_request() {
8395
let expected = fixtures::v10::minimal_string_extension();
8496

8597
let (req, payload) = test::TestRequest::post()
86-
.header("ce-specversion", "1.0")
87-
.header("ce-id", "0001")
88-
.header("ce-type", "test_event.test_application")
89-
.header("ce-source", "http://localhost/")
90-
.header("ce-someint", "10")
98+
.insert_header(("ce-specversion", "1.0"))
99+
.insert_header(("ce-id", "0001"))
100+
.insert_header(("ce-type", "test_event.test_application"))
101+
.insert_header(("ce-source", "http://localhost/"))
102+
.insert_header(("ce-someint", "10"))
91103
.to_http_parts();
92104

93-
let resp = req.to_event(web::Payload(payload)).await.unwrap();
94-
assert_eq!(expected, resp);
105+
assert_eq!(expected, to_event(&req, payload).await);
95106
}
96107

97108
#[actix_rt::test]
98109
async fn test_request_with_full_data() {
99110
let expected = fixtures::v10::full_binary_json_data_string_extension();
100111

101112
let (req, payload) = test::TestRequest::post()
102-
.header("ce-specversion", "1.0")
103-
.header("ce-id", "0001")
104-
.header("ce-type", "test_event.test_application")
105-
.header("ce-subject", "cloudevents-sdk")
106-
.header("ce-source", "http://localhost/")
107-
.header("ce-time", fixtures::time().to_rfc3339())
108-
.header("ce-string_ex", "val")
109-
.header("ce-int_ex", "10")
110-
.header("ce-bool_ex", "true")
111-
.header("content-type", "application/json")
113+
.insert_header(("ce-specversion", "1.0"))
114+
.insert_header(("ce-id", "0001"))
115+
.insert_header(("ce-type", "test_event.test_application"))
116+
.insert_header(("ce-subject", "cloudevents-sdk"))
117+
.insert_header(("ce-source", "http://localhost/"))
118+
.insert_header(("ce-time", fixtures::time().to_rfc3339()))
119+
.insert_header(("ce-string_ex", "val"))
120+
.insert_header(("ce-int_ex", "10"))
121+
.insert_header(("ce-bool_ex", "true"))
122+
.insert_header(("content-type", "application/json"))
112123
.set_json(&fixtures::json_data())
113124
.to_http_parts();
114125

115-
let resp = req.to_event(web::Payload(payload)).await.unwrap();
116-
assert_eq!(expected, resp);
126+
assert_eq!(expected, to_event(&req, payload).await);
117127
}
118128

119129
#[actix_rt::test]
@@ -136,11 +146,10 @@ mod tests {
136146
let expected = fixtures::v10::full_json_data_string_extension();
137147

138148
let (req, payload) = test::TestRequest::post()
139-
.header("content-type", "application/cloudevents+json")
149+
.insert_header(("content-type", "application/cloudevents+json"))
140150
.set_payload(bytes)
141151
.to_http_parts();
142152

143-
let resp = req.to_event(web::Payload(payload)).await.unwrap();
144-
assert_eq!(expected, resp);
153+
assert_eq!(expected, to_event(&req, payload).await);
145154
}
146155
}

src/binding/actix/server_response.rs

Lines changed: 13 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
11
use crate::binding::http::{Builder, Serializer};
22
use crate::message::{BinaryDeserializer, Result};
33
use crate::Event;
4-
use actix_web::dev::HttpResponseBuilder;
54
use actix_web::http::StatusCode;
6-
use actix_web::HttpResponse;
7-
use async_trait::async_trait;
8-
use futures::future::LocalBoxFuture;
9-
use futures::FutureExt;
5+
use actix_web::{HttpRequest, HttpResponse, HttpResponseBuilder};
106

117
impl Builder<HttpResponse> for HttpResponseBuilder {
128
fn header(&mut self, key: &str, value: http::header::HeaderValue) {
13-
self.set_header(key, value);
9+
self.insert_header((key, value));
1410
}
1511
fn body(&mut self, bytes: Vec<u8>) -> Result<HttpResponse> {
1612
Ok(HttpResponseBuilder::body(self, bytes))
@@ -21,7 +17,7 @@ impl Builder<HttpResponse> for HttpResponseBuilder {
2117
}
2218

2319
/// Method to fill an [`HttpResponseBuilder`] with an [`Event`].
24-
pub async fn event_to_response<T: Builder<HttpResponse> + 'static>(
20+
pub fn event_to_response<T: Builder<HttpResponse> + 'static>(
2521
event: Event,
2622
response: T,
2723
) -> std::result::Result<HttpResponse, actix_web::error::Error> {
@@ -31,40 +27,30 @@ pub async fn event_to_response<T: Builder<HttpResponse> + 'static>(
3127

3228
/// So that an actix-web handler may return an Event
3329
impl actix_web::Responder for Event {
34-
type Error = actix_web::Error;
35-
type Future = LocalBoxFuture<'static, std::result::Result<HttpResponse, Self::Error>>;
36-
37-
fn respond_to(self, _: &actix_web::HttpRequest) -> Self::Future {
38-
async { HttpResponse::build(StatusCode::OK).event(self).await }.boxed_local()
30+
type Body = actix_web::body::BoxBody;
31+
fn respond_to(self, _: &HttpRequest) -> HttpResponse {
32+
HttpResponse::build(StatusCode::OK).event(self).unwrap()
3933
}
4034
}
4135

4236
/// Extension Trait for [`HttpResponseBuilder`] which acts as a wrapper for the function [`event_to_response()`].
4337
///
4438
/// This trait is sealed and cannot be implemented for types outside of this crate.
45-
#[async_trait(?Send)]
4639
pub trait HttpResponseBuilderExt: private::Sealed {
4740
/// Fill this [`HttpResponseBuilder`] with an [`Event`].
48-
async fn event(
49-
self,
50-
event: Event,
51-
) -> std::result::Result<HttpResponse, actix_web::error::Error>;
41+
fn event(self, event: Event) -> std::result::Result<HttpResponse, actix_web::Error>;
5242
}
5343

54-
#[async_trait(?Send)]
5544
impl HttpResponseBuilderExt for HttpResponseBuilder {
56-
async fn event(
57-
self,
58-
event: Event,
59-
) -> std::result::Result<HttpResponse, actix_web::error::Error> {
60-
event_to_response(event, self).await
45+
fn event(self, event: Event) -> std::result::Result<HttpResponse, actix_web::Error> {
46+
event_to_response(event, self)
6147
}
6248
}
6349

6450
// Sealing the HttpResponseBuilderExt
6551
mod private {
6652
pub trait Sealed {}
67-
impl Sealed for actix_web::dev::HttpResponseBuilder {}
53+
impl Sealed for actix_web::HttpResponseBuilder {}
6854
}
6955

7056
#[cfg(test)]
@@ -74,15 +60,13 @@ mod tests {
7460
use crate::test::fixtures;
7561
use actix_web::http::StatusCode;
7662
use actix_web::test;
77-
use futures::TryStreamExt;
7863

7964
#[actix_rt::test]
8065
async fn test_response() {
8166
let input = fixtures::v10::minimal_string_extension();
8267

8368
let resp = HttpResponseBuilder::new(StatusCode::OK)
8469
.event(input)
85-
.await
8670
.unwrap();
8771

8872
assert_eq!(
@@ -115,9 +99,8 @@ mod tests {
11599
async fn test_response_with_full_data() {
116100
let input = fixtures::v10::full_binary_json_data_string_extension();
117101

118-
let mut resp = HttpResponseBuilder::new(StatusCode::OK)
102+
let resp = HttpResponseBuilder::new(StatusCode::OK)
119103
.event(input)
120-
.await
121104
.unwrap();
122105

123106
assert_eq!(
@@ -153,9 +136,7 @@ mod tests {
153136
"10"
154137
);
155138

156-
let bytes = test::load_stream(resp.take_body().into_stream())
157-
.await
158-
.unwrap();
159-
assert_eq!(fixtures::json_data_binary(), bytes.as_ref())
139+
let sr = test::TestRequest::default().to_srv_response(resp);
140+
assert_eq!(fixtures::json_data_binary(), test::read_body(sr).await);
160141
}
161142
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
//! - `actix`: Enables the [`binding::actix`] protocol binding module. This
4040
//! extends the [`actix_web::HttpRequest`] with a
4141
//! [`to_event`](binding::actix::HttpRequestExt::to_event) function, the
42-
//! [`actix_web::dev::HttpResponseBuilder`] with an
42+
//! [`actix_web::HttpResponseBuilder`] with an
4343
//! [`event`](binding::actix::HttpResponseBuilderExt::event) function,
4444
//! and implementations for [`actix_web::FromRequest`] and
4545
//! [`actix_web::Responder`] in order to take advantage of actix-web's

0 commit comments

Comments
 (0)