http_request_derive/
lib.rs

1// SPDX-FileCopyrightText: OpenTalk GmbH <mail@opentalk.eu>
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! Attention: this crate is still under development.
6//!
7//! You can derive `HttpRequest` on a struct and annotate it with some attributes,
8//! so that it can be used to build a
9//! [`http::Request`](https://docs.rs/http/latest/http/request/struct.Request.html)
10//! which can then be sent to a server. In addition, a response type can be read from
11//! the received
12//! [`http::Response`](https://docs.rs/http/latest/http/response/struct.Response.html).
13//!
14//! ```
15//! use http_request_derive::HttpRequest;
16//! use serde::Deserialize;
17//!
18//! #[derive(HttpRequest)]
19//! #[http_request(method = "GET", response = MyResponse, path = "/books/{id}/abstract")]
20//! struct MyRequest {
21//!     #[http_request(query)]
22//!     query: String,
23//!
24//!     id: usize,
25//! }
26//!
27//! #[derive(Deserialize)]
28//! struct MyResponse {}
29//! ```
30
31#![deny(
32    bad_style,
33    dead_code,
34    improper_ctypes,
35    non_shorthand_field_patterns,
36    no_mangle_generic_items,
37    overflowing_literals,
38    path_statements,
39    patterns_in_fns_without_body,
40    private_interfaces,
41    private_bounds,
42    unconditional_recursion,
43    unused,
44    unused_allocation,
45    unused_comparisons,
46    unused_parens,
47    while_true,
48    missing_debug_implementations,
49    missing_docs,
50    trivial_casts,
51    trivial_numeric_casts,
52    unused_extern_crates,
53    unused_import_braces,
54    unused_qualifications,
55    unused_results
56)]
57
58#[allow(unused_extern_crates)]
59extern crate self as http_request_derive;
60
61mod error;
62mod from_http_response;
63mod http_request;
64mod http_request_body;
65mod http_request_query_params;
66
67#[doc(hidden)]
68pub mod __exports {
69    pub use http;
70}
71
72pub use error::Error;
73pub use from_http_response::FromHttpResponse;
74pub use http_request::HttpRequest;
75pub use http_request_body::HttpRequestBody;
76/// [`derive@HttpRequest`] can be derived by structs that needs to implement the [`HttpRequest`] trait.
77///
78/// It generates the request types (e.g. Response, Body or Query) and the getter functions
79///
80/// # Examples:
81///
82/// ## GET Endpoint with dynamic path
83///
84/// ```
85/// # use http_request_derive::HttpRequest;
86/// #[derive(HttpRequest)]
87/// #[http_request(
88///     method = "GET",
89///     response = GetItemResponse,
90///     path = "/v1/item/{0}"
91/// )]
92/// pub struct GetItemRequest(pub usize);
93///
94/// # #[derive(serde::Deserialize)]
95/// # pub struct GetItemResponse;
96/// ```
97///
98/// ## GET Endpoint with query parameters
99///
100/// ```
101/// # use http_request_derive::HttpRequest;
102/// # pub type PostEventInviteQuery = String;
103/// #[derive(HttpRequest)]
104/// #[http_request(
105///     method = "GET",
106///     response = String,
107///     path = "/v1/items"
108/// )]
109/// pub struct GetItemsWithQueryRequest {
110///     #[http_request(query)]
111///     pub query: PostEventInviteQuery,
112/// }
113/// ```
114///
115/// ## POST Endpoint with multiple attributes
116///
117/// ```
118/// # use http_request_derive::HttpRequest;
119/// # pub type SampleQuery = String;
120/// # pub type SampleBody = String;
121/// # pub type SampleResponse = String;
122/// #[derive(HttpRequest)]
123/// #[http_request(
124///     method = "POST",
125///     response = SampleResponse,
126///     path = "/v1/item/{id}"
127/// )]
128/// pub struct SampleRequest {
129///     pub id: usize,
130///
131///     #[http_request(query)]
132///     pub query: SampleQuery,
133///
134///     #[http_request(body)]
135///     pub body: SampleBody,
136///
137///     #[http_request(header)]
138///     pub header: http::HeaderMap,
139/// }
140/// ```
141pub use http_request_derive_macros::HttpRequest;
142pub use http_request_query_params::HttpRequestQueryParams;
143
144#[cfg(all(test, feature = "serde"))]
145mod tests {
146    use http::HeaderValue;
147    use pretty_assertions::assert_eq;
148    use serde::{Deserialize, Serialize};
149    use serde_json::json;
150    use url::Url;
151
152    use crate::HttpRequest;
153
154    #[test]
155    fn simple_get_request() {
156        #[derive(HttpRequest)]
157        #[http_request(method="GET", response = String, path = "/info")]
158        struct SimpleGetRequest;
159
160        let base_url = Url::parse("https://example.com/").expect("parse url");
161
162        let request = SimpleGetRequest;
163
164        let http_request = request
165            .to_http_request(&base_url)
166            .expect("build http request");
167
168        assert_eq!(&http_request.uri().to_string(), "https://example.com/info");
169        assert_eq!(http_request.method(), http::Method::GET);
170        assert!(http_request.into_body().is_empty());
171    }
172
173    #[test]
174    fn simple_json_post_request() {
175        #[derive(Serialize)]
176        struct MyRequestQuery {
177            start_x: usize,
178            end_x: usize,
179            start_y: usize,
180            end_y: usize,
181        }
182
183        #[derive(Serialize)]
184        struct MyRequestBody {
185            x: usize,
186            y: usize,
187        }
188
189        #[derive(HttpRequest)]
190        #[http_request(method = "POST", response = MyResponse, path = "/books/{id}/abstract")]
191        struct MyRequest {
192            #[http_request(query)]
193            query: MyRequestQuery,
194
195            #[http_request(body)]
196            body: MyRequestBody,
197
198            id: usize,
199        }
200
201        #[derive(Deserialize)]
202        struct MyResponse {}
203
204        let base_url = Url::parse("https://example.com/").expect("parse url");
205        let query = MyRequestQuery {
206            start_x: 10,
207            end_x: 50,
208            start_y: 20,
209            end_y: 30,
210        };
211        let body = MyRequestBody { x: 10, y: 20 };
212        let request = MyRequest {
213            query,
214            body,
215            id: 55,
216        };
217
218        let http_request = request
219            .to_http_request(&base_url)
220            .expect("build http request");
221
222        assert_eq!(
223            &http_request.uri().to_string(),
224            "https://example.com/books/55/abstract?start_x=10&end_x=50&start_y=20&end_y=30"
225        );
226        assert_eq!(http_request.method(), http::Method::POST);
227        assert_eq!(http_request.headers().len(), 1);
228        assert_eq!(
229            http_request.headers().get("content-type"),
230            Some(&HeaderValue::from_str("application/json").unwrap())
231        );
232
233        let http_request_body: serde_json::Value =
234            serde_json::from_slice(&http_request.into_body()).expect("json");
235        assert_eq!(http_request_body, json!({"x": 10, "y": 20}));
236    }
237}