From a77c2cf47834cde2875d2131daffab18ee8b215d Mon Sep 17 00:00:00 2001 From: Poliorcetics Date: Sun, 17 Aug 2025 07:14:07 +0200 Subject: [PATCH] fix(axum-extra): don't require `S` generic param when using `FileStream::from_path()` (#3437) --- axum-extra/src/response/file_stream.rs | 95 +++++++++++++------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/axum-extra/src/response/file_stream.rs b/axum-extra/src/response/file_stream.rs index ee26148d..da9d0d78 100644 --- a/axum-extra/src/response/file_stream.rs +++ b/axum-extra/src/response/file_stream.rs @@ -70,52 +70,6 @@ where } } - /// Create a [`FileStream`] from a file path. - /// - /// # Examples - /// - /// ``` - /// use axum::{ - /// http::StatusCode, - /// response::IntoResponse, - /// Router, - /// routing::get - /// }; - /// use axum_extra::response::file_stream::FileStream; - /// use tokio::fs::File; - /// use tokio_util::io::ReaderStream; - /// - /// async fn file_stream() -> impl IntoResponse { - /// FileStream::>::from_path("test.txt") - /// .await - /// .map_err(|e| (StatusCode::NOT_FOUND, format!("File not found: {e}"))) - /// } - /// - /// let app = Router::new().route("/file-stream", get(file_stream)); - /// # let _: Router = app; - /// ``` - pub async fn from_path(path: impl AsRef) -> io::Result>> { - let file = File::open(&path).await?; - let mut content_size = None; - let mut file_name = None; - - if let Ok(metadata) = file.metadata().await { - content_size = Some(metadata.len()); - } - - if let Some(file_name_os) = path.as_ref().file_name() { - if let Some(file_name_str) = file_name_os.to_str() { - file_name = Some(file_name_str.to_owned()); - } - } - - Ok(FileStream { - stream: ReaderStream::new(file), - file_name, - content_size, - }) - } - /// Set the file name of the [`FileStream`]. /// /// This adds the attachment `Content-Disposition` header with the given `file_name`. @@ -259,6 +213,53 @@ where } } +// Split because the general impl requires to specify `S` and this one does not. +impl FileStream> { + /// Create a [`FileStream`] from a file path. + /// + /// # Examples + /// + /// ``` + /// use axum::{ + /// http::StatusCode, + /// response::IntoResponse, + /// Router, + /// routing::get + /// }; + /// use axum_extra::response::file_stream::FileStream; + /// + /// async fn file_stream() -> impl IntoResponse { + /// FileStream::from_path("test.txt") + /// .await + /// .map_err(|e| (StatusCode::NOT_FOUND, format!("File not found: {e}"))) + /// } + /// + /// let app = Router::new().route("/file-stream", get(file_stream)); + /// # let _: Router = app; + /// ``` + pub async fn from_path(path: impl AsRef) -> io::Result { + let file = File::open(&path).await?; + let mut content_size = None; + let mut file_name = None; + + if let Ok(metadata) = file.metadata().await { + content_size = Some(metadata.len()); + } + + if let Some(file_name_os) = path.as_ref().file_name() { + if let Some(file_name_str) = file_name_os.to_str() { + file_name = Some(file_name_str.to_owned()); + } + } + + Ok(Self { + stream: ReaderStream::new(file), + file_name, + content_size, + }) + } +} + impl IntoResponse for FileStream where S: TryStream + Send + 'static, @@ -474,7 +475,7 @@ mod tests { let app = Router::new().route( "/from_path", get(move || async move { - FileStream::>::from_path(Path::new("CHANGELOG.md")) + FileStream::from_path(Path::new("CHANGELOG.md")) .await .unwrap() .into_response()