use std::fmt;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::pin::Pin;
use bytes::Bytes;
use reqwest::header::CONTENT_TYPE;
use reqwest::multipart::Part;
use tokio::io::{AsyncRead, AsyncReadExt};
use crate::error::{Error, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MultipartField {
pub name: String,
pub value: String,
}
pub type FileLike = UploadSource;
pub enum ToFileInput {
Path(PathBuf),
Bytes(Bytes),
UploadSource(UploadSource),
Reader(Box<dyn Read + Send>),
AsyncReader(Pin<Box<dyn AsyncRead + Send>>),
Response(reqwest::Response),
}
impl fmt::Debug for ToFileInput {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Path(path) => f.debug_tuple("ToFileInput::Path").field(path).finish(),
Self::Bytes(bytes) => f
.debug_struct("ToFileInput::Bytes")
.field("size", &bytes.len())
.finish(),
Self::UploadSource(source) => f
.debug_tuple("ToFileInput::UploadSource")
.field(source)
.finish(),
Self::Reader(_) => f.write_str("ToFileInput::Reader(..)"),
Self::AsyncReader(_) => f.write_str("ToFileInput::AsyncReader(..)"),
Self::Response(response) => f
.debug_struct("ToFileInput::Response")
.field("url", response.url())
.finish(),
}
}
}
impl ToFileInput {
pub fn path(path: impl Into<PathBuf>) -> Self {
Self::Path(path.into())
}
pub fn reader<R>(reader: R) -> Self
where
R: Read + Send + 'static,
{
Self::Reader(Box::new(reader))
}
pub fn async_reader<R>(reader: R) -> Self
where
R: AsyncRead + Send + 'static,
{
Self::AsyncReader(Box::pin(reader))
}
pub fn upload(source: UploadSource) -> Self {
Self::UploadSource(source)
}
}
impl From<PathBuf> for ToFileInput {
fn from(value: PathBuf) -> Self {
Self::Path(value)
}
}
impl From<Vec<u8>> for ToFileInput {
fn from(value: Vec<u8>) -> Self {
Self::Bytes(Bytes::from(value))
}
}
impl From<Bytes> for ToFileInput {
fn from(value: Bytes) -> Self {
Self::Bytes(value)
}
}
impl From<UploadSource> for ToFileInput {
fn from(value: UploadSource) -> Self {
Self::UploadSource(value)
}
}
impl From<reqwest::Response> for ToFileInput {
fn from(value: reqwest::Response) -> Self {
Self::Response(value)
}
}
pub async fn to_file(
input: impl Into<ToFileInput>,
filename: Option<impl Into<String>>,
) -> Result<UploadSource> {
let filename = filename.map(Into::into);
match input.into() {
ToFileInput::Path(path) => {
let mut source = UploadSource::from_path(path)?;
if let Some(filename) = filename {
source = source.with_filename(filename);
}
Ok(source)
}
ToFileInput::Bytes(bytes) => {
let filename = filename.ok_or_else(|| {
Error::InvalidConfig("字节输入无法自动推导文件名,请显式提供 filename".into())
})?;
Ok(UploadSource::from_bytes(bytes, filename))
}
ToFileInput::UploadSource(source) => Ok(match filename {
Some(filename) => source.with_filename(filename),
None => source,
}),
ToFileInput::Reader(reader) => {
let filename = filename.ok_or_else(|| {
Error::InvalidConfig("读取器输入无法自动推导文件名,请显式提供 filename".into())
})?;
UploadSource::from_reader(reader, filename)
}
ToFileInput::AsyncReader(mut reader) => {
let filename = filename.ok_or_else(|| {
Error::InvalidConfig("异步读取器输入无法自动推导文件名,请显式提供 filename".into())
})?;
let mut buffer = Vec::new();
reader
.read_to_end(&mut buffer)
.await
.map_err(|error| Error::InvalidConfig(format!("读取异步上传流失败: {error}")))?;
Ok(UploadSource::from_bytes(buffer, filename))
}
ToFileInput::Response(response) => {
let mut source = UploadSource::from_response(response).await?;
if let Some(filename) = filename {
source = source.with_filename(filename);
}
Ok(source)
}
}
}
#[derive(Clone)]
pub enum UploadSource {
Bytes {
data: Bytes,
filename: String,
mime_type: Option<String>,
},
Path {
path: PathBuf,
data: Bytes,
filename: String,
mime_type: Option<String>,
},
Reader {
data: Bytes,
filename: String,
mime_type: Option<String>,
},
}
impl UploadSource {
pub fn from_path<P>(path: P) -> Result<Self>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let data = std::fs::read(path)
.map(Bytes::from)
.map_err(|error| Error::InvalidConfig(format!("读取上传文件失败: {error}")))?;
let filename = path
.file_name()
.and_then(|value| value.to_str())
.ok_or_else(|| Error::InvalidConfig("无法从路径推导文件名".into()))?
.to_owned();
let mime_type = mime_guess::from_path(path).first_raw().map(str::to_owned);
Ok(Self::Path {
path: path.to_path_buf(),
data,
filename,
mime_type,
})
}
pub fn from_bytes<T, U>(bytes: T, filename: U) -> Self
where
T: Into<Bytes>,
U: Into<String>,
{
Self::Bytes {
data: bytes.into(),
filename: filename.into(),
mime_type: None,
}
}
pub fn from_reader<R, U>(mut reader: R, filename: U) -> Result<Self>
where
R: Read,
U: Into<String>,
{
let mut buffer = Vec::new();
reader
.read_to_end(&mut buffer)
.map_err(|error| Error::InvalidConfig(format!("读取上传流失败: {error}")))?;
Ok(Self::Reader {
data: Bytes::from(buffer),
filename: filename.into(),
mime_type: None,
})
}
pub async fn from_response(response: reqwest::Response) -> Result<Self> {
let filename = response
.url()
.path_segments()
.and_then(|mut segments| segments.rfind(|segment| !segment.is_empty()))
.map(str::to_owned)
.unwrap_or_else(|| "upload.bin".into());
let mime_type = response
.headers()
.get(CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.map(str::to_owned);
let data = response
.bytes()
.await
.map_err(|error| Error::InvalidConfig(format!("读取上传响应失败: {error}")))?;
Ok(Self::Bytes {
data,
filename,
mime_type,
})
}
pub fn with_mime_type<T>(mut self, mime_type: T) -> Self
where
T: Into<String>,
{
let mime_type = Some(mime_type.into());
match &mut self {
Self::Bytes {
mime_type: target, ..
}
| Self::Path {
mime_type: target, ..
}
| Self::Reader {
mime_type: target, ..
} => {
*target = mime_type;
}
}
self
}
pub fn with_filename<T>(mut self, filename: T) -> Self
where
T: Into<String>,
{
let filename = filename.into();
match &mut self {
Self::Bytes {
filename: target, ..
}
| Self::Path {
filename: target, ..
}
| Self::Reader {
filename: target, ..
} => {
*target = filename;
}
}
self
}
pub fn filename(&self) -> &str {
match self {
Self::Bytes { filename, .. }
| Self::Path { filename, .. }
| Self::Reader { filename, .. } => filename,
}
}
pub fn mime_type(&self) -> Option<&str> {
match self {
Self::Bytes { mime_type, .. }
| Self::Path { mime_type, .. }
| Self::Reader { mime_type, .. } => mime_type.as_deref(),
}
}
pub fn bytes(&self) -> &Bytes {
match self {
Self::Bytes { data, .. } | Self::Path { data, .. } | Self::Reader { data, .. } => data,
}
}
pub fn to_part(&self) -> Result<Part> {
let mut part = Part::bytes(self.bytes().to_vec()).file_name(self.filename().to_owned());
if let Some(mime_type) = self.mime_type() {
part = part
.mime_str(mime_type)
.map_err(|error| Error::InvalidConfig(format!("非法 MIME 类型: {error}")))?;
}
Ok(part)
}
}
impl fmt::Debug for UploadSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("UploadSource");
debug.field("filename", &self.filename());
debug.field("mime_type", &self.mime_type());
debug.field("size", &self.bytes().len());
if let Self::Path { path, .. } = self {
debug.field("path", path);
}
debug.finish()
}
}