cloudevents_sdk_reqwest/
client_request.rs

1use super::headers;
2use cloudevents::event::SpecVersion;
3use cloudevents::message::{
4    BinaryDeserializer, BinarySerializer, MessageAttributeValue, Result, StructuredSerializer,
5};
6use cloudevents::Event;
7use reqwest::RequestBuilder;
8use std::str::FromStr;
9
10/// Wrapper for [`RequestBuilder`] that implements [`StructuredSerializer`] & [`BinarySerializer`] traits.
11pub struct RequestSerializer {
12    req: RequestBuilder,
13}
14
15impl RequestSerializer {
16    pub fn new(req: RequestBuilder) -> RequestSerializer {
17        RequestSerializer { req }
18    }
19}
20
21impl 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
53impl 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`].
66pub 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.
73pub 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
78impl RequestBuilderExt for RequestBuilder {
79    fn event(self, event: Event) -> Result<RequestBuilder> {
80        event_to_request(event, self)
81    }
82}
83
84// Sealing the RequestBuilderExt
85mod private {
86    pub trait Sealed {}
87    impl Sealed for reqwest::RequestBuilder {}
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use mockito::{mock, Matcher};
94
95    use cloudevents::message::StructuredDeserializer;
96    use cloudevents::{EventBuilder, EventBuilderV10};
97    use serde_json::json;
98    use url::Url;
99
100    #[tokio::test]
101    async fn test_request() {
102        let url = mockito::server_url();
103        let m = mock("POST", "/")
104            .match_header("ce-specversion", "1.0")
105            .match_header("ce-id", "0001")
106            .match_header("ce-type", "example.test")
107            .match_header("ce-source", "http://localhost/")
108            .match_header("ce-someint", "10")
109            .match_body(Matcher::Missing)
110            .create();
111
112        let input = EventBuilderV10::new()
113            .id("0001")
114            .ty("example.test")
115            .source(Url::from_str("http://localhost/").unwrap())
116            .extension("someint", "10")
117            .build()
118            .unwrap();
119
120        let client = reqwest::Client::new();
121        client
122            .post(&url)
123            .event(input)
124            .unwrap()
125            .send()
126            .await
127            .unwrap();
128
129        m.assert();
130    }
131
132    #[tokio::test]
133    async fn test_request_with_full_data() {
134        let j = json!({"hello": "world"});
135
136        let url = mockito::server_url();
137        let m = mock("POST", "/")
138            .match_header("ce-specversion", "1.0")
139            .match_header("ce-id", "0001")
140            .match_header("ce-type", "example.test")
141            .match_header("ce-source", "http://localhost/")
142            .match_header("content-type", "application/json")
143            .match_header("ce-someint", "10")
144            .match_body(Matcher::Exact(j.to_string()))
145            .create();
146
147        let input = EventBuilderV10::new()
148            .id("0001")
149            .ty("example.test")
150            .source(Url::from_str("http://localhost").unwrap())
151            .data("application/json", j.clone())
152            .extension("someint", "10")
153            .build()
154            .unwrap();
155
156        let client = reqwest::Client::new();
157
158        client
159            .post(&url)
160            .event(input)
161            .unwrap()
162            .send()
163            .await
164            .unwrap();
165
166        m.assert();
167    }
168
169    #[tokio::test]
170    async fn test_structured_request_with_full_data() {
171        let j = json!({"hello": "world"});
172
173        let input = EventBuilderV10::new()
174            .id("0001")
175            .ty("example.test")
176            .source(Url::from_str("http://localhost").unwrap())
177            .data("application/json", j.clone())
178            .extension("someint", "10")
179            .build()
180            .unwrap();
181
182        let url = mockito::server_url();
183        let m = mock("POST", "/")
184            .match_header("content-type", "application/cloudevents+json")
185            .match_body(Matcher::Exact(serde_json::to_string(&input).unwrap()))
186            .create();
187
188        let client = reqwest::Client::new();
189        StructuredDeserializer::deserialize_structured(
190            input,
191            RequestSerializer::new(client.post(&url)),
192        )
193        .unwrap()
194        .send()
195        .await
196        .unwrap();
197
198        m.assert();
199    }
200}