From a84e897db9597dd8aba5c77f8056e66a6f29d053 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 13 Jan 2022 15:11:50 +0300 Subject: [PATCH] Refactor multipart requests This removes the logic in the multipart serializer that unserialized `InputFile`s from serde. Now `InputFile`s are serialized either as their value (for `FileId` and `Url`) or as an `attach://` string where `` is replaced with some id unique for the file. The file data itself is acquired through `MultipartPayload` trait. Since the `` must be the same while serializing the file with serde and while acquiring data through `MultipartPayload` trait, `InputFile` needs to store said id. As such, `InputFile` is now a structure with private fields and it's structure can't be observed by users. The only things that `InputFile` provides are - Constructors (`url`, `file_id`, `file`, `memory`) - File name setter - `Clone` and `Debug` implementations --- Cargo.toml | 1 + src/bot.rs | 32 +- src/local_macros.rs | 52 +- src/requests/multipart.rs | 4 +- src/requests/multipart_payload.rs | 48 +- src/serde_multipart/error.rs | 53 ++ src/serde_multipart/mod.rs | 72 ++- src/serde_multipart/serializers.rs | 607 +++++++------------- src/serde_multipart/unserializers/bytes.rs | 145 ----- src/serde_multipart/unserializers/string.rs | 61 -- src/types/input_file.rs | 308 +++++++--- src/types/input_media.rs | 12 +- 12 files changed, 652 insertions(+), 743 deletions(-) create mode 100644 src/serde_multipart/error.rs delete mode 100644 src/serde_multipart/unserializers/bytes.rs delete mode 100644 src/serde_multipart/unserializers/string.rs diff --git a/Cargo.toml b/Cargo.toml index baa83bfd..1d22bbe2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ derive_more = "0.99.9" mime = "0.3.16" thiserror = "1.0.20" once_cell = "1.5.0" +take_mut = "0.2" never = "0.1.0" chrono = { version = "0.4.19", default-features = false } either = "1.6.1" diff --git a/src/bot.rs b/src/bot.rs index a79c75a7..9e57d393 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -233,7 +233,7 @@ impl Bot { pub(crate) fn execute_multipart

( &self, - payload: &P, + payload: &mut P, ) -> impl Future> where P: MultipartPayload + Serialize, @@ -247,7 +247,35 @@ impl Bot { // async move to capture client&token&api_url¶ms async move { - let params = params.await?; + let params = params?.await; + net::request_multipart( + &client, + token.as_ref(), + reqwest::Url::clone(&*api_url), + P::NAME, + params, + ) + .await + } + } + + pub(crate) fn execute_multipart_ref

( + &self, + payload: &P, + ) -> impl Future> + where + P: MultipartPayload + Serialize, + P::Output: DeserializeOwned, + { + let client = self.client.clone(); + let token = Arc::clone(&self.token); + let api_url = self.api_url.clone(); + + let params = serde_multipart::to_form_ref(payload); + + // async move to capture client&token&api_url¶ms + async move { + let params = params?.await; net::request_multipart( &client, token.as_ref(), diff --git a/src/local_macros.rs b/src/local_macros.rs index 33c255d2..e4a786a2 100644 --- a/src/local_macros.rs +++ b/src/local_macros.rs @@ -1,41 +1,3 @@ -macro_rules! forward_to_unsuported_ty { - ( - supported: $supported:expr; - simple { $( $method:ident $arg:ty )* } - unit { $( $method1:ident $ty:expr )* } - compound { - $( $method2:ident $( <$T:ident: ?Sized + Serialize> )? ( $( $args:tt )* ) -> $ret:ty => $message:expr )* - } - ) => { - $( - fn $method(self, _: $arg) -> Result { - Err(Self::Error::UnsupportedType { - ty: stringify!($arg), - supported: $supported, - }) - } - )+ - - $( - fn $method1(self) -> Result { - Err(Self::Error::UnsupportedType { - ty: $ty, - supported: $supported, - }) - } - )+ - - $( - fn $method2 $( <$T: ?Sized + Serialize> )? (self, $( $args )*) -> Result<$ret, Self::Error> { - Err(Self::Error::UnsupportedType { - ty: $message, - supported: $supported, - }) - } - )+ - }; -} - macro_rules! req_future { ( $v2:vis def: | $( $arg:ident: $ArgTy:ty ),* $(,)? | $body:block @@ -388,7 +350,19 @@ macro_rules! impl_payload { $e }; (@[multipart = $($multipart_attr:ident),*] $Method:ident req { $($reqf:ident),* } opt { $($optf:ident),*} ) => { - impl crate::requests::MultipartPayload for $Method {} + impl crate::requests::MultipartPayload for $Method { + fn copy_files(&self, into: &mut dyn FnMut(crate::types::InputFile)) { + $( + crate::types::InputFileLike::copy_into(&self.$multipart_attr, into); + )* + } + + fn move_files(&mut self, into: &mut dyn FnMut(crate::types::InputFile)) { + $( + crate::types::InputFileLike::move_into(&mut self.$multipart_attr, into); + )* + } + } }; (@[] $($ignored:tt)*) => {} } diff --git a/src/requests/multipart.rs b/src/requests/multipart.rs index 1b873e62..8ab398f7 100644 --- a/src/requests/multipart.rs +++ b/src/requests/multipart.rs @@ -90,7 +90,7 @@ where req_future! { def: |it: MultipartRequest| { - it.bot.execute_multipart(&it.payload) + it.bot.execute_multipart(&mut {it.payload}) } pub Send (inner0) -> ResponseResult where @@ -101,7 +101,7 @@ req_future! { req_future! { def: |it: &MultipartRequest| { - it.bot.execute_multipart(&it.payload) + it.bot.execute_multipart_ref(&it.payload) } pub SendRef (inner1) -> ResponseResult where diff --git a/src/requests/multipart_payload.rs b/src/requests/multipart_payload.rs index d3b58457..f55ee74c 100644 --- a/src/requests/multipart_payload.rs +++ b/src/requests/multipart_payload.rs @@ -1,11 +1,49 @@ -use crate::{payloads, requests::Payload}; +use crate::{ + payloads, + requests::Payload, + types::{InputFile, InputFileLike, InputMedia}, +}; /// Payloads that need to be sent as `multipart/form-data` because they contain /// files inside. -pub trait MultipartPayload: Payload {} +pub trait MultipartPayload: Payload { + fn copy_files(&self, into: &mut dyn FnMut(InputFile)); -impl MultipartPayload for payloads::SendMediaGroup {} + fn move_files(&mut self, into: &mut dyn FnMut(InputFile)); +} -impl MultipartPayload for payloads::EditMessageMedia {} +impl MultipartPayload for payloads::SendMediaGroup { + fn copy_files(&self, into: &mut dyn FnMut(InputFile)) { + self.media + .iter() + .flat_map(InputMedia::files) + .for_each(|f| f.copy_into(into)) + } -impl MultipartPayload for payloads::EditMessageMediaInline {} + fn move_files(&mut self, into: &mut dyn FnMut(InputFile)) { + self.media + .iter_mut() + .flat_map(InputMedia::files_mut) + .for_each(|f| f.move_into(into)) + } +} + +impl MultipartPayload for payloads::EditMessageMedia { + fn copy_files(&self, into: &mut dyn FnMut(InputFile)) { + self.media.files().for_each(|f| f.copy_into(into)) + } + + fn move_files(&mut self, into: &mut dyn FnMut(InputFile)) { + self.media.files_mut().for_each(|f| f.move_into(into)) + } +} + +impl MultipartPayload for payloads::EditMessageMediaInline { + fn copy_files(&self, into: &mut dyn FnMut(InputFile)) { + self.media.files().for_each(|f| f.copy_into(into)) + } + + fn move_files(&mut self, into: &mut dyn FnMut(InputFile)) { + self.media.files_mut().for_each(|f| f.move_into(into)) + } +} diff --git a/src/serde_multipart/error.rs b/src/serde_multipart/error.rs new file mode 100644 index 00000000..427f2da9 --- /dev/null +++ b/src/serde_multipart/error.rs @@ -0,0 +1,53 @@ +use std::fmt; + +use serde::ser; + +use crate::RequestError; + +#[derive(Debug, derive_more::From)] +pub(crate) enum Error { + Custom(String), + TopLevelNotStruct, + Fmt(std::fmt::Error), + Io(std::io::Error), + Json(serde_json::Error), +} + +impl ser::Error for Error { + fn custom(msg: T) -> Self + where + T: fmt::Display, + { + Self::Custom(msg.to_string()) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Custom(s) => write!(f, "Custom serde error: {}", s), + Self::TopLevelNotStruct => write!(f, "Multipart supports only structs at top level"), + Self::Fmt(inner) => write!(f, "Formatting error: {}", inner), + Self::Io(inner) => write!(f, "Io error: {}", inner), + Self::Json(inner) => write!(f, "Json (de)serialization error: {}", inner), + } + } +} + +impl std::error::Error for Error {} + +impl From for RequestError { + fn from(err: Error) -> Self { + match err { + Error::Io(ioerr) => RequestError::Io(ioerr), + + // This should be ok since we (hopefuly) don't write request those may trigger errors + // and `Error` is internal. + e => unreachable!( + "we don't create requests those fail to serialize (if you see this, open an issue \ + :|): {}", + e + ), + } + } +} diff --git a/src/serde_multipart/mod.rs b/src/serde_multipart/mod.rs index 42ffa7f7..e4cf70a7 100644 --- a/src/serde_multipart/mod.rs +++ b/src/serde_multipart/mod.rs @@ -10,24 +10,74 @@ //! This whole module is an awful hack and we'll probably stop using it in next //! versions (in favor of something less automatic, but more simple). +mod error; mod serializers; -mod unserializers; use std::future::Future; use reqwest::multipart::Form; use serde::Serialize; -use serializers::MultipartTopLvlSerializer; +use crate::requests::MultipartPayload; +use error::Error; +use serializers::MultipartSerializer; -pub(crate) use serializers::Error; - -/// Serializes given value into [`Form`] +/// Serializes given value into [`Form`] **taking all input files out**. /// /// [`Form`]: reqwest::multipart::Form -pub(crate) fn to_form(val: &T) -> impl Future> { - let fut = val.serialize(MultipartTopLvlSerializer {}); - async { Ok(fut?.await?) } +pub(crate) fn to_form(val: &mut T) -> Result, Error> +where + T: Serialize + MultipartPayload, +{ + let mut form = val.serialize(MultipartSerializer::new())?; + + let mut vec = Vec::with_capacity(1); + val.move_files(&mut |f| vec.push(f)); + let iter = vec.into_iter(); + + let fut = async move { + for file in iter { + if file.needs_attach() { + let id = file.id().to_owned(); + if let Some(part) = file.into_part() { + form = form.part(id, part.await); + } + } + } + + form + }; + + Ok(fut) +} + +/// Serializes given value into [`Form`]. +/// +/// [`Form`]: reqwest::multipart::Form +pub(crate) fn to_form_ref(val: &T) -> Result, Error> +where + T: Serialize + MultipartPayload, +{ + let mut form = val.serialize(MultipartSerializer::new())?; + let mut vec = Vec::with_capacity(1); + val.copy_files(&mut |f| vec.push(f)); + + let iter = vec.into_iter(); + + let fut = async move { + for file in iter { + if file.needs_attach() { + let id = file.id().to_owned(); + if let Some(part) = file.into_part() { + form = form.part(id, part.await); + } + } + } + + form + }; + + Ok(fut) } // https://github.com/teloxide/teloxide/issues/473 @@ -39,13 +89,13 @@ async fn issue_473() { types::{InputFile, MessageEntity, MessageEntityKind}, }; - to_form( + to_form_ref( &payloads::SendPhoto::new(0, InputFile::file_id("0")).caption_entities([MessageEntity { kind: MessageEntityKind::Url, offset: 0, length: 0, }]), ) - .await - .unwrap(); + .unwrap() + .await; } diff --git a/src/serde_multipart/serializers.rs b/src/serde_multipart/serializers.rs index 05b04d1f..cf3a4b98 100644 --- a/src/serde_multipart/serializers.rs +++ b/src/serde_multipart/serializers.rs @@ -1,87 +1,84 @@ -use crate::{ - serde_multipart::unserializers::{InputFileUnserializer, StringUnserializer}, - types::InputFile, - RequestError, -}; -use futures::{ - future::{ready, BoxFuture}, - stream::FuturesUnordered, - FutureExt, StreamExt, TryStreamExt, -}; +use crate::serde_multipart::error::Error; + use reqwest::multipart::{Form, Part}; use serde::{ - ser, - ser::{Impossible, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant}, + ser::{Impossible, SerializeMap, SerializeSeq, SerializeStruct}, Serialize, Serializer, }; -use std::{fmt, fmt::Display, io}; -#[derive(Debug, derive_more::From)] -pub(crate) enum Error { - Custom(String), - TopLevelNotStruct, - InputFileUnserializer(crate::serde_multipart::unserializers::UnserializerError), - Io(std::io::Error), - Json(serde_json::Error), +/// The main serializer that serializes top-level and structures +pub(super) struct MultipartSerializer(Form); + +/// Serializer for maps (support for `#[serde(flatten)]`) +pub(super) struct MultipartMapSerializer { + form: Form, + key: Option, } -impl ser::Error for Error { - fn custom(msg: T) -> Self - where - T: Display, - { - Self::Custom(msg.to_string()) +/// Serializer for single "fields" that are serialized as multipart "part"s. +/// +/// - Integers serialized as their text decimal representation +/// - Strings and byte slices are serialized as-is, without any changes +/// - Structs are serialized with JSON +/// - C-like enums are serialized as their names +struct PartSerializer; + +/// Struct or Seq -> Json -> Part serializer +struct JsonPartSerializer { + buf: String, + state: PartSerializerStructState, +} + +/// State for `PartSerializerStruct` +/// +/// Json doesn't allow trailing commas, so we need to know if we already +/// serialized something and need to add a comma before next field +enum PartSerializerStructState { + Empty, + Rest, +} + +impl MultipartSerializer { + pub(super) fn new() -> Self { + Self(Form::new()) } } -impl Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Custom(s) => write!(f, "Custom serde error: {}", s), - Self::TopLevelNotStruct => write!(f, "Multipart supports only structs at top level"), - Self::InputFileUnserializer(inner) => { - write!(f, "Error while unserializing input file: {}", inner) - } - Self::Io(inner) => write!(f, "Io error: {}", inner), - Self::Json(inner) => write!(f, "Json (de)serialization error: {}", inner), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - None - } -} - -impl From for RequestError { - fn from(err: Error) -> Self { - match err { - Error::Io(ioerr) => RequestError::Io(ioerr), - // this should be ok since we don't write request those may trigger errors and - // Error is internal. - e => unreachable!( - "we don't create requests those fail to serialize (if you see this, open an issue \ - :|): {}", - e - ), - } - } -} - -pub(crate) struct MultipartTopLvlSerializer {} - -impl Serializer for MultipartTopLvlSerializer { - type Ok = ::Ok; +impl Serializer for MultipartSerializer { + type Ok = Form; type Error = Error; + + // for `serde(flatten)` (e.g.: in CreateNewStickerSet) + type SerializeMap = MultipartMapSerializer; + + // The main serializer - struct + type SerializeStruct = Self; + + // Unimplemented type SerializeSeq = Impossible; type SerializeTuple = Impossible; type SerializeTupleStruct = Impossible; type SerializeTupleVariant = Impossible; - type SerializeMap = MultipartMapSerializer; // for `serde(flatten)` (e.g.: in CreateNewStickerSet) - type SerializeStruct = MultipartSerializer; type SerializeStructVariant = Impossible; + fn serialize_map(self, _: Option) -> Result { + Ok(MultipartMapSerializer { + form: Form::new(), + key: None, + }) + } + + fn serialize_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Ok(self) + } + + // Everything down below in this impl just returns + // `Err(Error::TopLevelNotStruct)` + fn serialize_bool(self, _: bool) -> Result { Err(Error::TopLevelNotStruct) } @@ -216,22 +213,6 @@ impl Serializer for MultipartTopLvlSerializer { Err(Error::TopLevelNotStruct) } - fn serialize_map(self, _: Option) -> Result { - Ok(MultipartMapSerializer { - parts: vec![], - files: vec![], - key: None, - }) - } - - fn serialize_struct( - self, - _: &'static str, - _: usize, - ) -> Result { - Ok(MultipartSerializer::new()) - } - fn serialize_struct_variant( self, _: &'static str, @@ -243,22 +224,8 @@ impl Serializer for MultipartTopLvlSerializer { } } -pub(crate) struct MultipartSerializer { - parts: Vec<(&'static str, Part)>, // TODO: Array vecs - files: Vec<(String, InputFile)>, -} - -impl MultipartSerializer { - fn new() -> Self { - Self { - parts: Vec::new(), - files: vec![], - } - } -} - impl SerializeStruct for MultipartSerializer { - type Ok = BoxFuture<'static, io::Result

>; // impl Future> + type Ok = Form; type Error = Error; fn serialize_field( @@ -269,52 +236,29 @@ impl SerializeStruct for MultipartSerializer { where T: Serialize, { - let (part, file) = value.serialize(PartSerializer {})?; - self.parts.push((key, part)); - self.files.extend(file); + let part = value.serialize(PartSerializer {})?; + take_mut::take(&mut self.0, |f| f.part(key, part)); Ok(()) } fn end(self) -> Result { - let form = self - .parts - .into_iter() - .fold(Form::new(), |acc, (key, value)| acc.part(key, value)); - - if self.files.is_empty() { - //Ok(Either::Left(ready(Ok(form)))) - Ok(Box::pin(ready(Ok(form)))) - } else { - let fut = self - .files - .into_iter() - .map(|(k, f)| f.into_part().map(move |p| (k, p))) - .collect::>() - .map(Ok) - .try_fold(form, |acc, (k, p)| async { Ok(acc.part(k, p?)) }); - - //Ok(Either::Right(fut)) - Ok(Box::pin(fut)) - } + Ok(self.0) } } -pub(crate) struct MultipartMapSerializer { - parts: Vec<(String, Part)>, // TODO: Array vecs - files: Vec<(String, InputFile)>, - key: Option, -} - impl SerializeMap for MultipartMapSerializer { - type Ok = ::Ok; + type Ok = Form; type Error = Error; fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> where T: Serialize, { - self.key = Some(key.serialize(StringUnserializer)?); + if let Ok(serde_json::Value::String(s)) = serde_json::to_value(key) { + self.key = Some(s); + } + Ok(()) } @@ -322,110 +266,90 @@ impl SerializeMap for MultipartMapSerializer { where T: Serialize, { - let key = self.key.take().unwrap(); + let key = self + .key + .take() + .expect("Value serialized before key or key is not string"); - let (part, file) = value.serialize(PartSerializer {})?; - self.parts.push((key, part)); - self.files.extend(file); + let part = value.serialize(PartSerializer {})?; + take_mut::take(&mut self.form, |f| f.part(key, part)); Ok(()) } fn end(self) -> Result { - let form = self - .parts - .into_iter() - .fold(Form::new(), |acc, (key, value)| acc.part(key, value)); - - if self.files.is_empty() { - //Ok(Either::Left(ready(Ok(form)))) - Ok(Box::pin(ready(Ok(form)))) - } else { - let fut = self - .files - .into_iter() - .map(|(k, f)| f.into_part().map(move |p| (k, p))) - .collect::>() - .map(Ok) - .try_fold(form, |acc, (k, p)| async { Ok(acc.part(k, p?)) }); - - //Ok(Either::Right(fut)) - Ok(Box::pin(fut)) - } + Ok(self.form) } } -struct PartSerializer {} - impl Serializer for PartSerializer { - type Ok = (Part, Vec<(String, InputFile)>); + type Ok = Part; type Error = Error; - type SerializeSeq = InnerPartSerializer; + + type SerializeStruct = JsonPartSerializer; + type SerializeSeq = JsonPartSerializer; + + // Unimplemented type SerializeTuple = Impossible; type SerializeTupleStruct = Impossible; type SerializeTupleVariant = Impossible; type SerializeMap = Impossible; - type SerializeStruct = PartSerializerStruct; - type SerializeStructVariant = PartFromFile; + type SerializeStructVariant = Impossible; fn serialize_bool(self, v: bool) -> Result { - Ok((Part::text(v.to_string()), Vec::new())) + Ok(Part::text(v.to_string())) } - fn serialize_i8(self, _: i8) -> Result { - unimplemented!() + fn serialize_i8(self, v: i8) -> Result { + Ok(Part::text(v.to_string())) } - fn serialize_i16(self, _: i16) -> Result { - unimplemented!() + fn serialize_i16(self, v: i16) -> Result { + Ok(Part::text(v.to_string())) } fn serialize_i32(self, v: i32) -> Result { - Ok((Part::text(v.to_string()), Vec::new())) + Ok(Part::text(v.to_string())) } fn serialize_i64(self, v: i64) -> Result { - Ok((Part::text(v.to_string()), Vec::new())) + Ok(Part::text(v.to_string())) } - fn serialize_u8(self, _: u8) -> Result { - unimplemented!() + fn serialize_u8(self, v: u8) -> Result { + Ok(Part::text(v.to_string())) } - fn serialize_u16(self, _: u16) -> Result { - unimplemented!() + fn serialize_u16(self, v: u16) -> Result { + Ok(Part::text(v.to_string())) } - fn serialize_u32(self, _: u32) -> Result { - unimplemented!() + fn serialize_u32(self, v: u32) -> Result { + Ok(Part::text(v.to_string())) } - fn serialize_u64(self, _: u64) -> Result { - unimplemented!() + fn serialize_u64(self, v: u64) -> Result { + Ok(Part::text(v.to_string())) } - fn serialize_f32(self, _: f32) -> Result { - unimplemented!() + fn serialize_f32(self, v: f32) -> Result { + Ok(Part::text(v.to_string())) } - fn serialize_f64(self, _: f64) -> Result { - unimplemented!() + fn serialize_f64(self, v: f64) -> Result { + Ok(Part::text(v.to_string())) } - fn serialize_char(self, _: char) -> Result { - unimplemented!() + fn serialize_char(self, v: char) -> Result { + Ok(Part::text(v.to_string())) } fn serialize_str(self, v: &str) -> Result { - Ok((Part::text(v.to_owned()), Vec::new())) + Ok(Part::text(v.to_owned())) } - fn serialize_bytes(self, _: &[u8]) -> Result { - unimplemented!() - } - - fn serialize_none(self) -> Result { - unimplemented!() + fn serialize_bytes(self, v: &[u8]) -> Result { + Ok(Part::bytes(v.to_owned())) } fn serialize_some(self, value: &T) -> Result @@ -435,21 +359,48 @@ impl Serializer for PartSerializer { value.serialize(self) } - fn serialize_unit(self) -> Result { - unimplemented!() - } - - fn serialize_unit_struct(self, _: &'static str) -> Result { - unimplemented!() - } - fn serialize_unit_variant( self, _: &'static str, _: u32, variant_name: &'static str, ) -> Result { - Ok((Part::text(variant_name), Vec::new())) + Ok(Part::text(variant_name)) + } + + fn serialize_struct( + self, + _: &'static str, + _: usize, + ) -> Result { + Ok(JsonPartSerializer { + buf: String::new(), + state: PartSerializerStructState::Empty, + }) + } + + fn serialize_seq(self, _: Option) -> Result { + Ok(JsonPartSerializer { + buf: String::new(), + state: PartSerializerStructState::Empty, + }) + } + + // Unimplemented + + fn serialize_none(self) -> Result { + unimplemented!( + "We use `#[serde_with_macros::skip_serializing_none]` everywhere so `None`s are not \ + serialized" + ) + } + + fn serialize_unit(self) -> Result { + unimplemented!() + } + + fn serialize_unit_struct(self, _: &'static str) -> Result { + unimplemented!() } fn serialize_newtype_struct( @@ -465,37 +416,15 @@ impl Serializer for PartSerializer { fn serialize_newtype_variant( self, - name: &'static str, - variant_index: u32, - variant: &'static str, - value: &T, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _value: &T, ) -> Result where T: Serialize, { - let file: InputFile = InputFileUnserializer::NotMem.serialize_newtype_variant( - name, - variant_index, - variant, - value, - )?; - - match file { - f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { - let uuid = uuid::Uuid::new_v4().to_string(); - let part = Part::text(format!("attach://{}", uuid)); - Ok((part, vec![(uuid, f)])) - } - InputFile::FileId(s) => Ok((Part::text(s), Vec::new())), - InputFile::Url(s) => Ok((Part::text(String::from(s)), Vec::new())), - } - } - - fn serialize_seq(self, _: Option) -> Result { - Ok(Self::SerializeSeq { - array_json_parts: vec![], - files: vec![], - }) + unimplemented!() } fn serialize_tuple(self, _: usize) -> Result { @@ -524,44 +453,19 @@ impl Serializer for PartSerializer { unimplemented!() } - fn serialize_struct( - self, - name: &'static str, - len: usize, - ) -> Result { - let mut ser = serde_json::Serializer::new(Vec::new()); - ser.serialize_struct(name, len)?; - Ok(PartSerializerStruct( - ser, // TODO: capcity - serde_json::ser::State::First, - Vec::new(), - )) - } - fn serialize_struct_variant( self, - name: &'static str, - variant_index: u32, - variant: &'static str, - len: usize, + _name: &'static str, + _variant_index: u32, + _variant: &'static str, + _len: usize, ) -> Result { - Ok(PartFromFile { - inner: InputFileUnserializer::memory().serialize_struct_variant( - name, - variant_index, - variant, - len, - )?, - }) + unimplemented!() } } -struct PartFromFile { - inner: InputFileUnserializer, -} - -impl SerializeStructVariant for PartFromFile { - type Ok = (Part, Vec<(String, InputFile)>); +impl SerializeStruct for JsonPartSerializer { + type Ok = Part; type Error = Error; fn serialize_field( @@ -572,158 +476,71 @@ impl SerializeStructVariant for PartFromFile { where T: Serialize, { - self.inner - .serialize_field(key, value) - .map_err(Error::InputFileUnserializer) - } + use std::fmt::Write; + use PartSerializerStructState::*; - fn end(self) -> Result { - let file = self.inner.end()?; + let value = serde_json::to_string(value)?; + match self.state { + Empty => { + self.state = Rest; - // TODO: to method - match file { - f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { - let uuid = uuid::Uuid::new_v4().to_string(); - let part = Part::text(format!("attach://{}", uuid)); - - Ok((part, vec![(uuid, f)])) + write!(&mut self.buf, "{{\"{}\":{}", key, value)? } - InputFile::FileId(s) => Ok((Part::text(s), vec![])), - InputFile::Url(s) => Ok((Part::text(String::from(s)), vec![])), - } - } -} - -struct InnerPartSerializer { - array_json_parts: Vec, // using value is such a workaround :| - files: Vec<(String, InputFile)>, -} - -impl SerializeSeq for InnerPartSerializer { - type Ok = (Part, Vec<(String, InputFile)>); - type Error = Error; - - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { - // NOTE: this is probably highly inefficient (especially for ::Memory), - // but at least it works - let mut value = serde_json::to_value(value)?; - - // Update value if it contains InputFile in a `media` field (i.e.: `InputMedia`) - if let Some(file) = value.get_mut("media") { - let file: InputFile = serde_json::from_value(file.take())?; - - match file { - f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { - let uuid = uuid::Uuid::new_v4().to_string(); - value["media"] = serde_json::Value::String(format!("attach://{}", uuid)); - self.files.push((uuid, f)); - } - InputFile::FileId(s) => { - value["media"] = serde_json::Value::String(s); - } - InputFile::Url(s) => { - value["media"] = serde_json::Value::String(String::from(s)); - } - } - } - - self.array_json_parts.push(value); - - Ok(()) - } - - fn end(self) -> Result { - let s = serde_json::to_string(&self.array_json_parts)?; - Ok((Part::text(s), self.files)) - } -} - -struct PartSerializerStruct( - serde_json::Serializer>, - serde_json::ser::State, - Vec<(String, InputFile)>, -); - -impl SerializeStruct for PartSerializerStruct { - type Ok = (Part, Vec<(String, InputFile)>); - type Error = Error; - - fn serialize_field( - &mut self, - key: &'static str, - value: &T, - ) -> Result<(), Self::Error> - where - T: Serialize, - { - let state = match self.1 { - serde_json::ser::State::Empty => serde_json::ser::State::Empty, - serde_json::ser::State::First => serde_json::ser::State::First, - serde_json::ser::State::Rest => serde_json::ser::State::Rest, - }; - let mut ser = serde_json::ser::Compound::Map { - ser: &mut self.0, - state, - }; - - // special case media (required for `edit_message_media` to work) - if key == "media" { - let file = value.serialize(InputFileUnserializer::NotMem)?; - - match file { - f @ InputFile::Memory { .. } | f @ InputFile::File(_) => { - let uuid = uuid::Uuid::new_v4().to_string(); - let attach = format!("attach://{}", uuid); - - SerializeStruct::serialize_field(&mut ser, key, attach.as_str())?; - self.1 = get_state(ser); - - self.2.push((uuid, f)); - } - InputFile::FileId(s) => { - SerializeStruct::serialize_field(&mut ser, key, &s)?; - self.1 = get_state(ser) - } - InputFile::Url(s) => { - SerializeStruct::serialize_field(&mut ser, key, s.as_str())?; - self.1 = get_state(ser) - } - } - } else { - SerializeStruct::serialize_field(&mut ser, key, value)?; - self.1 = get_state(ser); + Rest => write!(&mut self.buf, ",\"{}\":{}", key, value)?, } Ok(()) } fn end(mut self) -> Result { - let state = match self.1 { - serde_json::ser::State::Empty => serde_json::ser::State::Empty, - serde_json::ser::State::First => serde_json::ser::State::First, - serde_json::ser::State::Rest => serde_json::ser::State::Rest, - }; - let ser = serde_json::ser::Compound::Map { - ser: &mut self.0, - state, - }; - SerializeStruct::end(ser)?; + use PartSerializerStructState::*; - let json = self.0.into_inner(); - Ok((Part::bytes(json), self.2)) + match self.state { + Empty => Ok(Part::text("{{}}")), + Rest => { + self.buf += "}"; + + Ok(Part::text(self.buf)) + } + } } } -fn get_state( - compound: serde_json::ser::Compound, serde_json::ser::CompactFormatter>, -) -> serde_json::ser::State { - // Compound may have more variants under some serde_json features - #[allow(unreachable_patterns)] - match compound { - serde_json::ser::Compound::Map { ser: _, state } => state, - _ => unreachable!(), +impl SerializeSeq for JsonPartSerializer { + type Ok = Part; + + type Error = Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + use std::fmt::Write; + use PartSerializerStructState::*; + + let value = serde_json::to_string(value)?; + match self.state { + Empty => { + self.state = Rest; + + write!(&mut self.buf, "[{}", value)? + } + Rest => write!(&mut self.buf, ",{}", value)?, + } + + Ok(()) + } + + fn end(mut self) -> Result { + use PartSerializerStructState::*; + + match self.state { + Empty => Ok(Part::text("[]")), + Rest => { + self.buf += "]"; + + Ok(Part::text(self.buf)) + } + } } } diff --git a/src/serde_multipart/unserializers/bytes.rs b/src/serde_multipart/unserializers/bytes.rs deleted file mode 100644 index 182b9da7..00000000 --- a/src/serde_multipart/unserializers/bytes.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::serde_multipart::unserializers::UnserializerError; -use serde::{ - ser::{Impossible, SerializeSeq}, - Serialize, Serializer, -}; - -#[derive(Default)] -pub(crate) struct BytesUnserializer(Vec); - -impl Serializer for BytesUnserializer { - type Ok = Vec; - type Error = UnserializerError; - - type SerializeSeq = Self; - type SerializeTuple = Impossible, UnserializerError>; - type SerializeTupleStruct = Impossible, UnserializerError>; - type SerializeTupleVariant = Impossible, UnserializerError>; - type SerializeMap = Impossible, UnserializerError>; - type SerializeStruct = Impossible, UnserializerError>; - type SerializeStructVariant = Impossible, UnserializerError>; - - fn serialize_bytes(self, v: &[u8]) -> Result { - Ok(v.to_owned()) - } - - fn serialize_seq(mut self, len: Option) -> Result { - if let Some(len) = len { - self.0.reserve_exact(len); - } - Ok(self) - } - - forward_to_unsuported_ty! { - supported: "&[u8], Vec, Cow<[u8]>"; - simple { - serialize_bool bool - serialize_i8 i8 - serialize_i16 i16 - serialize_i32 i32 - serialize_i64 i64 - serialize_u8 u8 - serialize_u16 u16 - serialize_u32 u32 - serialize_u64 u64 - serialize_f32 f32 - serialize_f64 f64 - serialize_char char - serialize_str &str - } - unit { - serialize_none "None" - serialize_unit "unit" - } - compound { - serialize_some(_: &T) -> Self::Ok => "Some(_)" - serialize_unit_struct(_: &'static str) -> Self::Ok => "unit struct" - serialize_unit_variant(_: &'static str, _: u32, _: &'static str) -> Self::Ok => "unit variant" - serialize_newtype_struct(_: &'static str, _: &T) -> Self::Ok => "newtype struct" - serialize_newtype_variant(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant" - serialize_tuple(_: usize) -> Self::SerializeTuple => "tuple" - serialize_tuple_struct(_: &'static str, _: usize) -> Self::SerializeTupleStruct => "tuple struct" - serialize_tuple_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeTupleVariant => "tuple variant" - serialize_map(_: Option) -> Self::SerializeMap => "map" - serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct" - serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant" - } - } -} - -impl SerializeSeq for BytesUnserializer { - type Ok = Vec; - type Error = UnserializerError; - - fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> - where - T: Serialize, - { - value.serialize(BytesUnserializerPush(&mut self.0)) - } - - fn end(self) -> Result { - Ok(self.0) - } -} - -pub(crate) struct BytesUnserializerPush<'a>(&'a mut Vec); - -impl Serializer for BytesUnserializerPush<'_> { - type Ok = (); - type Error = UnserializerError; - - type SerializeSeq = Impossible<(), UnserializerError>; - type SerializeTuple = Impossible<(), UnserializerError>; - type SerializeTupleStruct = Impossible<(), UnserializerError>; - type SerializeTupleVariant = Impossible<(), UnserializerError>; - type SerializeMap = Impossible<(), UnserializerError>; - type SerializeStruct = Impossible<(), UnserializerError>; - type SerializeStructVariant = Impossible<(), UnserializerError>; - - fn serialize_u8(self, v: u8) -> Result { - self.0.push(v); - Ok(()) - } - - fn serialize_bytes(self, v: &[u8]) -> Result { - self.0.extend_from_slice(v); - Ok(()) - } - - forward_to_unsuported_ty! { - supported: "&[u8], Vec, Cow<[u8]>"; - simple { - serialize_bool bool - serialize_i8 i8 - serialize_i16 i16 - serialize_i32 i32 - serialize_i64 i64 - serialize_u16 u16 - serialize_u32 u32 - serialize_u64 u64 - serialize_f32 f32 - serialize_f64 f64 - serialize_char char - serialize_str &str - } - unit { - serialize_none "None" - serialize_unit "unit" - } - compound { - serialize_some(_: &T) -> Self::Ok => "Some(_)" - serialize_unit_struct(_: &'static str) -> Self::Ok => "unit struct" - serialize_unit_variant(_: &'static str, _: u32, _: &'static str) -> Self::Ok => "unit variant" - serialize_newtype_struct(_: &'static str, _: &T) -> Self::Ok => "newtype struct" - serialize_newtype_variant(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant" - serialize_seq(_: Option) -> Self::SerializeSeq => "sequence" - serialize_tuple(_: usize) -> Self::SerializeTuple => "tuple" - serialize_tuple_struct(_: &'static str, _: usize) -> Self::SerializeTupleStruct => "tuple struct" - serialize_tuple_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeTupleVariant => "tuple variant" - serialize_map(_: Option) -> Self::SerializeMap => "map" - serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct" - serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant" - } - } -} diff --git a/src/serde_multipart/unserializers/string.rs b/src/serde_multipart/unserializers/string.rs deleted file mode 100644 index 7d4a8548..00000000 --- a/src/serde_multipart/unserializers/string.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::serde_multipart::unserializers::UnserializerError; -use serde::{ser::Impossible, Serialize, Serializer}; - -pub(crate) struct StringUnserializer; - -impl Serializer for StringUnserializer { - type Ok = String; - type Error = UnserializerError; - - type SerializeSeq = Impossible; - type SerializeTuple = Impossible; - type SerializeTupleStruct = Impossible; - type SerializeTupleVariant = Impossible; - type SerializeMap = Impossible; - type SerializeStruct = Impossible; - type SerializeStructVariant = Impossible; - - fn serialize_char(self, v: char) -> Result { - Ok(v.to_string()) - } - - fn serialize_str(self, v: &str) -> Result { - Ok(v.to_owned()) - } - - forward_to_unsuported_ty! { - supported: "&str, String"; - simple { - serialize_bool bool - serialize_i8 i8 - serialize_i16 i16 - serialize_i32 i32 - serialize_i64 i64 - serialize_u8 u8 - serialize_u16 u16 - serialize_u32 u32 - serialize_u64 u64 - serialize_f32 f32 - serialize_f64 f64 - serialize_bytes &[u8] - } - unit { - serialize_none "None" - serialize_unit "unit" - } - compound { - serialize_some(_: &T) -> Self::Ok => "Some(_)" - serialize_unit_struct(_: &'static str) -> Self::Ok => "unit struct" - serialize_unit_variant(_: &'static str, _: u32, _: &'static str) -> Self::Ok => "unit variant" - serialize_newtype_struct(_: &'static str, _: &T) -> Self::Ok => "newtype struct" - serialize_newtype_variant(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant" - serialize_seq(_: Option) -> Self::SerializeSeq => "sequence" - serialize_tuple(_: usize) -> Self::SerializeTuple => "tuple" - serialize_tuple_struct(_: &'static str, _: usize) -> Self::SerializeTupleStruct => "tuple struct" - serialize_tuple_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeTupleVariant => "tuple variant" - serialize_map(_: Option) -> Self::SerializeMap => "map" - serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct" - serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant" - } - } -} diff --git a/src/types/input_file.rs b/src/types/input_file.rs index baf487c0..26ee7b10 100644 --- a/src/types/input_file.rs +++ b/src/types/input_file.rs @@ -1,123 +1,277 @@ -use reqwest::Url; -use serde::{Deserialize, Serialize}; +use bytes::{Bytes, BytesMut}; +use futures::{ + future::{ready, Either}, + stream, +}; +use once_cell::sync::OnceCell; +use reqwest::{multipart::Part, Body}; +use serde::Serialize; +use tokio_util::codec::{Decoder, FramedRead}; -use std::{borrow::Cow, path::PathBuf}; +use std::{borrow::Cow, fmt, future::Future, io, mem, path::PathBuf, sync::Arc}; + +use crate::types::InputSticker; /// This object represents the contents of a file to be uploaded. /// /// [The official docs](https://core.telegram.org/bots/api#inputfile). -#[derive(Clone, Debug, Serialize, Deserialize)] -#[non_exhaustive] -pub enum InputFile { +#[derive(Debug, Clone)] +pub struct InputFile { + id: OnceCell>, + file_name: Option>, + inner: InnerFile, +} + +#[derive(Clone)] +enum InnerFile { File(PathBuf), - Memory { - file_name: String, - data: Cow<'static, [u8]>, - }, - Url(Url), + Bytes(bytes::Bytes), + Url(url::Url), FileId(String), } +use InnerFile::*; + impl InputFile { - pub fn file

(path: P) -> Self - where - P: Into, - { - Self::File(path.into()) + /// Creates an `InputFile` from an url. + /// + /// Notes: + /// - When sending by URL the target file must have the correct MIME type + /// (e.g., `audio/mpeg` for [`SendAudio`], etc.). + /// - In [`SendDocument`], sending by URL will currently only work for + /// `GIF`, `PDF` and `ZIP` files. + /// - To use [`SendVoice`], the file must have the type audio/ogg and be no + /// more than 1MB in size. 1-20MB voice notes will be sent as files. + /// - Other configurations may work but we can't guarantee that they will. + /// + /// [`SendAudio`]: crate::payloads::SendAudio + /// [`SendDocument`]: crate::payloads::SendDocument + /// [`SendVoice`]: crate::payloads::SendVoice + pub fn url(url: url::Url) -> Self { + Self::new(Url(url)) } - pub fn memory(file_name: S, data: D) -> Self - where - S: Into, - D: Into>, - { - Self::Memory { - file_name: file_name.into(), - data: data.into(), + /// Creates an `InputFile` from a file id. + /// + /// File id can be obtained from + /// + /// Notes: + /// - It is not possible to change the file type when resending by file id. + /// I.e. a video can't be sent as a photo, a photo can't be sent as a + /// document, etc. + /// - It is not possible to resend thumbnails. + /// - Resending a photo by file id will send all of its [sizes]. + /// - file id is unique for each individual bot and can't be transferred + /// from one bot to another. + /// - file id uniquely identifies a file, but a file can have different + /// valid file_ids even for the same bot. + /// + /// [sizes]: crate::types::PhotoSize + pub fn file_id(file_id: impl Into) -> Self { + Self::new(FileId(file_id.into())) + } + + /// Creates an `InputFile` from a file path. + pub fn file(path: impl Into) -> Self { + Self::new(File(path.into())) + } + + /// Creates an `InputFile` from a in-memory bytes. + pub fn memory(data: impl Into) -> Self { + Self::new(Bytes(data.into())) + } + + /// Set the file name for this file. + pub fn file_name(mut self, name: impl Into>) -> Self { + self.file_name = Some(name.into()); + self + } + + /// Shorthand for `Self { file_name: None, inner, id: default() }` + /// (private because `InnerFile` iы private implementation detail) + fn new(inner: InnerFile) -> Self { + Self { + file_name: None, + inner, + id: OnceCell::new(), } } - pub fn url(url: Url) -> Self { - Self::Url(url) + /// Returns id of this file. + /// + /// This is used to coordinate with `attach://`. + pub(crate) fn id(&self) -> &str { + // FIXME: remove extra alloc + self.id + .get_or_init(|| uuid::Uuid::new_v4().to_string().into()) } - pub fn file_id(file_id: T) -> Self - where - T: Into, - { - Self::FileId(file_id.into()) + /// Returns `true` if this file needs an attachment i.e. it's not a file_id + /// or url that can be serialized without any additional multipart parts. + pub(crate) fn needs_attach(&self) -> bool { + !matches!(self.inner, Url(_) | FileId(_)) } - pub fn as_file(&self) -> Option<&PathBuf> { - match self { - Self::File(path) => Some(path), - _ => None, + /// Takes this file out. + /// + /// **Note**: this replaces `self` with a dummy value, this function should + /// only be used when the file is about to get dropped. + pub(crate) fn take(&mut self) -> Self { + mem::replace(self, InputFile::file_id(String::new())) + } + + /// Returns an attach string for `multipart/form-data` in the form of + /// `"attach://{id}"` if this file should be uploaded via + /// `multipart/form-data`, or the value if it may be uploaded in any way (ie + /// it's an URL or file id). + fn attach_or_value(&self) -> String { + match &self.inner { + Url(url) => url.as_str().to_owned(), + FileId(file_id) => file_id.clone(), + _ => { + const PREFIX: &str = "attach://"; + + let id = self.id(); + let mut s = String::with_capacity(PREFIX.len() + id.len()); + s += PREFIX; + s += id; + + s + } } } - pub fn as_url(&self) -> Option<&Url> { - match self { - Self::Url(url) => Some(url), - _ => None, - } + /// Takes the file name or tries to guess it based on file name in the path + /// if `File.0`. Returns an empty string if couldn't guess. + fn take_or_guess_filename(&mut self) -> Cow<'static, str> { + self.file_name.take().unwrap_or_else(|| match &self.inner { + File(path_to_file) => match path_to_file.file_name() { + Some(name) => Cow::Owned(name.to_string_lossy().into_owned()), + None => Cow::Borrowed(""), + }, + _ => Cow::Borrowed(""), + }) } +} - pub fn as_file_id(&self) -> Option<&String> { +impl fmt::Debug for InnerFile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::FileId(id) => Some(id), - _ => None, + File(path) => f.debug_struct("File").field("path", path).finish(), + Bytes(bytes) if f.alternate() => f.debug_tuple("Memory").field(bytes).finish(), + Bytes(_) => f.debug_struct("Memory").finish_non_exhaustive(), + Url(url) => f.debug_tuple("Url").field(url).finish(), + FileId(file_id) => f.debug_tuple("FileId").field(file_id).finish(), } } } -impl From for Option { - fn from(file: InputFile) -> Self { - match file { - InputFile::File(path) => Some(path), - _ => None, - } +impl Serialize for InputFile { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.attach_or_value().serialize(serializer) } } // internal api -use reqwest::multipart::Part; - impl InputFile { - pub(crate) async fn into_part(self) -> std::io::Result { - use bytes::{Bytes, BytesMut}; - use reqwest::Body; - use tokio_util::codec::{Decoder, FramedRead}; + pub(crate) fn into_part(mut self) -> Option> { + let filename = self.take_or_guess_filename(); - struct FileDecoder; + let file_part = match self.inner { + // Url and FileId are serialized just as strings, they don't need additional parts + Url(_) | FileId(_) => None, - impl Decoder for FileDecoder { - type Item = Bytes; - type Error = std::io::Error; + File(path_to_file) => { + let fut = async { + let body = match tokio::fs::File::open(path_to_file).await { + Ok(file) => { + let file = FramedRead::new(file, BytesDecoder); - fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if src.is_empty() { - return Ok(None); - } - Ok(Some(src.split().freeze())) + Body::wrap_stream(file) + } + Err(err) => { + // explicit type needed for `Bytes: From` in `wrap_stream` + let err = Err::(err); + Body::wrap_stream(stream::iter([err])) + } + }; + + Part::stream(body).file_name(filename) + }; + + Some(Either::Left(fut)) } + Bytes(data) => { + let stream = Part::stream(data).file_name(filename); + Some(Either::Right(ready(stream))) + } + }; + + file_part + } +} + +struct BytesDecoder; + +impl Decoder for BytesDecoder { + type Item = Bytes; + type Error = io::Error; + + fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { + if src.is_empty() { + return Ok(None); } + Ok(Some(src.split().freeze())) + } +} - match self { - Self::File(path_to_file) => { - let file_name = path_to_file - .file_name() - .unwrap() - .to_string_lossy() - .into_owned(); +/// An internal trait that is used in expansion of `impl_payload!` used to work +/// with input-file-like things (`InputFile` itself, `Option`, +/// `InputSticker`) +pub(crate) trait InputFileLike { + fn copy_into(&self, into: &mut dyn FnMut(InputFile)); - let file = FramedRead::new(tokio::fs::File::open(path_to_file).await?, FileDecoder); + fn move_into(&mut self, into: &mut dyn FnMut(InputFile)); +} - Ok(Part::stream(Body::wrap_stream(file)).file_name(file_name)) - } - Self::Memory { file_name, data } => Ok(Part::bytes(data).file_name(file_name)), - Self::Url(s) => Ok(Part::text(String::from(s))), - Self::FileId(s) => Ok(Part::text(s)), +impl InputFileLike for InputFile { + fn copy_into(&self, into: &mut dyn FnMut(InputFile)) { + into(self.clone()) + } + + fn move_into(&mut self, into: &mut dyn FnMut(InputFile)) { + into(self.take()) + } +} + +impl InputFileLike for Option { + fn copy_into(&self, into: &mut dyn FnMut(InputFile)) { + if let Some(this) = self { + this.copy_into(into) + } + } + + fn move_into(&mut self, into: &mut dyn FnMut(InputFile)) { + if let Some(this) = self { + this.move_into(into) } } } + +impl InputFileLike for InputSticker { + fn copy_into(&self, into: &mut dyn FnMut(InputFile)) { + let (InputSticker::Png(input_file) | InputSticker::Tgs(input_file)) = self; + + input_file.copy_into(into) + } + + fn move_into(&mut self, into: &mut dyn FnMut(InputFile)) { + let (InputSticker::Png(input_file) | InputSticker::Tgs(input_file)) = self; + + input_file.move_into(into) + } +} diff --git a/src/types/input_media.rs b/src/types/input_media.rs index 4ef04ab9..fd4ec1d8 100644 --- a/src/types/input_media.rs +++ b/src/types/input_media.rs @@ -1,6 +1,6 @@ use std::iter; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use crate::types::{InputFile, MessageEntity, ParseMode}; @@ -533,7 +533,7 @@ mod tests { #[test] fn photo_serialize() { - let expected_json = r#"{"type":"photo","media":{"FileId":"123456"}}"#; + let expected_json = r#"{"type":"photo","media":"123456"}"#; let photo = InputMedia::Photo(InputMediaPhoto { media: InputFile::file_id("123456"), caption: None, @@ -547,7 +547,7 @@ mod tests { #[test] fn video_serialize() { - let expected_json = r#"{"type":"video","media":{"FileId":"123456"}}"#; + let expected_json = r#"{"type":"video","media":"123456"}"#; let video = InputMedia::Video(InputMediaVideo { media: InputFile::file_id("123456"), thumb: None, @@ -566,7 +566,7 @@ mod tests { #[test] fn animation_serialize() { - let expected_json = r#"{"type":"animation","media":{"FileId":"123456"}}"#; + let expected_json = r#"{"type":"animation","media":"123456"}"#; let video = InputMedia::Animation(InputMediaAnimation { media: InputFile::file_id("123456"), thumb: None, @@ -584,7 +584,7 @@ mod tests { #[test] fn audio_serialize() { - let expected_json = r#"{"type":"audio","media":{"FileId":"123456"}}"#; + let expected_json = r#"{"type":"audio","media":"123456"}"#; let video = InputMedia::Audio(InputMediaAudio { media: InputFile::file_id("123456"), thumb: None, @@ -602,7 +602,7 @@ mod tests { #[test] fn document_serialize() { - let expected_json = r#"{"type":"document","media":{"FileId":"123456"}}"#; + let expected_json = r#"{"type":"document","media":"123456"}"#; let video = InputMedia::Document(InputMediaDocument { media: InputFile::file_id("123456"), thumb: None,