diff --git a/services/backup/Cargo.lock b/services/backup/Cargo.lock --- a/services/backup/Cargo.lock +++ b/services/backup/Cargo.lock @@ -83,6 +83,44 @@ "syn 2.0.29", ] +[[package]] +name = "actix-multipart" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee489e3c01eae4d1c35b03c4493f71cb40d93f66b14558feb1b1a807671cc4e" +dependencies = [ + "actix-multipart-derive", + "actix-utils", + "actix-web", + "bytes", + "derive_more", + "futures-core", + "futures-util", + "httparse", + "local-waker", + "log", + "memchr", + "mime", + "serde", + "serde_json", + "serde_plain", + "tempfile", + "tokio", +] + +[[package]] +name = "actix-multipart-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec592f234db8a253cf80531246a4407c8a70530423eea80688a6c5a44a110e7" +dependencies = [ + "darling", + "parse-size", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "actix-router" version = "0.5.1" @@ -888,6 +926,9 @@ version = "0.1.0" dependencies = [ "actix-cors", + "actix-multipart", + "actix-web", + "anyhow", "aws-config", "aws-sdk-dynamodb", "aws-types", @@ -895,10 +936,12 @@ "derive_more", "futures-core", "futures-util", + "http", "reqwest", "serde", "serde_json", "tokio", + "tokio-stream", "tracing", ] @@ -963,6 +1006,41 @@ "typenum", ] +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "deranged" version = "0.3.8" @@ -1379,6 +1457,12 @@ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.4.0" @@ -1724,6 +1808,12 @@ "windows-targets", ] +[[package]] +name = "parse-size" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" + [[package]] name = "paste" version = "1.0.14" @@ -2162,6 +2252,15 @@ "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" diff --git a/services/blob/Cargo.lock b/services/blob/Cargo.lock --- a/services/blob/Cargo.lock +++ b/services/blob/Cargo.lock @@ -976,11 +976,18 @@ version = "0.1.0" dependencies = [ "actix-cors", + "actix-multipart", + "actix-web", + "anyhow", "aws-config", "aws-sdk-dynamodb", "aws-types", "chrono", "derive_more", + "futures-core", + "futures-util", + "http", + "tokio-stream", "tracing", ] diff --git a/services/blob/src/http/handlers/blob.rs b/services/blob/src/http/handlers/blob.rs --- a/services/blob/src/http/handlers/blob.rs +++ b/services/blob/src/http/handlers/blob.rs @@ -2,18 +2,16 @@ use crate::service::BlobService; use crate::validate_identifier; -use actix_web::error::{ - ErrorBadRequest, ErrorInternalServerError, ErrorRangeNotSatisfiable, -}; +use actix_web::error::{ErrorBadRequest, ErrorRangeNotSatisfiable}; use actix_web::{ http::header::{ByteRangeSpec, Range}, - web, Error as HttpError, HttpResponse, + web, HttpResponse, }; -use anyhow::Result; use async_stream::try_stream; +use comm_services_lib::http::multipart; use serde::{Deserialize, Serialize}; use tokio_stream::StreamExt; -use tracing::{debug, info, instrument, trace, warn}; +use tracing::{info, instrument, trace, warn}; use tracing_futures::Instrument; /// Returns a tuple of first and last byte number (inclusive) represented by given range header. @@ -137,31 +135,6 @@ Ok(HttpResponse::Ok().json(web::Json(AssignHolderResponnse { data_exists }))) } -async fn get_blob_hash_field( - multipart_payload: &mut actix_multipart::Multipart, -) -> Result { - let Some(mut field) = multipart_payload.try_next().await? else { - debug!("Malfolmed multipart request"); - return Err(ErrorBadRequest("Bad request")); - }; - - if field.name() != "blob_hash" { - warn!("Blob hash is required as a first form field"); - return Err(ErrorBadRequest("Bad request")); - } - - let mut buf = Vec::new(); - while let Some(chunk) = field.try_next().await? { - buf.extend_from_slice(&chunk); - } - - let blob_hash = String::from_utf8(buf) - .map_err(|_| ErrorInternalServerError("Internal error"))?; - - validate_identifier!(blob_hash); - return Ok(blob_hash); -} - #[instrument(skip_all, name = "upload_blob", fields(blob_hash))] pub async fn upload_blob_handler( service: web::Data, @@ -169,13 +142,22 @@ ) -> actix_web::Result { info!("Upload blob request"); - let blob_hash = get_blob_hash_field(&mut payload).await?; - debug!("Received blob_hash: {}", &blob_hash); + let Some((name, blob_hash)) = multipart::get_text_field(&mut payload).await? else { + warn!("Malformed request: expected a field."); + return Err(ErrorBadRequest("Bad request")); + }; + + if name != "blob_hash" { + warn!(name, "Malformed request: 'blob_hash' text field expected."); + return Err(ErrorBadRequest("Bad request")); + } + validate_identifier!(blob_hash); + tracing::Span::current().record("blob_hash", &blob_hash); trace!("Receiving blob data"); let stream = try_stream! { - while let Some(mut field) = payload.try_next().await.map_err(Box::new)? { + while let Some(mut field) = payload.try_next().await? { let field_name = field.name(); if field_name != "blob_data" { warn!( @@ -185,8 +167,8 @@ Err(ErrorBadRequest("Bad request"))?; } - while let Some(chunk) = field.try_next().await.map_err(Box::new)? { - yield chunk.to_vec(); + while let Some(chunk) = field.try_next().await? { + yield chunk; } } trace!("Stream done"); diff --git a/services/blob/src/service.rs b/services/blob/src/service.rs --- a/services/blob/src/service.rs +++ b/services/blob/src/service.rs @@ -4,6 +4,8 @@ use async_stream::try_stream; use chrono::Duration; +use comm_services_lib::http::ByteStream; +use comm_services_lib::tools::BoxedError; use tokio_stream::StreamExt; use tonic::codegen::futures_core::Stream; use tracing::{debug, error, trace, warn}; @@ -14,7 +16,7 @@ }; use crate::database::DBError; use crate::s3::{Error as S3Error, S3Client, S3Path}; -use crate::tools::{BoxedError, ByteStream, MemOps}; +use crate::tools::MemOps; use crate::{constants::BLOB_DOWNLOAD_CHUNK_SIZE, database::DatabaseClient}; #[derive( @@ -132,13 +134,13 @@ tokio::pin!(blob_data_stream); let mut s3_chunk: Vec = Vec::new(); - while let Some(mut chunk) = + while let Some(chunk) = blob_data_stream.try_next().await.map_err(|err| { warn!("Failed to get data chunk: {:?}", err); BlobServiceError::InputError(err) })? { - s3_chunk.append(&mut chunk); + s3_chunk.extend_from_slice(&chunk); // New parts should be added to AWS only if they exceed minimum part size, // Otherwise AWS returns error diff --git a/services/blob/src/tools.rs b/services/blob/src/tools.rs --- a/services/blob/src/tools.rs +++ b/services/blob/src/tools.rs @@ -1,17 +1,3 @@ -use std::error::Error as StdError; -use tonic::codegen::futures_core::Stream; - -pub type BoxedError = Box; -// Trait type aliases aren't supported in Rust, but -// we can workaround this by creating an empty trait -// that extends the traits we want to alias. -#[rustfmt::skip] -pub trait ByteStream: - Stream, BoxedError>> {} -#[rustfmt::skip] -impl ByteStream for T where - T: Stream, BoxedError>> {} - pub trait MemOps { fn take_out(&mut self) -> Self; } diff --git a/services/comm-services-lib/Cargo.lock b/services/comm-services-lib/Cargo.lock --- a/services/comm-services-lib/Cargo.lock +++ b/services/comm-services-lib/Cargo.lock @@ -47,10 +47,12 @@ "ahash 0.8.3", "base64", "bitflags", + "brotli", "bytes", "bytestring", "derive_more", "encoding_rs", + "flate2", "futures-core", "h2", "http", @@ -68,6 +70,55 @@ "tokio", "tokio-util", "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.29", +] + +[[package]] +name = "actix-multipart" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee489e3c01eae4d1c35b03c4493f71cb40d93f66b14558feb1b1a807671cc4e" +dependencies = [ + "actix-multipart-derive", + "actix-utils", + "actix-web", + "bytes", + "derive_more", + "futures-core", + "futures-util", + "httparse", + "local-waker", + "log", + "memchr", + "mime", + "serde", + "serde_json", + "serde_plain", + "tempfile", + "tokio", +] + +[[package]] +name = "actix-multipart-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec592f234db8a253cf80531246a4407c8a70530423eea80688a6c5a44a110e7" +dependencies = [ + "darling", + "parse-size", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -140,15 +191,18 @@ dependencies = [ "actix-codec", "actix-http", + "actix-macros", "actix-router", "actix-rt", "actix-server", "actix-service", "actix-utils", + "actix-web-codegen", "ahash 0.7.6", "bytes", "bytestring", "cfg-if", + "cookie", "derive_more", "encoding_rs", "futures-core", @@ -170,6 +224,18 @@ "url", ] +[[package]] +name = "actix-web-codegen" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "addr2line" version = "0.21.0" @@ -217,6 +283,21 @@ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -226,6 +307,12 @@ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "autocfg" version = "1.1.0" @@ -596,6 +683,27 @@ "generic-array", ] +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -632,6 +740,9 @@ version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -669,6 +780,9 @@ version = "0.1.0" dependencies = [ "actix-cors", + "actix-multipart", + "actix-web", + "anyhow", "aws-config", "aws-sdk-dynamodb", "aws-types", @@ -676,10 +790,12 @@ "derive_more", "futures-core", "futures-util", + "http", "reqwest", "serde", "serde_json", "tokio", + "tokio-stream", "tracing", ] @@ -689,6 +805,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time 0.3.20", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -714,6 +841,15 @@ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -748,7 +884,7 @@ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -765,7 +901,42 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", ] [[package]] @@ -778,7 +949,7 @@ "proc-macro2", "quote", "rustc_version", - "syn", + "syn 1.0.109", ] [[package]] @@ -837,6 +1008,16 @@ "instant", ] +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -896,7 +1077,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1111,6 +1292,12 @@ "cxx-build", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -1163,6 +1350,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.61" @@ -1370,7 +1566,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1420,6 +1616,12 @@ "windows-targets 0.48.1", ] +[[package]] +name = "parse-size" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "944553dd59c802559559161f9816429058b869003836120e262e8caec061b7ae" + [[package]] name = "paste" version = "1.0.14" @@ -1449,7 +1651,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1750,7 +1952,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1764,6 +1966,15 @@ "serde", ] +[[package]] +name = "serde_plain" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6018081315db179d0ce57b1fe4b62a12a0028c9cf9bbef868c9cf477b3c34ae" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1848,6 +2059,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.4.1" @@ -1865,6 +2082,17 @@ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "tempfile" version = "3.5.0" @@ -1980,9 +2208,9 @@ [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -2052,7 +2280,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2196,7 +2424,7 @@ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -2230,7 +2458,7 @@ dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2472,3 +2700,33 @@ version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" + +[[package]] +name = "zstd" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a27595e173641171fc74a1232b7b1c7a7cb6e18222c11e9dfb9888fa424c53c" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee98ffd0b48ee95e6c5168188e44a54550b1564d9d530ee21d5f0eaed1069581" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.8+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/services/comm-services-lib/Cargo.toml b/services/comm-services-lib/Cargo.toml --- a/services/comm-services-lib/Cargo.toml +++ b/services/comm-services-lib/Cargo.toml @@ -13,7 +13,15 @@ "dep:serde_json", "dep:tokio", ] -http = ["dep:actix-cors"] +http = [ + "dep:actix-cors", + "dep:actix-web", + "dep:actix-multipart", + "dep:futures-core", + "dep:futures-util", + "dep:http", + "dep:tokio-stream", +] [dependencies] aws-config = "0.55" @@ -22,6 +30,7 @@ chrono = "0.4" derive_more = "0.99" tracing = "0.1" +anyhow = "1.0.74" # blob client dependencies futures-core = { version = "0.3", optional = true } futures-util = { version = "0.3", optional = true } @@ -35,3 +44,7 @@ tokio = { version = "1.32", optional = true } # http dependencies actix-cors = { version = "0.6", optional = true } +actix-web = { version = "4.3", optional = true } +http = { version = "0.2.9", optional = true } +actix-multipart = { version = "0.6", optional = true } +tokio-stream = { version = "0.1.14", optional = true } diff --git a/services/comm-services-lib/src/http.rs b/services/comm-services-lib/src/http.rs --- a/services/comm-services-lib/src/http.rs +++ b/services/comm-services-lib/src/http.rs @@ -1,4 +1,9 @@ +pub mod multipart; + +use crate::tools::BoxedError; use actix_cors::Cors; +use actix_web::web::Bytes; +use futures_core::Stream; pub fn cors_config(is_sandbox: bool) -> Cors { // For local development, use relaxed CORS config @@ -16,3 +21,13 @@ .allow_any_header() .expose_any_header() } + +// Trait type aliases aren't supported in Rust, but +// we can workaround this by creating an empty trait +// that extends the traits we want to alias. +#[rustfmt::skip] +pub trait ByteStream: + Stream> {} +#[rustfmt::skip] +impl ByteStream for T where + T: Stream> {} diff --git a/services/comm-services-lib/src/http/multipart.rs b/services/comm-services-lib/src/http/multipart.rs new file mode 100644 --- /dev/null +++ b/services/comm-services-lib/src/http/multipart.rs @@ -0,0 +1,42 @@ +use actix_multipart::{Field, MultipartError}; +use actix_web::error::ParseError; +use tokio_stream::StreamExt; + +/// Can be used to get a single field from multipart body with it's data +/// converted to a string +/// +/// # Example +/// ```no_run +/// # use comm_services_lib::http::multipart; +/// # use actix_multipart::Multipart; +/// # async fn f(mut payload: Multipart) { +/// let Some((name, mut stream)) = multipart::get_text_field(&mut payload).await.unwrap() else { +/// // Missing field +/// return; +/// }; +/// if name != "Field name" { +/// // Wrong field name +/// return; +/// }; +/// println!("Got string: {name}"); +/// # } +/// ``` +pub async fn get_text_field( + multipart: &mut actix_multipart::Multipart, +) -> anyhow::Result, MultipartError> { + let Some(mut field): Option = multipart.try_next().await? else { + return Ok(None); + }; + + let name = field.name().to_string(); + + let mut buf = Vec::new(); + while let Some(chunk) = field.try_next().await? { + buf.extend_from_slice(&chunk); + } + + let text = + String::from_utf8(buf).map_err(|err| ParseError::Utf8(err.utf8_error()))?; + + Ok(Some((name, text))) +} diff --git a/services/comm-services-lib/src/tools.rs b/services/comm-services-lib/src/tools.rs --- a/services/comm-services-lib/src/tools.rs +++ b/services/comm-services-lib/src/tools.rs @@ -18,6 +18,8 @@ .all(|c| c.is_ascii_alphanumeric() || VALID_IDENTIFIER_CHARS.contains(&c)) } +pub type BoxedError = Box; + #[cfg(test)] mod valid_identifier_tests { use super::*; diff --git a/services/feature-flags/Cargo.lock b/services/feature-flags/Cargo.lock --- a/services/feature-flags/Cargo.lock +++ b/services/feature-flags/Cargo.lock @@ -235,9 +235,9 @@ [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "autocfg" @@ -727,6 +727,7 @@ name = "comm-services-lib" version = "0.1.0" dependencies = [ + "anyhow", "aws-config", "aws-sdk-dynamodb", "aws-types",