Skip to content

Commit 51b49f1

Browse files
committed
New feature: cloudevents-reqwest
Conditionally compile reqwest module when enabled This resulted in a naming conflict between my desired feature name, "reqwest", and the optional dependency itself. So I adopted the convention of prefixing the features with "cloudevents-". Signed-off-by: Jim Crossley <[email protected]>
1 parent 935234a commit 51b49f1

File tree

11 files changed

+585
-13
lines changed

11 files changed

+585
-13
lines changed

.github/workflows/rust_tests.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,13 @@ jobs:
9191
CC: musl-gcc
9292
CXX: g++
9393

94-
# If wasm, then we test only the main module and cloudevents-sdk-reqwest
9594
- uses: actions-rs/cargo@v1
9695
name: "Build"
9796
if: matrix.target == 'wasm32-unknown-unknown'
9897
with:
9998
command: build
10099
toolchain: ${{ matrix.toolchain }}
101-
args: --target wasm32-unknown-unknown --package cloudevents-sdk --package cloudevents-sdk-reqwest
100+
args: --target wasm32-unknown-unknown --package cloudevents-sdk
102101

103102
# Build examples
104103
- uses: actions-rs/cargo@v1

Cargo.toml

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

1919
[features]
2020
cloudevents-actix = ["actix-web", "async-trait", "lazy_static", "bytes", "futures"]
21+
cloudevents-reqwest = ["reqwest", "async-trait", "lazy_static", "bytes"]
2122

2223
[dependencies]
2324
serde = { version = "^1.0", features = ["derive"] }
@@ -29,8 +30,9 @@ url = { version = "^2.1", features = ["serde"] }
2930
snafu = "^0.6"
3031
bitflags = "^1.2"
3132

32-
# actix optional deps
33+
# runtime optional deps
3334
actix-web = { version = "^3", default-features = false, optional = true }
35+
reqwest = { version = "^0.11", default-features = false, features = ["rustls-tls"], optional = true }
3436
async-trait = { version = "^0.1.33", optional = true }
3537
lazy_static = { version = "1.4.0", optional = true }
3638
bytes = { version = "^1.0", optional = true }
@@ -50,11 +52,13 @@ claim = "0.3.1"
5052
version-sync = "^0.9"
5153
serde_yaml = "0.8"
5254

53-
# actix dev-deps
55+
# runtime dev-deps
5456
actix-rt = { version = "^1" }
5557
url = { version = "^2.1", features = ["serde"] }
5658
serde_json = { version = "^1.0" }
5759
chrono = { version = "^0.4", features = ["serde"] }
60+
mockito = "0.25.1"
61+
tokio = { version = "^1.0", features = ["full"] }
5862

5963
[workspace]
6064
members = [

cloudevents-sdk-reqwest/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ categories = ["web-programming", "encoding", "web-programming::http-client"]
1414

1515
[dependencies]
1616
async-trait = "^0.1.33"
17-
cloudevents-sdk = { path = ".." }
17+
cloudevents-sdk = { version = "0.3.0", path = ".." }
1818
lazy_static = "1.4.0"
1919
bytes = "^1.0"
2020

example-projects/reqwest-wasm-example/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ crate-type = ["cdylib"]
1111

1212
[dependencies]
1313
reqwest = "^0.11"
14-
cloudevents-sdk = { path = "../.." }
15-
cloudevents-sdk-reqwest = { path = "../../cloudevents-sdk-reqwest" }
14+
cloudevents-sdk = { path = "../..", features = ["cloudevents-reqwest"] }
1615
url = { version = "^2.1" }
1716
web-sys = { version = "0.3.39", features = ["Window", "Location"] }
1817
wasm-bindgen-futures = "0.4.12"

example-projects/reqwest-wasm-example/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
use cloudevents::reqwest::RequestBuilderExt;
12
use cloudevents::{EventBuilder, EventBuilderV10};
2-
use cloudevents_sdk_reqwest::RequestBuilderExt;
33
use wasm_bindgen::prelude::*;
44

55
#[wasm_bindgen]

src/actix/server_request.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use super::headers;
2-
use actix_web::http::HeaderName;
3-
use actix_web::web::{Bytes, BytesMut};
4-
use actix_web::{web, HttpMessage, HttpRequest};
5-
use async_trait::async_trait;
62
use crate::event::SpecVersion;
73
use crate::message::{
84
BinaryDeserializer, BinarySerializer, Encoding, MessageAttributeValue, MessageDeserializer,
95
Result, StructuredDeserializer, StructuredSerializer,
106
};
117
use crate::{message, Event};
8+
use actix_web::http::HeaderName;
9+
use actix_web::web::{Bytes, BytesMut};
10+
use actix_web::{web, HttpMessage, HttpRequest};
11+
use async_trait::async_trait;
1212
use futures::StreamExt;
1313
use std::convert::TryFrom;
1414

@@ -145,8 +145,8 @@ mod tests {
145145
use super::*;
146146
use actix_web::test;
147147

148-
use chrono::Utc;
149148
use crate::{EventBuilder, EventBuilderV10};
149+
use chrono::Utc;
150150
use serde_json::json;
151151

152152
#[actix_rt::test]

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
#[cfg(feature = "cloudevents-actix")]
4444
pub mod actix;
45+
#[cfg(feature = "cloudevents-reqwest")]
46+
pub mod reqwest;
4547

4648
pub mod event;
4749
pub mod message;

src/reqwest/client_request.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use super::headers;
2+
use crate::event::SpecVersion;
3+
use crate::message::{
4+
BinaryDeserializer, BinarySerializer, MessageAttributeValue, Result, StructuredSerializer,
5+
};
6+
use crate::Event;
7+
use reqwest::RequestBuilder;
8+
use std::str::FromStr;
9+
10+
/// Wrapper for [`RequestBuilder`] that implements [`StructuredSerializer`] & [`BinarySerializer`] traits.
11+
pub struct RequestSerializer {
12+
req: RequestBuilder,
13+
}
14+
15+
impl RequestSerializer {
16+
pub fn new(req: RequestBuilder) -> RequestSerializer {
17+
RequestSerializer { req }
18+
}
19+
}
20+
21+
impl BinarySerializer<RequestBuilder> for RequestSerializer {
22+
fn set_spec_version(mut self, spec_version: SpecVersion) -> Result<Self> {
23+
self.req = self
24+
.req
25+
.header(headers::SPEC_VERSION_HEADER.clone(), spec_version.as_str());
26+
Ok(self)
27+
}
28+
29+
fn set_attribute(mut self, name: &str, value: MessageAttributeValue) -> Result<Self> {
30+
self.req = self.req.header(
31+
headers::ATTRIBUTES_TO_HEADERS.get(name).unwrap().clone(),
32+
value.to_string(),
33+
);
34+
Ok(self)
35+
}
36+
37+
fn set_extension(mut self, name: &str, value: MessageAttributeValue) -> Result<Self> {
38+
self.req = self
39+
.req
40+
.header(attribute_name_to_header!(name)?, value.to_string());
41+
Ok(self)
42+
}
43+
44+
fn end_with_data(self, bytes: Vec<u8>) -> Result<RequestBuilder> {
45+
Ok(self.req.body(bytes))
46+
}
47+
48+
fn end(self) -> Result<RequestBuilder> {
49+
Ok(self.req)
50+
}
51+
}
52+
53+
impl StructuredSerializer<RequestBuilder> for RequestSerializer {
54+
fn set_structured_event(self, bytes: Vec<u8>) -> Result<RequestBuilder> {
55+
Ok(self
56+
.req
57+
.header(
58+
reqwest::header::CONTENT_TYPE,
59+
headers::CLOUDEVENTS_JSON_HEADER.clone(),
60+
)
61+
.body(bytes))
62+
}
63+
}
64+
65+
/// Method to fill a [`RequestBuilder`] with an [`Event`].
66+
pub fn event_to_request(event: Event, request_builder: RequestBuilder) -> Result<RequestBuilder> {
67+
BinaryDeserializer::deserialize_binary(event, RequestSerializer::new(request_builder))
68+
}
69+
70+
/// Extension Trait for [`RequestBuilder`] which acts as a wrapper for the function [`event_to_request()`].
71+
///
72+
/// This trait is sealed and cannot be implemented for types outside of this crate.
73+
pub trait RequestBuilderExt: private::Sealed {
74+
/// Write in this [`RequestBuilder`] the provided [`Event`]. Similar to invoking [`Event`].
75+
fn event(self, event: Event) -> Result<RequestBuilder>;
76+
}
77+
78+
impl RequestBuilderExt for RequestBuilder {
79+
fn event(self, event: Event) -> Result<RequestBuilder> {
80+
event_to_request(event, self)
81+
}
82+
}
83+
84+
// Sealing the RequestBuilderExt
85+
mod private {
86+
pub trait Sealed {}
87+
impl Sealed for reqwest::RequestBuilder {}
88+
}
89+
90+
#[cfg(test)]
91+
mod tests {
92+
use super::*;
93+
use mockito::{mock, Matcher};
94+
95+
use crate::message::StructuredDeserializer;
96+
use crate::{EventBuilder, EventBuilderV10};
97+
use serde_json::json;
98+
99+
#[tokio::test]
100+
async fn test_request() {
101+
let url = mockito::server_url();
102+
let m = mock("POST", "/")
103+
.match_header("ce-specversion", "1.0")
104+
.match_header("ce-id", "0001")
105+
.match_header("ce-type", "example.test")
106+
.match_header("ce-source", "http://localhost/")
107+
.match_header("ce-someint", "10")
108+
.match_body(Matcher::Missing)
109+
.create();
110+
111+
let input = EventBuilderV10::new()
112+
.id("0001")
113+
.ty("example.test")
114+
.source("http://localhost/")
115+
.extension("someint", "10")
116+
.build()
117+
.unwrap();
118+
119+
let client = reqwest::Client::new();
120+
client
121+
.post(&url)
122+
.event(input)
123+
.unwrap()
124+
.send()
125+
.await
126+
.unwrap();
127+
128+
m.assert();
129+
}
130+
131+
#[tokio::test]
132+
async fn test_request_with_full_data() {
133+
let j = json!({"hello": "world"});
134+
135+
let url = mockito::server_url();
136+
let m = mock("POST", "/")
137+
.match_header("ce-specversion", "1.0")
138+
.match_header("ce-id", "0001")
139+
.match_header("ce-type", "example.test")
140+
.match_header("ce-source", "http://localhost/")
141+
.match_header("content-type", "application/json")
142+
.match_header("ce-someint", "10")
143+
.match_body(Matcher::Exact(j.to_string()))
144+
.create();
145+
146+
let input = EventBuilderV10::new()
147+
.id("0001")
148+
.ty("example.test")
149+
.source("http://localhost/")
150+
.data("application/json", j.clone())
151+
.extension("someint", "10")
152+
.build()
153+
.unwrap();
154+
155+
let client = reqwest::Client::new();
156+
157+
client
158+
.post(&url)
159+
.event(input)
160+
.unwrap()
161+
.send()
162+
.await
163+
.unwrap();
164+
165+
m.assert();
166+
}
167+
168+
#[tokio::test]
169+
async fn test_structured_request_with_full_data() {
170+
let j = json!({"hello": "world"});
171+
172+
let input = EventBuilderV10::new()
173+
.id("0001")
174+
.ty("example.test")
175+
.source("http://localhost")
176+
.data("application/json", j.clone())
177+
.extension("someint", "10")
178+
.build()
179+
.unwrap();
180+
181+
let url = mockito::server_url();
182+
let m = mock("POST", "/")
183+
.match_header("content-type", "application/cloudevents+json")
184+
.match_body(Matcher::Exact(serde_json::to_string(&input).unwrap()))
185+
.create();
186+
187+
let client = reqwest::Client::new();
188+
StructuredDeserializer::deserialize_structured(
189+
input,
190+
RequestSerializer::new(client.post(&url)),
191+
)
192+
.unwrap()
193+
.send()
194+
.await
195+
.unwrap();
196+
197+
m.assert();
198+
}
199+
}

0 commit comments

Comments
 (0)