Repository: tokio-rs/axum
File: axum-extra/src/response/file_stream.rs (Rust)

Code before:
    /// use tokio_util::io::ReaderStream;
    /// use tokio::io::AsyncSeekExt;
    ///
    /// async fn range_stream() -> impl IntoResponse {
    ///     let range_start = 0;
    ///     let range_end = 1024;
    ///
    ///     FileStream::<ReaderStream<File>>::try_range_response("CHANGELOG.md", range_start, range_end).await
    ///         .map_err(|e| (StatusCode::NOT_FOUND, format!("File not found: {e}")))
    /// }
    ///
    /// let app = Router::new().route("/file-stream", get(range_stream));
    /// # let _: Router = app;
    /// ```
    pub async fn try_range_response(
        file_path: impl AsRef<Path>,
        start: u64,
        mut end: u64,
    ) -> io::Result<Response> {
        let mut file = File::open(file_path).await?;

        let metadata = file.metadata().await?;
        let total_size = metadata.len();

        if end == 0 {
            end = total_size - 1;
        }

        if start > total_size {
            return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response());
        }
        if start > end {
            return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response());
        }
        if end >= total_size {
            return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response());
        }

        file.seek(std::io::SeekFrom::Start(start)).await?;

        let stream = ReaderStream::new(file.take(end - start + 1));

        Ok(FileStream::new(stream).into_range_response(start, end, total_size))
    }
}

// Split because the general impl requires to specify `S` and this one does not.
impl FileStream<ReaderStream<File>> {
    /// Create a [`FileStream`] from a file path.
    ///
    /// # Examples

Review comment (refactor):
Question:
I noticed that the error body `"Range Not Satisfiable"` is hard-coded directly into the response. Would it make sense to extract this into a shared constant so tests can assert on the *exact* same value?

Having the literal inline makes tests fragile, since they need to duplicate the exact string. If the message ever changes, that could also unintentionally break consumers relying on the current wording or cause test drift.

In particular, there are two branches in this PR that both return `StatusCode::RANGE_NOT_SATISFIABLE`. It might be useful if the [test](https://github.com/tokio-rs/axum/pull/3566/files#diff-572de7d1dc9a91415d797b7a1fdb59e7ce3113da6f2f123739dff2f25becf601R648) also asserted on the error body, to ensure the `StatusCode::RANGE_NOT_SATISFIABLE` comes from the branch in [`file_stream.rs`](https://github.com/tokio-rs/axum/pull/3566/files#diff-572de7d1dc9a91415d797b7a1fdb59e7ce3113da6f2f123739dff2f25becf601R195) with the expected response body, and not the [test branch](https://github.com/tokio-rs/axum/pull/3566/files#diff-572de7d1dc9a91415d797b7a1fdb59e7ce3113da6f2f123739dff2f25becf601R623).

Code after:
    /// use tokio_util::io::ReaderStream;
    /// use tokio::io::AsyncSeekExt;
    ///
    /// async fn range_stream() -> impl IntoResponse {
    ///     let range_start = 0;
    ///     let range_end = 1024;
    ///
    ///     FileStream::<ReaderStream<File>>::try_range_response("CHANGELOG.md", range_start, range_end).await
    ///         .map_err(|e| (StatusCode::NOT_FOUND, format!("File not found: {e}")))
    /// }
    ///
    /// let app = Router::new().route("/file-stream", get(range_stream));
    /// # let _: Router = app;
    /// ```
    pub async fn try_range_response(
        file_path: impl AsRef<Path>,
        start: u64,
        mut end: u64,
    ) -> io::Result<Response> {
        let mut file = File::open(file_path).await?;

        let metadata = file.metadata().await?;
        let total_size = metadata.len();

        if total_size == 0 {
            return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response());
        }

        if end == 0 {
            end = total_size - 1;
        }

        if start > total_size {
            return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response());
        }
        if start > end {
            return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response());
        }
        if end >= total_size {
            return Ok((StatusCode::RANGE_NOT_SATISFIABLE, "Range Not Satisfiable").into_response());
        }

        file.seek(std::io::SeekFrom::Start(start)).await?;

        let stream = ReaderStream::new(file.take(end - start + 1));

        Ok(FileStream::new(stream).into_range_response(start, end, total_size))
    }
}

// Split because the general impl requires to specify `S` and this one does not.