mirror of
https://github.com/teloxide/teloxide.git
synced 2025-03-25 16:12:06 +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"
|
mime = "0.3.16"
|
||||||
thiserror = "1.0.20"
|
thiserror = "1.0.20"
|
||||||
once_cell = "1.5.0"
|
once_cell = "1.5.0"
|
||||||
|
take_mut = "0.2"
|
||||||
never = "0.1.0"
|
never = "0.1.0"
|
||||||
chrono = { version = "0.4.19", default-features = false }
|
chrono = { version = "0.4.19", default-features = false }
|
||||||
either = "1.6.1"
|
either = "1.6.1"
|
||||||
|
|
32
src/bot.rs
32
src/bot.rs
|
@ -233,7 +233,7 @@ impl Bot {
|
||||||
|
|
||||||
pub(crate) fn execute_multipart<P>(
|
pub(crate) fn execute_multipart<P>(
|
||||||
&self,
|
&self,
|
||||||
payload: &P,
|
payload: &mut P,
|
||||||
) -> impl Future<Output = ResponseResult<P::Output>>
|
) -> impl Future<Output = ResponseResult<P::Output>>
|
||||||
where
|
where
|
||||||
P: MultipartPayload + Serialize,
|
P: MultipartPayload + Serialize,
|
||||||
|
@ -247,7 +247,35 @@ impl Bot {
|
||||||
|
|
||||||
// async move to capture client&token&api_url¶ms
|
// async move to capture client&token&api_url¶ms
|
||||||
async move {
|
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(
|
net::request_multipart(
|
||||||
&client,
|
&client,
|
||||||
token.as_ref(),
|
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 {
|
macro_rules! req_future {
|
||||||
(
|
(
|
||||||
$v2:vis def: | $( $arg:ident: $ArgTy:ty ),* $(,)? | $body:block
|
$v2:vis def: | $( $arg:ident: $ArgTy:ty ),* $(,)? | $body:block
|
||||||
|
@ -388,7 +350,19 @@ macro_rules! impl_payload {
|
||||||
$e
|
$e
|
||||||
};
|
};
|
||||||
(@[multipart = $($multipart_attr:ident),*] $Method:ident req { $($reqf:ident),* } opt { $($optf:ident),*} ) => {
|
(@[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)*) => {}
|
(@[] $($ignored:tt)*) => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ where
|
||||||
|
|
||||||
req_future! {
|
req_future! {
|
||||||
def: |it: MultipartRequest<U>| {
|
def: |it: MultipartRequest<U>| {
|
||||||
it.bot.execute_multipart(&it.payload)
|
it.bot.execute_multipart(&mut {it.payload})
|
||||||
}
|
}
|
||||||
pub Send<U> (inner0) -> ResponseResult<U::Output>
|
pub Send<U> (inner0) -> ResponseResult<U::Output>
|
||||||
where
|
where
|
||||||
|
@ -101,7 +101,7 @@ req_future! {
|
||||||
|
|
||||||
req_future! {
|
req_future! {
|
||||||
def: |it: &MultipartRequest<U>| {
|
def: |it: &MultipartRequest<U>| {
|
||||||
it.bot.execute_multipart(&it.payload)
|
it.bot.execute_multipart_ref(&it.payload)
|
||||||
}
|
}
|
||||||
pub SendRef<U> (inner1) -> ResponseResult<U::Output>
|
pub SendRef<U> (inner1) -> ResponseResult<U::Output>
|
||||||
where
|
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
|
/// Payloads that need to be sent as `multipart/form-data` because they contain
|
||||||
/// files inside.
|
/// 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
|
//! 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).
|
//! versions (in favor of something less automatic, but more simple).
|
||||||
|
|
||||||
|
mod error;
|
||||||
mod serializers;
|
mod serializers;
|
||||||
mod unserializers;
|
|
||||||
|
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
use reqwest::multipart::Form;
|
use reqwest::multipart::Form;
|
||||||
use serde::Serialize;
|
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`] **taking all input files out**.
|
||||||
|
|
||||||
/// Serializes given value into [`Form`]
|
|
||||||
///
|
///
|
||||||
/// [`Form`]: reqwest::multipart::Form
|
/// [`Form`]: reqwest::multipart::Form
|
||||||
pub(crate) fn to_form<T: ?Sized + Serialize>(val: &T) -> impl Future<Output = Result<Form, Error>> {
|
pub(crate) fn to_form<T>(val: &mut T) -> Result<impl Future<Output = Form>, Error>
|
||||||
let fut = val.serialize(MultipartTopLvlSerializer {});
|
where
|
||||||
async { Ok(fut?.await?) }
|
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
|
// https://github.com/teloxide/teloxide/issues/473
|
||||||
|
@ -39,13 +89,13 @@ async fn issue_473() {
|
||||||
types::{InputFile, MessageEntity, MessageEntityKind},
|
types::{InputFile, MessageEntity, MessageEntityKind},
|
||||||
};
|
};
|
||||||
|
|
||||||
to_form(
|
to_form_ref(
|
||||||
&payloads::SendPhoto::new(0, InputFile::file_id("0")).caption_entities([MessageEntity {
|
&payloads::SendPhoto::new(0, InputFile::file_id("0")).caption_entities([MessageEntity {
|
||||||
kind: MessageEntityKind::Url,
|
kind: MessageEntityKind::Url,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
length: 0,
|
length: 0,
|
||||||
}]),
|
}]),
|
||||||
)
|
)
|
||||||
.await
|
.unwrap()
|
||||||
.unwrap();
|
.await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,87 +1,84 @@
|
||||||
use crate::{
|
use crate::serde_multipart::error::Error;
|
||||||
serde_multipart::unserializers::{InputFileUnserializer, StringUnserializer},
|
|
||||||
types::InputFile,
|
|
||||||
RequestError,
|
|
||||||
};
|
|
||||||
use futures::{
|
|
||||||
future::{ready, BoxFuture},
|
|
||||||
stream::FuturesUnordered,
|
|
||||||
FutureExt, StreamExt, TryStreamExt,
|
|
||||||
};
|
|
||||||
use reqwest::multipart::{Form, Part};
|
use reqwest::multipart::{Form, Part};
|
||||||
use serde::{
|
use serde::{
|
||||||
ser,
|
ser::{Impossible, SerializeMap, SerializeSeq, SerializeStruct},
|
||||||
ser::{Impossible, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant},
|
|
||||||
Serialize, Serializer,
|
Serialize, Serializer,
|
||||||
};
|
};
|
||||||
use std::{fmt, fmt::Display, io};
|
|
||||||
|
|
||||||
#[derive(Debug, derive_more::From)]
|
/// The main serializer that serializes top-level and structures
|
||||||
pub(crate) enum Error {
|
pub(super) struct MultipartSerializer(Form);
|
||||||
Custom(String),
|
|
||||||
TopLevelNotStruct,
|
/// Serializer for maps (support for `#[serde(flatten)]`)
|
||||||
InputFileUnserializer(crate::serde_multipart::unserializers::UnserializerError),
|
pub(super) struct MultipartMapSerializer {
|
||||||
Io(std::io::Error),
|
form: Form,
|
||||||
Json(serde_json::Error),
|
key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ser::Error for Error {
|
/// Serializer for single "fields" that are serialized as multipart "part"s.
|
||||||
fn custom<T>(msg: T) -> Self
|
///
|
||||||
where
|
/// - Integers serialized as their text decimal representation
|
||||||
T: Display,
|
/// - Strings and byte slices are serialized as-is, without any changes
|
||||||
{
|
/// - Structs are serialized with JSON
|
||||||
Self::Custom(msg.to_string())
|
/// - 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 {
|
impl Serializer for MultipartSerializer {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
type Ok = Form;
|
||||||
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;
|
|
||||||
type Error = Error;
|
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 SerializeSeq = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
|
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeTupleVariant = 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>;
|
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> {
|
fn serialize_bool(self, _: bool) -> Result<Self::Ok, Self::Error> {
|
||||||
Err(Error::TopLevelNotStruct)
|
Err(Error::TopLevelNotStruct)
|
||||||
}
|
}
|
||||||
|
@ -216,22 +213,6 @@ impl Serializer for MultipartTopLvlSerializer {
|
||||||
Err(Error::TopLevelNotStruct)
|
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(
|
fn serialize_struct_variant(
|
||||||
self,
|
self,
|
||||||
_: &'static str,
|
_: &'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 {
|
impl SerializeStruct for MultipartSerializer {
|
||||||
type Ok = BoxFuture<'static, io::Result<Form>>; // impl Future<Output = io::Result<Form>>
|
type Ok = Form;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn serialize_field<T: ?Sized>(
|
fn serialize_field<T: ?Sized>(
|
||||||
|
@ -269,52 +236,29 @@ impl SerializeStruct for MultipartSerializer {
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
let (part, file) = value.serialize(PartSerializer {})?;
|
let part = value.serialize(PartSerializer {})?;
|
||||||
self.parts.push((key, part));
|
take_mut::take(&mut self.0, |f| f.part(key, part));
|
||||||
self.files.extend(file);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
let form = self
|
Ok(self.0)
|
||||||
.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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct MultipartMapSerializer {
|
|
||||||
parts: Vec<(String, Part)>, // TODO: Array vecs
|
|
||||||
files: Vec<(String, InputFile)>,
|
|
||||||
key: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SerializeMap for MultipartMapSerializer {
|
impl SerializeMap for MultipartMapSerializer {
|
||||||
type Ok = <MultipartSerializer as SerializeStruct>::Ok;
|
type Ok = Form;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), Self::Error>
|
fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<(), Self::Error>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -322,110 +266,90 @@ impl SerializeMap for MultipartMapSerializer {
|
||||||
where
|
where
|
||||||
T: Serialize,
|
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 {})?;
|
let part = value.serialize(PartSerializer {})?;
|
||||||
self.parts.push((key, part));
|
|
||||||
self.files.extend(file);
|
|
||||||
|
|
||||||
|
take_mut::take(&mut self.form, |f| f.part(key, part));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
fn end(self) -> Result<Self::Ok, Self::Error> {
|
||||||
let form = self
|
Ok(self.form)
|
||||||
.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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PartSerializer {}
|
|
||||||
|
|
||||||
impl Serializer for PartSerializer {
|
impl Serializer for PartSerializer {
|
||||||
type Ok = (Part, Vec<(String, InputFile)>);
|
type Ok = Part;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type SerializeSeq = InnerPartSerializer;
|
|
||||||
|
type SerializeStruct = JsonPartSerializer;
|
||||||
|
type SerializeSeq = JsonPartSerializer;
|
||||||
|
|
||||||
|
// Unimplemented
|
||||||
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
|
type SerializeTuple = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
type SerializeTupleStruct = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
|
type SerializeTupleVariant = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeMap = Impossible<Self::Ok, Self::Error>;
|
type SerializeMap = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeStruct = PartSerializerStruct;
|
type SerializeStructVariant = Impossible<Self::Ok, Self::Error>;
|
||||||
type SerializeStructVariant = PartFromFile;
|
|
||||||
|
|
||||||
fn serialize_bool(self, v: bool) -> Result<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> {
|
fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_i16(self, _: i16) -> Result<Self::Ok, Self::Error> {
|
fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> {
|
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> {
|
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> {
|
fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_u16(self, _: u16) -> Result<Self::Ok, Self::Error> {
|
fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_u32(self, _: u32) -> Result<Self::Ok, Self::Error> {
|
fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_u64(self, _: u64) -> Result<Self::Ok, Self::Error> {
|
fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_f32(self, _: f32) -> Result<Self::Ok, Self::Error> {
|
fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_f64(self, _: f64) -> Result<Self::Ok, Self::Error> {
|
fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_char(self, _: char) -> Result<Self::Ok, Self::Error> {
|
fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::text(v.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> {
|
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> {
|
fn serialize_bytes(self, v: &[u8]) -> Result<Self::Ok, Self::Error> {
|
||||||
unimplemented!()
|
Ok(Part::bytes(v.to_owned()))
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
|
fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error>
|
||||||
|
@ -435,21 +359,48 @@ impl Serializer for PartSerializer {
|
||||||
value.serialize(self)
|
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(
|
fn serialize_unit_variant(
|
||||||
self,
|
self,
|
||||||
_: &'static str,
|
_: &'static str,
|
||||||
_: u32,
|
_: u32,
|
||||||
variant_name: &'static str,
|
variant_name: &'static str,
|
||||||
) -> Result<Self::Ok, Self::Error> {
|
) -> 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>(
|
fn serialize_newtype_struct<T: ?Sized>(
|
||||||
|
@ -465,37 +416,15 @@ impl Serializer for PartSerializer {
|
||||||
|
|
||||||
fn serialize_newtype_variant<T: ?Sized>(
|
fn serialize_newtype_variant<T: ?Sized>(
|
||||||
self,
|
self,
|
||||||
name: &'static str,
|
_name: &'static str,
|
||||||
variant_index: u32,
|
_variant_index: u32,
|
||||||
variant: &'static str,
|
_variant: &'static str,
|
||||||
value: &T,
|
_value: &T,
|
||||||
) -> Result<Self::Ok, Self::Error>
|
) -> Result<Self::Ok, Self::Error>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
let file: InputFile = InputFileUnserializer::NotMem.serialize_newtype_variant(
|
unimplemented!()
|
||||||
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![],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
fn serialize_tuple(self, _: usize) -> Result<Self::SerializeTuple, Self::Error> {
|
||||||
|
@ -524,44 +453,19 @@ impl Serializer for PartSerializer {
|
||||||
unimplemented!()
|
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(
|
fn serialize_struct_variant(
|
||||||
self,
|
self,
|
||||||
name: &'static str,
|
_name: &'static str,
|
||||||
variant_index: u32,
|
_variant_index: u32,
|
||||||
variant: &'static str,
|
_variant: &'static str,
|
||||||
len: usize,
|
_len: usize,
|
||||||
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
) -> Result<Self::SerializeStructVariant, Self::Error> {
|
||||||
Ok(PartFromFile {
|
unimplemented!()
|
||||||
inner: InputFileUnserializer::memory().serialize_struct_variant(
|
|
||||||
name,
|
|
||||||
variant_index,
|
|
||||||
variant,
|
|
||||||
len,
|
|
||||||
)?,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PartFromFile {
|
impl SerializeStruct for JsonPartSerializer {
|
||||||
inner: InputFileUnserializer,
|
type Ok = Part;
|
||||||
}
|
|
||||||
|
|
||||||
impl SerializeStructVariant for PartFromFile {
|
|
||||||
type Ok = (Part, Vec<(String, InputFile)>);
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
|
||||||
fn serialize_field<T: ?Sized>(
|
fn serialize_field<T: ?Sized>(
|
||||||
|
@ -572,158 +476,71 @@ impl SerializeStructVariant for PartFromFile {
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
self.inner
|
use std::fmt::Write;
|
||||||
.serialize_field(key, value)
|
use PartSerializerStructState::*;
|
||||||
.map_err(Error::InputFileUnserializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn end(self) -> Result<Self::Ok, Self::Error> {
|
let value = serde_json::to_string(value)?;
|
||||||
let file = self.inner.end()?;
|
match self.state {
|
||||||
|
Empty => {
|
||||||
|
self.state = Rest;
|
||||||
|
|
||||||
// TODO: to method
|
write!(&mut self.buf, "{{\"{}\":{}", key, 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![])),
|
Rest => write!(&mut self.buf, ",\"{}\":{}", key, value)?,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(mut self) -> Result<Self::Ok, Self::Error> {
|
fn end(mut self) -> Result<Self::Ok, Self::Error> {
|
||||||
let state = match self.1 {
|
use PartSerializerStructState::*;
|
||||||
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)?;
|
|
||||||
|
|
||||||
let json = self.0.into_inner();
|
match self.state {
|
||||||
Ok((Part::bytes(json), self.2))
|
Empty => Ok(Part::text("{{}}")),
|
||||||
|
Rest => {
|
||||||
|
self.buf += "}";
|
||||||
|
|
||||||
|
Ok(Part::text(self.buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_state(
|
impl SerializeSeq for JsonPartSerializer {
|
||||||
compound: serde_json::ser::Compound<Vec<u8>, serde_json::ser::CompactFormatter>,
|
type Ok = Part;
|
||||||
) -> serde_json::ser::State {
|
|
||||||
// Compound may have more variants under some serde_json features
|
type Error = Error;
|
||||||
#[allow(unreachable_patterns)]
|
|
||||||
match compound {
|
fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<(), Self::Error>
|
||||||
serde_json::ser::Compound::Map { ser: _, state } => state,
|
where
|
||||||
_ => unreachable!(),
|
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 bytes::{Bytes, BytesMut};
|
||||||
use serde::{Deserialize, Serialize};
|
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.
|
/// This object represents the contents of a file to be uploaded.
|
||||||
///
|
///
|
||||||
/// [The official docs](https://core.telegram.org/bots/api#inputfile).
|
/// [The official docs](https://core.telegram.org/bots/api#inputfile).
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Debug, Clone)]
|
||||||
#[non_exhaustive]
|
pub struct InputFile {
|
||||||
pub enum InputFile {
|
id: OnceCell<Arc<str>>,
|
||||||
|
file_name: Option<Cow<'static, str>>,
|
||||||
|
inner: InnerFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum InnerFile {
|
||||||
File(PathBuf),
|
File(PathBuf),
|
||||||
Memory {
|
Bytes(bytes::Bytes),
|
||||||
file_name: String,
|
Url(url::Url),
|
||||||
data: Cow<'static, [u8]>,
|
|
||||||
},
|
|
||||||
Url(Url),
|
|
||||||
FileId(String),
|
FileId(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use InnerFile::*;
|
||||||
|
|
||||||
impl InputFile {
|
impl InputFile {
|
||||||
pub fn file<P>(path: P) -> Self
|
/// Creates an `InputFile` from an url.
|
||||||
where
|
///
|
||||||
P: Into<PathBuf>,
|
/// Notes:
|
||||||
{
|
/// - When sending by URL the target file must have the correct MIME type
|
||||||
Self::File(path.into())
|
/// (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
|
/// Creates an `InputFile` from a file id.
|
||||||
where
|
///
|
||||||
S: Into<String>,
|
/// File id can be obtained from
|
||||||
D: Into<Cow<'static, [u8]>>,
|
///
|
||||||
{
|
/// Notes:
|
||||||
Self::Memory {
|
/// - It is not possible to change the file type when resending by file id.
|
||||||
file_name: file_name.into(),
|
/// I.e. a video can't be sent as a photo, a photo can't be sent as a
|
||||||
data: data.into(),
|
/// 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 {
|
/// Returns id of this file.
|
||||||
Self::Url(url)
|
///
|
||||||
|
/// 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
|
/// Returns `true` if this file needs an attachment i.e. it's not a file_id
|
||||||
where
|
/// or url that can be serialized without any additional multipart parts.
|
||||||
T: Into<String>,
|
pub(crate) fn needs_attach(&self) -> bool {
|
||||||
{
|
!matches!(self.inner, Url(_) | FileId(_))
|
||||||
Self::FileId(file_id.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_file(&self) -> Option<&PathBuf> {
|
/// Takes this file out.
|
||||||
match self {
|
///
|
||||||
Self::File(path) => Some(path),
|
/// **Note**: this replaces `self` with a dummy value, this function should
|
||||||
_ => None,
|
/// 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> {
|
/// Takes the file name or tries to guess it based on file name in the path
|
||||||
match self {
|
/// if `File.0`. Returns an empty string if couldn't guess.
|
||||||
Self::Url(url) => Some(url),
|
fn take_or_guess_filename(&mut self) -> Cow<'static, str> {
|
||||||
_ => None,
|
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 {
|
match self {
|
||||||
Self::FileId(id) => Some(id),
|
File(path) => f.debug_struct("File").field("path", path).finish(),
|
||||||
_ => None,
|
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> {
|
impl Serialize for InputFile {
|
||||||
fn from(file: InputFile) -> Self {
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
match file {
|
where
|
||||||
InputFile::File(path) => Some(path),
|
S: serde::Serializer,
|
||||||
_ => None,
|
{
|
||||||
}
|
self.attach_or_value().serialize(serializer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal api
|
// internal api
|
||||||
|
|
||||||
use reqwest::multipart::Part;
|
|
||||||
|
|
||||||
impl InputFile {
|
impl InputFile {
|
||||||
pub(crate) async fn into_part(self) -> std::io::Result<Part> {
|
pub(crate) fn into_part(mut self) -> Option<impl Future<Output = Part>> {
|
||||||
use bytes::{Bytes, BytesMut};
|
let filename = self.take_or_guess_filename();
|
||||||
use reqwest::Body;
|
|
||||||
use tokio_util::codec::{Decoder, FramedRead};
|
|
||||||
|
|
||||||
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 {
|
File(path_to_file) => {
|
||||||
type Item = Bytes;
|
let fut = async {
|
||||||
type Error = std::io::Error;
|
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> {
|
Body::wrap_stream(file)
|
||||||
if src.is_empty() {
|
}
|
||||||
return Ok(None);
|
Err(err) => {
|
||||||
}
|
// explicit type needed for `Bytes: From<?T>` in `wrap_stream`
|
||||||
Ok(Some(src.split().freeze()))
|
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 {
|
/// An internal trait that is used in expansion of `impl_payload!` used to work
|
||||||
Self::File(path_to_file) => {
|
/// with input-file-like things (`InputFile` itself, `Option<InputFile>`,
|
||||||
let file_name = path_to_file
|
/// `InputSticker`)
|
||||||
.file_name()
|
pub(crate) trait InputFileLike {
|
||||||
.unwrap()
|
fn copy_into(&self, into: &mut dyn FnMut(InputFile));
|
||||||
.to_string_lossy()
|
|
||||||
.into_owned();
|
|
||||||
|
|
||||||
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))
|
impl InputFileLike for InputFile {
|
||||||
}
|
fn copy_into(&self, into: &mut dyn FnMut(InputFile)) {
|
||||||
Self::Memory { file_name, data } => Ok(Part::bytes(data).file_name(file_name)),
|
into(self.clone())
|
||||||
Self::Url(s) => Ok(Part::text(String::from(s))),
|
}
|
||||||
Self::FileId(s) => Ok(Part::text(s)),
|
|
||||||
|
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 std::iter;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::types::{InputFile, MessageEntity, ParseMode};
|
use crate::types::{InputFile, MessageEntity, ParseMode};
|
||||||
|
|
||||||
|
@ -533,7 +533,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn photo_serialize() {
|
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 {
|
let photo = InputMedia::Photo(InputMediaPhoto {
|
||||||
media: InputFile::file_id("123456"),
|
media: InputFile::file_id("123456"),
|
||||||
caption: None,
|
caption: None,
|
||||||
|
@ -547,7 +547,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn video_serialize() {
|
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 {
|
let video = InputMedia::Video(InputMediaVideo {
|
||||||
media: InputFile::file_id("123456"),
|
media: InputFile::file_id("123456"),
|
||||||
thumb: None,
|
thumb: None,
|
||||||
|
@ -566,7 +566,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn animation_serialize() {
|
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 {
|
let video = InputMedia::Animation(InputMediaAnimation {
|
||||||
media: InputFile::file_id("123456"),
|
media: InputFile::file_id("123456"),
|
||||||
thumb: None,
|
thumb: None,
|
||||||
|
@ -584,7 +584,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn audio_serialize() {
|
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 {
|
let video = InputMedia::Audio(InputMediaAudio {
|
||||||
media: InputFile::file_id("123456"),
|
media: InputFile::file_id("123456"),
|
||||||
thumb: None,
|
thumb: None,
|
||||||
|
@ -602,7 +602,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn document_serialize() {
|
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 {
|
let video = InputMedia::Document(InputMediaDocument {
|
||||||
media: InputFile::file_id("123456"),
|
media: InputFile::file_id("123456"),
|
||||||
thumb: None,
|
thumb: None,
|
||||||
|
|
Loading…
Add table
Reference in a new issue