Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

File Upload

AFast supports multipart/form-data file upload via the multer crate. Two extraction modes are available:

Raw Multipart (Multipart)

For direct access to the multipart stream:

#![allow(unused)]
fn main() {
use afast::{post, Multipart, HttpResult, Json, Tag};
use serde::Serialize;

#[derive(Serialize, Tag)]
#[tag("Upload result")]
struct UploadResult {
    filename: String,
    size: usize,
}

#[post(desc("Upload file"))]
async fn upload(mut form: Multipart) -> HttpResult<Json<UploadResult>> {
    let field = form.next_field().await?.ok_or_else(|| {
        afast::Error::custom(400, "no file provided")
    })?;
    let filename = field.file_name().unwrap_or("unknown").to_string();
    let data = field.bytes().await?;
    Ok(Json(UploadResult { filename, size: data.len() }))
}
}

Typed Extraction (MultipartForm<T>)

For automatic extraction into a struct using #[derive(FromFormData)]:

#![allow(unused)]
fn main() {
use afast::{post, MultipartForm, FileField, HttpResult, Json, Tag, FromFormData};
use serde::Serialize;

#[derive(FromFormData, Tag)]
#[tag("Upload form")]
struct UploadForm {
    description: String,
    file: FileField,
}

#[derive(Serialize, Tag)]
#[tag("Upload result")]
struct UploadResult {
    filename: String,
    description: String,
    size: usize,
}

#[post(desc("Upload with form data"))]
async fn upload_typed(form: MultipartForm<UploadForm>) -> HttpResult<Json<UploadResult>> {
    let data = form.0;
    Ok(Json(UploadResult {
        filename: data.file.filename.unwrap_or_else(|| "unknown".to_string()),
        description: data.description,
        size: data.file.bytes.len(),
    }))
}
}

#[derive(FromFormData)]

The derive macro automatically implements the FromFormData trait. Each struct field name must match the corresponding form field name.

Supported Field Types

Rust TypeForm FieldNotes
StringText fieldDirect text value
i8/i16/i32/i64/u8/u16/u32/u64/f32/f64Text fieldParsed from text
boolText field"true"/"false"/"1"/"0"
FileFieldFile fieldCollects bytes, filename, content type
Option<T>Optional fieldDefaults to None if missing

FileField Structure

#![allow(unused)]
fn main() {
pub struct FileField {
    pub name: String,                // Form field name
    pub filename: Option<String>,    // Original filename
    pub content_type: Option<String>, // MIME type
    pub bytes: Vec<u8>,              // File content
}
}

Client Code Generation

The TS/JS/KT code generators automatically produce FormData-based upload code:

// Generated TypeScript client
const formData = new FormData();
formData.append('description', 'My file');
formData.append('file', new Blob(['content']), 'test.txt');
const result = await client.apis.upload_typed({ body: formData });

Testing with curl

# Raw upload
curl -F "file=@test.txt" http://localhost:5001/upload

# Typed upload with multiple fields
curl -F "description=My file" -F "file=@test.txt" http://localhost:5001/upload/typed
Copied to clipboard!