Skip to content

Commit 8c45200

Browse files
Implemented reqwest integration
Signed-off-by: Francesco Guardiani <[email protected]>
1 parent 4814505 commit 8c45200

File tree

8 files changed

+1022
-2
lines changed

8 files changed

+1022
-2
lines changed

Cargo.lock

Lines changed: 538 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ name = "cloudevents"
3333
[workspace]
3434
members = [
3535
".",
36-
"cloudevents-sdk-actix-web"
36+
"cloudevents-sdk-actix-web",
37+
"cloudevents-sdk-reqwest"
3738
]

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Work in progress SDK for [CloudEvents](https://github.com/cloudevents/spec)
2020

2121
* `cloudevents-sdk`: Provides Event data structure, JSON Event format implementation
2222
* `cloudevents-sdk-actix-web`: Integration with [Actix Web](https://github.com/actix/actix-web)
23+
* `cloudevents-sdk-reqwest`: Integration with [reqwest](https://github.com/seanmonstar/reqwest)
2324

2425
## Development & Contributing
2526

cloudevents-sdk-reqwest/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "cloudevents-sdk-reqwest"
3+
version = "0.1.0"
4+
authors = ["Francesco Guardiani <[email protected]>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
cloudevents-sdk = { path = ".." }
11+
reqwest = { version = "0.10" }
12+
lazy_static = "1.4.0"
13+
bytes = "^0.5"
14+
serde_json = "^1.0"
15+
url = { version = "^2.1", features = ["serde"] }
16+
17+
[dev-dependencies]
18+
mockito = "0.25.1"
19+
tokio = { version = "^0.2", features = ["full"] }
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
use super::headers;
2+
use cloudevents::event::SpecVersion;
3+
use cloudevents::message::{
4+
BinaryDeserializer, BinarySerializer, MessageAttributeValue,
5+
Result, StructuredSerializer,
6+
};
7+
use cloudevents::Event;
8+
use reqwest::RequestBuilder;
9+
use std::str::FromStr;
10+
11+
/// Wrapper for [`RequestBuilder`] that implements [`StructuredSerializer`] & [`BinarySerializer`] traits
12+
pub struct RequestSerializer {
13+
req: RequestBuilder,
14+
}
15+
16+
impl RequestSerializer {
17+
pub fn new(req: RequestBuilder) -> RequestSerializer {
18+
RequestSerializer { req }
19+
}
20+
}
21+
22+
impl BinarySerializer<RequestBuilder> for RequestSerializer {
23+
fn set_spec_version(mut self, spec_version: SpecVersion) -> Result<Self> {
24+
self.req = self
25+
.req
26+
.header(headers::SPEC_VERSION_HEADER.clone(), spec_version.as_str());
27+
Ok(self)
28+
}
29+
30+
fn set_attribute(mut self, name: &str, value: MessageAttributeValue) -> Result<Self> {
31+
self.req = self.req.header(
32+
headers::ATTRIBUTES_TO_HEADERS.get(name).unwrap().clone(),
33+
value.to_string(),
34+
);
35+
Ok(self)
36+
}
37+
38+
fn set_extension(mut self, name: &str, value: MessageAttributeValue) -> Result<Self> {
39+
self.req = self
40+
.req
41+
.header(attribute_name_to_header!(name)?, value.to_string());
42+
Ok(self)
43+
}
44+
45+
fn end_with_data(self, bytes: Vec<u8>) -> Result<RequestBuilder> {
46+
Ok(self.req.body(bytes))
47+
}
48+
49+
fn end(self) -> Result<RequestBuilder> {
50+
Ok(self.req)
51+
}
52+
}
53+
54+
impl StructuredSerializer<RequestBuilder> for RequestSerializer {
55+
fn set_structured_event(self, bytes: Vec<u8>) -> Result<RequestBuilder> {
56+
Ok(self
57+
.req
58+
.header(
59+
reqwest::header::CONTENT_TYPE,
60+
headers::CLOUDEVENTS_JSON_HEADER.clone(),
61+
)
62+
.body(bytes))
63+
}
64+
}
65+
66+
/// Method to transform an incoming [`HttpRequest`] to [`Event`]
67+
pub fn event_to_request(
68+
event: Event,
69+
request_builder: RequestBuilder,
70+
) -> Result<RequestBuilder> {
71+
BinaryDeserializer::deserialize_binary(event, RequestSerializer::new(request_builder))
72+
}
73+
74+
#[cfg(test)]
75+
mod tests {
76+
use super::*;
77+
use mockito::{mock, Matcher};
78+
79+
use cloudevents::EventBuilder;
80+
use serde_json::json;
81+
use url::Url;
82+
use cloudevents::message::StructuredDeserializer;
83+
84+
#[tokio::test]
85+
async fn test_request() {
86+
let url = mockito::server_url();
87+
let m = mock("POST", "/")
88+
.match_header("ce-specversion", "1.0")
89+
.match_header("ce-id", "0001")
90+
.match_header("ce-type", "example.test")
91+
.match_header("ce-source", "http://localhost/")
92+
.match_header("ce-someint", "10")
93+
.match_body(Matcher::Missing)
94+
.create();
95+
96+
let input = EventBuilder::new()
97+
.id("0001")
98+
.ty("example.test")
99+
.source(Url::from_str("http://localhost/").unwrap())
100+
.extension("someint", "10")
101+
.build();
102+
103+
let client = reqwest::Client::new();
104+
event_to_request(input, client.post(&url))
105+
.unwrap()
106+
.send()
107+
.await
108+
.unwrap();
109+
110+
m.assert();
111+
}
112+
113+
#[tokio::test]
114+
async fn test_request_with_full_data() {
115+
let j = json!({"hello": "world"});
116+
117+
let url = mockito::server_url();
118+
let m = mock("POST", "/")
119+
.match_header("ce-specversion", "1.0")
120+
.match_header("ce-id", "0001")
121+
.match_header("ce-type", "example.test")
122+
.match_header("ce-source", "http://localhost/")
123+
.match_header("content-type", "application/json")
124+
.match_header("ce-someint", "10")
125+
.match_body(Matcher::Exact(j.to_string()))
126+
.create();
127+
128+
let input = EventBuilder::new()
129+
.id("0001")
130+
.ty("example.test")
131+
.source(Url::from_str("http://localhost").unwrap())
132+
.data("application/json", j.clone())
133+
.extension("someint", "10")
134+
.build();
135+
136+
let client = reqwest::Client::new();
137+
event_to_request(input, client.post(&url))
138+
.unwrap()
139+
.send()
140+
.await
141+
.unwrap();
142+
143+
m.assert();
144+
}
145+
146+
#[tokio::test]
147+
async fn test_structured_request_with_full_data() {
148+
let j = json!({"hello": "world"});
149+
150+
let input = EventBuilder::new()
151+
.id("0001")
152+
.ty("example.test")
153+
.source(Url::from_str("http://localhost").unwrap())
154+
.data("application/json", j.clone())
155+
.extension("someint", "10")
156+
.build();
157+
158+
let url = mockito::server_url();
159+
let m = mock("POST", "/")
160+
.match_header("content-type", "application/cloudevents+json")
161+
.match_body(Matcher::Exact(serde_json::to_string(&input).unwrap()))
162+
.create();
163+
164+
let client = reqwest::Client::new();
165+
StructuredDeserializer::deserialize_structured(
166+
input,
167+
RequestSerializer::new(client.post(&url))
168+
).unwrap()
169+
.send()
170+
.await
171+
.unwrap();
172+
173+
m.assert();
174+
}
175+
}

0 commit comments

Comments
 (0)