use actix_form_data::{Error, Field, Form, FormData, Multipart, Value}; use actix_web::{ http::StatusCode, middleware::Logger, web::{post, resource, Bytes}, App, HttpResponse, HttpServer, ResponseError, }; use futures_util::stream::{Stream, StreamExt, TryStreamExt}; use std::{ env, path::PathBuf, pin::Pin, sync::{ atomic::{AtomicUsize, Ordering}, Arc, }, }; use tracing::info; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] struct JsonError { msg: String, } impl From for JsonError where T: std::error::Error, { fn from(e: T) -> Self { JsonError { msg: format!("{}", e), } } } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, thiserror::Error)] #[error("Errors occurred")] struct Errors { errors: Vec, } impl From for Errors { fn from(e: JsonError) -> Self { Errors { errors: vec![e] } } } impl ResponseError for Errors { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().json(self) } } struct UploadedContent(Value); impl FormData for UploadedContent { type Config = Arc; type Item = PathBuf; type Error = Errors; fn form(file_count: Option<&Self::Config>) -> Form { let file_count = Arc::clone(file_count.expect("Set config")); Form::new() .field("Hey", Field::text()) .field( "Hi", Field::map() .field("One", Field::int()) .field("Two", Field::float()) .finalize(), ) .field( "files", Field::array(Field::file(move |_filename, _content_type, stream| { let count = file_count.fetch_add(1, Ordering::Relaxed); async move { save_file(stream, count) .await .map(PathBuf::from) .map_err(Errors::from) } })), ) } fn extract(value: Value) -> Result where Self: Sized, { Ok(UploadedContent(value)) } } async fn upload(Multipart(UploadedContent(value)): Multipart) -> HttpResponse { info!("Uploaded Content: {:#?}", value); HttpResponse::Created().finish() } async fn save_file( stream: Pin>>>, count: usize, ) -> Result { use futures_lite::io::AsyncWriteExt; let mut stream = stream.err_into::(); let filename = format!("examples/filename{}.png", count); let mut file = async_fs::File::create(&filename).await?; while let Some(res) = stream.next().await { let bytes = res?; file.write_all(&bytes).await?; } file.flush().await?; Ok(filename) } #[actix_rt::main] async fn main() -> Result<(), anyhow::Error> { if env::var("RUST_LOG").is_err() { env::set_var("RUST_LOG", "upload=debug,actix_form_data=debug"); } tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .init(); let file_count = Arc::new(AtomicUsize::new(0)); HttpServer::new(move || { App::new() .wrap(Logger::default()) .app_data(file_count.clone()) .service(resource("/upload").route(post().to(upload))) }) .bind("127.0.0.1:8080")? .run() .await?; Ok(()) }