http_client_multipart/
lib.rs

1//! Multipart request support for the `http-client` crate.
2//!
3//! This module provides functionality to create and send multipart requests using the `http-client` crate.
4//! It supports file uploads, form fields, and custom headers for each part.
5//!
6//! Example:
7//!
8//! ```rust
9//! # #[cfg(any(feature = "h1_client", feature = "docs"))]
10//! # use http_client::h1::H1Client as Client;
11//! # use http_client::{HttpClient, Request};
12//! # use http_types::{Method, Url};
13//! # use http_client_multipart::Multipart;
14//!
15//! # async fn run() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
16//! // Create a new multipart form.
17//! let mut multipart = Multipart::new();
18//!
19//! // Add a text field.
20//! multipart.add_text("name", "John Doe");
21//!
22//! // Add a file.
23//! multipart.add_file("avatar", "examples/avatar.jpg", None).await?;
24//!
25//! // Create a request.
26//! let url = "https://httpbin.org/post".parse::<Url>()?;
27//! let mut req = Request::new(Method::Post, url);
28//!
29//! // Set the multipart body.
30//! multipart.set_request(&mut req);
31//!
32//! // Create a client.
33//! # #[cfg(any(feature = "h1_client", feature = "docs"))]
34//! let client = Client::new();
35//!
36//! // Send the request.
37//! # #[cfg(any(feature = "h1_client", feature = "docs"))]
38//! let mut response = client.send(req).await?;
39//!
40//! // Read the response body.
41//! # #[cfg(any(feature = "h1_client", feature = "docs"))]
42//! let body = response.body_string().await?;
43//!
44//! // Print the response body.
45//! # #[cfg(any(feature = "h1_client", feature = "docs"))]
46//! println!("{}", body);
47//!
48//! # Ok(())
49//! # }
50//! ```
51use http_types::Request;
52
53mod encoding;
54mod multipart;
55mod part;
56mod reader_stream;
57
58pub use encoding::Encoding;
59pub use multipart::Multipart;
60
61pub type StreamChunk = std::result::Result<Vec<u8>, futures_lite::io::Error>;
62
63/// Generates a random boundary string.
64fn generate_boundary() -> String {
65    (0..30).map(|_| fastrand::alphanumeric()).collect()
66}
67
68// Extension trait for adding multipart functionality.
69pub trait RequestMultipartExt {
70    fn multipart(&mut self, multipart: Multipart);
71}
72
73impl RequestMultipartExt for Request {
74    fn multipart(&mut self, multipart: Multipart) {
75        multipart.set_request(self)
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use crate::multipart::Multipart;
82
83    use super::*;
84    use http_types::{Method, Result, Url};
85
86    #[async_std::test]
87    async fn test_multipart_text() -> Result<()> {
88        let mut multipart = Multipart::new();
89        multipart.add_text("name", "John Doe");
90        multipart.add_text("age", "42");
91
92        let mut req = Request::new(Method::Post, Url::parse("http://example.com")?);
93        multipart.set_request(&mut req);
94
95        let content_type = req.header("Content-Type").unwrap().last().as_str();
96        assert!(content_type.starts_with("multipart/form-data; boundary="));
97
98        let body = req.body_string().await?;
99        assert!(body.contains("John Doe"));
100        assert!(body.contains("42"));
101        Ok(())
102    }
103
104    #[async_std::test]
105    async fn test_multipart_file() -> Result<()> {
106        let mut multipart = Multipart::new();
107        multipart.add_file("avatar", "Cargo.toml", None).await?;
108
109        let mut req = Request::new(Method::Post, Url::parse("http://example.com")?);
110        multipart.set_request(&mut req);
111
112        let content_type = req.header("Content-Type").unwrap().last().as_str();
113        assert!(content_type.starts_with("multipart/form-data; boundary="));
114
115        let body = req.body_string().await?;
116        assert!(body.contains("[package]"));
117        Ok(())
118    }
119
120    #[async_std::test]
121    async fn test_multipart_mixed() -> Result<()> {
122        let mut multipart = Multipart::new();
123        multipart.add_text("name", "John Doe");
124        multipart.add_file("avatar", "Cargo.toml", None).await?;
125
126        let mut req = Request::new(Method::Post, Url::parse("http://example.com")?);
127        multipart.set_request(&mut req);
128
129        let content_type = req.header("Content-Type").unwrap().last().as_str();
130        assert!(content_type.starts_with("multipart/form-data; boundary="));
131
132        let body = dbg!(req.body_string().await?);
133        assert!(body.contains("John Doe"));
134        assert!(body.contains("[package]"));
135        Ok(())
136    }
137
138    #[async_std::test]
139    async fn example_test() -> Result<()> {
140        use http_client::h1::H1Client as Client;
141        use http_client::HttpClient;
142
143        // Create a new multipart form.
144        let mut multipart = Multipart::new();
145
146        // Add a text field.
147        multipart.add_text("name", "John Doe");
148
149        // Add a file.
150        multipart.add_file("avatar", "Cargo.toml", None).await?;
151
152        // Create a request.
153        let url = "https://httpbin.org/post".parse::<Url>()?;
154        let mut req = Request::new(Method::Post, url);
155
156        // Set the multipart body.
157        multipart.set_request(&mut req);
158
159        // Create a client.
160        let client = Client::new();
161
162        // Send the request.
163        let mut response = client.send(req).await?;
164
165        // Read the response body.
166        let body = response.body_string().await?;
167
168        // Print the response body.
169        println!("{}", body);
170
171        Ok(())
172    }
173}