spikard_http/testing/
multipart.rs

1use std::time::{SystemTime, UNIX_EPOCH};
2
3/// File part metadata for multipart/form-data payloads.
4#[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
12/// Build a multipart/form-data body from fields and files.
13pub 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"\"");
35        if !file.filename.is_empty() {
36            body.extend_from_slice(b"; filename=\"");
37            body.extend_from_slice(file.filename.as_bytes());
38            body.extend_from_slice(b"\"");
39        }
40        body.extend_from_slice(b"\r\n");
41        if let Some(content_type) = &file.content_type {
42            body.extend_from_slice(b"Content-Type: ");
43            body.extend_from_slice(content_type.as_bytes());
44            body.extend_from_slice(b"\r\n");
45        }
46        body.extend_from_slice(b"\r\n");
47        body.extend_from_slice(&file.content);
48        body.extend_from_slice(b"\r\n");
49    }
50
51    body.extend_from_slice(b"--");
52    body.extend_from_slice(boundary.as_bytes());
53    body.extend_from_slice(b"--\r\n");
54
55    (body, boundary)
56}
57
58fn generate_boundary() -> String {
59    let nanos = SystemTime::now()
60        .duration_since(UNIX_EPOCH)
61        .map(|duration| duration.as_nanos())
62        .unwrap_or_default();
63    format!("spikard-boundary-{nanos}")
64}