mirror of
https://github.com/teloxide/teloxide.git
synced 2024-12-23 06:51:01 +01:00
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://<id>` string where `<id>` is replaced with some id unique for the file. The file data itself is acquired through `MultipartPayload` trait. Since the `<id>` 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
This commit is contained in:
parent
bed5805610
commit
a84e897db9
12 changed files with 652 additions and 743 deletions
|
@ -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"
|
||||
|
|
32
src/bot.rs
32
src/bot.rs
|
@ -233,7 +233,7 @@ impl Bot {
|
|||
|
||||
pub(crate) fn execute_multipart<P>(
|
||||
&self,
|
||||
payload: &P,
|
||||
payload: &mut P,
|
||||
) -> impl Future<Output = ResponseResult<P::Output>>
|
||||
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<P>(
|
||||
&self,
|
||||
payload: &P,
|
||||
) -> impl Future<Output = ResponseResult<P::Output>>
|
||||
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(),
|
||||
|
|
|
@ -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<Self::Ok, Self::Error> {
|
||||
Err(Self::Error::UnsupportedType {
|
||||
ty: stringify!($arg),
|
||||
supported: $supported,
|
||||
})
|
||||
}
|
||||
)+
|
||||
|
||||
$(
|
||||
fn $method1(self) -> Result<Self::Ok, Self::Error> {
|
||||
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)*) => {}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ where
|
|||
|
||||
req_future! {
|
||||
def: |it: MultipartRequest<U>| {
|
||||
it.bot.execute_multipart(&it.payload)
|
||||
it.bot.execute_multipart(&mut {it.payload})
|
||||
}
|
||||
pub Send<U> (inner0) -> ResponseResult<U::Output>
|
||||
where
|
||||
|
@ -101,7 +101,7 @@ req_future! {
|
|||
|
||||
req_future! {
|
||||
def: |it: &MultipartRequest<U>| {
|
||||
it.bot.execute_multipart(&it.payload)
|
||||
it.bot.execute_multipart_ref(&it.payload)
|
||||
}
|
||||
pub SendRef<U> (inner1) -> ResponseResult<U::Output>
|
||||
where
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
53
src/serde_multipart/error.rs
Normal file
53
src/serde_multipart/error.rs
Normal file
|
@ -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<T>(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<Error> 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
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<T: ?Sized + Serialize>(val: &T) -> impl Future<Output = Result<Form, Error>> {
|
||||
let fut = val.serialize(MultipartTopLvlSerializer {});
|
||||
async { Ok(fut?.await?) }
|
||||
pub(crate) fn to_form<T>(val: &mut T) -> Result<impl Future<Output = Form>, 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<T: ?Sized>(val: &T) -> Result<impl Future<Output = Form>, 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;
|
||||
}
|
||||
|
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
impl ser::Error for Error {
|
||||
fn custom<T>(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<Error> 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 = <MultipartSerializer as SerializeStruct>::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<Self::Ok, Self::Error>;
|
||||
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeMap = MultipartMapSerializer; // for `serde(flatten)` (e.g.: in CreateNewStickerSet)
|
||||
type SerializeStruct = MultipartSerializer;
|
||||
type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;
|
||||
|
||||
fn serialize_map(self, _: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||
Ok(MultipartMapSerializer {
|
||||
form: Form::new(),
|
||||
key: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
// Everything down below in this impl just returns
|
||||
// `Err(Error::TopLevelNotStruct)`
|
||||
|
||||
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
|
||||
Err(Error::TopLevelNotStruct)
|
||||
}
|
||||
|
@ -216,22 +213,6 @@ impl Serializer for MultipartTopLvlSerializer {
|
|||
Err(Error::TopLevelNotStruct)
|
||||
}
|
||||
|
||||
fn serialize_map(self, _: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
|
||||
Ok(MultipartMapSerializer {
|
||||
parts: vec![],
|
||||
files: vec![],
|
||||
key: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
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<Form>>; // impl Future<Output = io::Result<Form>>
|
||||
type Ok = Form;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T: ?Sized>(
|
||||
|
@ -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<Self::Ok, Self::Error> {
|
||||
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::<FuturesUnordered<_>>()
|
||||
.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<String>,
|
||||
}
|
||||
|
||||
impl SerializeMap for MultipartMapSerializer {
|
||||
type Ok = <MultipartSerializer as SerializeStruct>::Ok;
|
||||
type Ok = Form;
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_key<T: ?Sized>(&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<Self::Ok, Self::Error> {
|
||||
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::<FuturesUnordered<_>>()
|
||||
.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<Self::Ok, Self::Error>;
|
||||
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeMap = Impossible<Self::Ok, Self::Error>;
|
||||
type SerializeStruct = PartSerializerStruct;
|
||||
type SerializeStructVariant = PartFromFile;
|
||||
type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;
|
||||
|
||||
fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> {
|
||||
Ok((Part::text(v.to_string()), Vec::new()))
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_i8(self, _: i8) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_i16(self, _: i16) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
|
||||
Ok((Part::text(v.to_string()), Vec::new()))
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> {
|
||||
Ok((Part::text(v.to_string()), Vec::new()))
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_u8(self, _: u8) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_u16(self, _: u16) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_u32(self, _: u32) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_u64(self, _: u64) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_f32(self, _: f32) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_f64(self, _: f64) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_char(self, _: char) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::text(v.to_string()))
|
||||
}
|
||||
|
||||
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
|
||||
Ok((Part::text(v.to_owned()), Vec::new()))
|
||||
Ok(Part::text(v.to_owned()))
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, _: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(Part::bytes(v.to_owned()))
|
||||
}
|
||||
|
||||
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
|
||||
|
@ -435,21 +359,48 @@ impl Serializer for PartSerializer {
|
|||
value.serialize(self)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_unit_variant(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: u32,
|
||||
variant_name: &'static str,
|
||||
) -> Result<Self::Ok, Self::Error> {
|
||||
Ok((Part::text(variant_name), Vec::new()))
|
||||
Ok(Part::text(variant_name))
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
_: &'static str,
|
||||
_: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
Ok(JsonPartSerializer {
|
||||
buf: String::new(),
|
||||
state: PartSerializerStructState::Empty,
|
||||
})
|
||||
}
|
||||
|
||||
fn serialize_seq(self, _: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
Ok(JsonPartSerializer {
|
||||
buf: String::new(),
|
||||
state: PartSerializerStructState::Empty,
|
||||
})
|
||||
}
|
||||
|
||||
// Unimplemented
|
||||
|
||||
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!(
|
||||
"We use `#[serde_with_macros::skip_serializing_none]` everywhere so `None`s are not \
|
||||
serialized"
|
||||
)
|
||||
}
|
||||
|
||||
fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_unit_struct(self, _: &'static str) -> Result<Self::Ok, Self::Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_newtype_struct<T: ?Sized>(
|
||||
|
@ -465,37 +416,15 @@ impl Serializer for PartSerializer {
|
|||
|
||||
fn serialize_newtype_variant<T: ?Sized>(
|
||||
self,
|
||||
name: &'static str,
|
||||
variant_index: u32,
|
||||
variant: &'static str,
|
||||
value: &T,
|
||||
_name: &'static str,
|
||||
_variant_index: u32,
|
||||
_variant: &'static str,
|
||||
_value: &T,
|
||||
) -> Result<Self::Ok, Self::Error>
|
||||
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<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
Ok(Self::SerializeSeq {
|
||||
array_json_parts: vec![],
|
||||
files: vec![],
|
||||
})
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||
|
@ -524,44 +453,19 @@ impl Serializer for PartSerializer {
|
|||
unimplemented!()
|
||||
}
|
||||
|
||||
fn serialize_struct(
|
||||
self,
|
||||
name: &'static str,
|
||||
len: usize,
|
||||
) -> Result<Self::SerializeStruct, Self::Error> {
|
||||
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<Self::SerializeStructVariant, Self::Error> {
|
||||
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<T: ?Sized>(
|
||||
|
@ -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<Self::Ok, Self::Error> {
|
||||
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<serde_json::Value>, // 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<T: ?Sized>(&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<Self::Ok, Self::Error> {
|
||||
let s = serde_json::to_string(&self.array_json_parts)?;
|
||||
Ok((Part::text(s), self.files))
|
||||
}
|
||||
}
|
||||
|
||||
struct PartSerializerStruct(
|
||||
serde_json::Serializer<Vec<u8>>,
|
||||
serde_json::ser::State,
|
||||
Vec<(String, InputFile)>,
|
||||
);
|
||||
|
||||
impl SerializeStruct for PartSerializerStruct {
|
||||
type Ok = (Part, Vec<(String, InputFile)>);
|
||||
type Error = Error;
|
||||
|
||||
fn serialize_field<T: ?Sized>(
|
||||
&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<Self::Ok, Self::Error> {
|
||||
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<Vec<u8>, 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<T: ?Sized>(&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<Self::Ok, Self::Error> {
|
||||
use PartSerializerStructState::*;
|
||||
|
||||
match self.state {
|
||||
Empty => Ok(Part::text("[]")),
|
||||
Rest => {
|
||||
self.buf += "]";
|
||||
|
||||
Ok(Part::text(self.buf))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,145 +0,0 @@
|
|||
use crate::serde_multipart::unserializers::UnserializerError;
|
||||
use serde::{
|
||||
ser::{Impossible, SerializeSeq},
|
||||
Serialize, Serializer,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct BytesUnserializer(Vec<u8>);
|
||||
|
||||
impl Serializer for BytesUnserializer {
|
||||
type Ok = Vec<u8>;
|
||||
type Error = UnserializerError;
|
||||
|
||||
type SerializeSeq = Self;
|
||||
type SerializeTuple = Impossible<Vec<u8>, UnserializerError>;
|
||||
type SerializeTupleStruct = Impossible<Vec<u8>, UnserializerError>;
|
||||
type SerializeTupleVariant = Impossible<Vec<u8>, UnserializerError>;
|
||||
type SerializeMap = Impossible<Vec<u8>, UnserializerError>;
|
||||
type SerializeStruct = Impossible<Vec<u8>, UnserializerError>;
|
||||
type SerializeStructVariant = Impossible<Vec<u8>, UnserializerError>;
|
||||
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(v.to_owned())
|
||||
}
|
||||
|
||||
fn serialize_seq(mut self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
|
||||
if let Some(len) = len {
|
||||
self.0.reserve_exact(len);
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
forward_to_unsuported_ty! {
|
||||
supported: "&[u8], Vec<u8>, 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: ?Sized + Serialize>(_: &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<T: ?Sized + Serialize>(_: &'static str, _: &T) -> Self::Ok => "newtype struct"
|
||||
serialize_newtype_variant<T: ?Sized + Serialize>(_: &'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<usize>) -> 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<u8>;
|
||||
type Error = UnserializerError;
|
||||
|
||||
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
value.serialize(BytesUnserializerPush(&mut self.0))
|
||||
}
|
||||
|
||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BytesUnserializerPush<'a>(&'a mut Vec<u8>);
|
||||
|
||||
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::Ok, Self::Error> {
|
||||
self.0.push(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||
self.0.extend_from_slice(v);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
forward_to_unsuported_ty! {
|
||||
supported: "&[u8], Vec<u8>, 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: ?Sized + Serialize>(_: &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<T: ?Sized + Serialize>(_: &'static str, _: &T) -> Self::Ok => "newtype struct"
|
||||
serialize_newtype_variant<T: ?Sized + Serialize>(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant"
|
||||
serialize_seq(_: Option<usize>) -> 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<usize>) -> Self::SerializeMap => "map"
|
||||
serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct"
|
||||
serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<String, UnserializerError>;
|
||||
type SerializeTuple = Impossible<String, UnserializerError>;
|
||||
type SerializeTupleStruct = Impossible<String, UnserializerError>;
|
||||
type SerializeTupleVariant = Impossible<String, UnserializerError>;
|
||||
type SerializeMap = Impossible<String, UnserializerError>;
|
||||
type SerializeStruct = Impossible<String, UnserializerError>;
|
||||
type SerializeStructVariant = Impossible<String, UnserializerError>;
|
||||
|
||||
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
|
||||
Ok(v.to_string())
|
||||
}
|
||||
|
||||
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
|
||||
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: ?Sized + Serialize>(_: &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<T: ?Sized + Serialize>(_: &'static str, _: &T) -> Self::Ok => "newtype struct"
|
||||
serialize_newtype_variant<T: ?Sized + Serialize>(_: &'static str, _: u32, _: &'static str, _: &T) -> Self::Ok => "newtype variant"
|
||||
serialize_seq(_: Option<usize>) -> 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<usize>) -> Self::SerializeMap => "map"
|
||||
serialize_struct(_: &'static str, _: usize) -> Self::SerializeStruct => "struct"
|
||||
serialize_struct_variant(_: &'static str, _: u32, _: &'static str, _: usize) -> Self::SerializeStructVariant => "struct variant"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Arc<str>>,
|
||||
file_name: Option<Cow<'static, str>>,
|
||||
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<P>(path: P) -> Self
|
||||
where
|
||||
P: Into<PathBuf>,
|
||||
{
|
||||
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<S, D>(file_name: S, data: D) -> Self
|
||||
where
|
||||
S: Into<String>,
|
||||
D: Into<Cow<'static, [u8]>>,
|
||||
{
|
||||
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<String>) -> Self {
|
||||
Self::new(FileId(file_id.into()))
|
||||
}
|
||||
|
||||
/// Creates an `InputFile` from a file path.
|
||||
pub fn file(path: impl Into<PathBuf>) -> Self {
|
||||
Self::new(File(path.into()))
|
||||
}
|
||||
|
||||
/// Creates an `InputFile` from a in-memory bytes.
|
||||
pub fn memory(data: impl Into<bytes::Bytes>) -> Self {
|
||||
Self::new(Bytes(data.into()))
|
||||
}
|
||||
|
||||
/// Set the file name for this file.
|
||||
pub fn file_name(mut self, name: impl Into<Cow<'static, str>>) -> 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<T>(file_id: T) -> Self
|
||||
where
|
||||
T: Into<String>,
|
||||
{
|
||||
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<InputFile> for Option<PathBuf> {
|
||||
fn from(file: InputFile) -> Self {
|
||||
match file {
|
||||
InputFile::File(path) => Some(path),
|
||||
_ => None,
|
||||
}
|
||||
impl Serialize for InputFile {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
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<Part> {
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use reqwest::Body;
|
||||
use tokio_util::codec::{Decoder, FramedRead};
|
||||
pub(crate) fn into_part(mut self) -> Option<impl Future<Output = Part>> {
|
||||
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<Option<Self::Item>, 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<?T>` in `wrap_stream`
|
||||
let err = Err::<Bytes, _>(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<Option<Self::Item>, 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<InputFile>`,
|
||||
/// `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<InputFile> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue