spikard_http/testing/
multipart.rs1use std::time::{SystemTime, UNIX_EPOCH};
2
3#[derive(Debug, Clone)]
5pub struct MultipartFilePart {
6 pub field_name: String,
7 pub filename: String,
8 pub content_type: Option<String>,
9 pub content: Vec<u8>,
10}
11
12pub fn build_multipart_body(form_fields: &[(String, String)], files: &[MultipartFilePart]) -> (Vec<u8>, String) {
14 let boundary = generate_boundary();
15 let mut body = Vec::new();
16
17 for (name, value) in form_fields {
18 body.extend_from_slice(b"--");
19 body.extend_from_slice(boundary.as_bytes());
20 body.extend_from_slice(b"\r\n");
21 body.extend_from_slice(b"Content-Disposition: form-data; name=\"");
22 body.extend_from_slice(name.as_bytes());
23 body.extend_from_slice(b"\"\r\n\r\n");
24 body.extend_from_slice(value.as_bytes());
25 body.extend_from_slice(b"\r\n");
26 }
27
28 for file in files {
29 body.extend_from_slice(b"--");
30 body.extend_from_slice(boundary.as_bytes());
31 body.extend_from_slice(b"\r\n");
32 body.extend_from_slice(b"Content-Disposition: form-data; name=\"");
33 body.extend_from_slice(file.field_name.as_bytes());
34 body.extend_from_slice(b"\"; filename=\"");
35 body.extend_from_slice(file.filename.as_bytes());
36 body.extend_from_slice(b"\"\r\n");
37 if let Some(content_type) = &file.content_type {
38 body.extend_from_slice(b"Content-Type: ");
39 body.extend_from_slice(content_type.as_bytes());
40 body.extend_from_slice(b"\r\n");
41 }
42 body.extend_from_slice(b"\r\n");
43 body.extend_from_slice(&file.content);
44 body.extend_from_slice(b"\r\n");
45 }
46
47 body.extend_from_slice(b"--");
48 body.extend_from_slice(boundary.as_bytes());
49 body.extend_from_slice(b"--\r\n");
50
51 (body, boundary)
52}
53
54fn generate_boundary() -> String {
55 let nanos = SystemTime::now()
56 .duration_since(UNIX_EPOCH)
57 .map(|duration| duration.as_nanos())
58 .unwrap_or_default();
59 format!("spikard-boundary-{nanos}")
60}