From 478ac079ee5b4006df30fdcb19128f0d702f1b34 Mon Sep 17 00:00:00 2001
From: Martin Algesten <martin@algesten.se>
Date: Mon, 3 Feb 2025 22:08:46 +0100
Subject: [PATCH] Pass through ureq::Error wrapped as io::Error

---
 CHANGELOG.md       |  2 ++
 src/body/brotli.rs | 12 +++++++++---
 src/body/gzip.rs   | 12 +++++++++---
 src/error.rs       |  8 +++++---
 4 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c4303413..2da8b3e6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 # Unreleased
 
+  * ureq::Error wrapped as io::Error should pass through body chain (#984)
+
 # 3.0.4
 
   * Manually unroll some macros to regular code (#978)
diff --git a/src/body/brotli.rs b/src/body/brotli.rs
index 8379619f..ea81be3e 100644
--- a/src/body/brotli.rs
+++ b/src/body/brotli.rs
@@ -2,6 +2,7 @@ use std::io;
 
 use brotli_decompressor::Decompressor;
 
+use crate::error::is_wrapped_ureq_error;
 use crate::Error;
 
 pub(crate) struct BrotliDecoder<R: io::Read>(Decompressor<R>);
@@ -14,8 +15,13 @@ impl<R: io::Read> BrotliDecoder<R> {
 
 impl<R: io::Read> io::Read for BrotliDecoder<R> {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        self.0
-            .read(buf)
-            .map_err(|e| Error::Decompress("brotli", e).into_io())
+        self.0.read(buf).map_err(|e| {
+            if is_wrapped_ureq_error(&e) {
+                // If this already is a ureq::Error, like Timeout, pass it along.
+                e
+            } else {
+                Error::Decompress("brotli", e).into_io()
+            }
+        })
     }
 }
diff --git a/src/body/gzip.rs b/src/body/gzip.rs
index 0cf0511b..96397271 100644
--- a/src/body/gzip.rs
+++ b/src/body/gzip.rs
@@ -2,6 +2,7 @@ use std::io;
 
 use flate2::read::MultiGzDecoder;
 
+use crate::error::is_wrapped_ureq_error;
 use crate::Error;
 
 pub(crate) struct GzipDecoder<R>(MultiGzDecoder<R>);
@@ -14,9 +15,14 @@ impl<R: io::Read> GzipDecoder<R> {
 
 impl<R: io::Read> io::Read for GzipDecoder<R> {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        self.0
-            .read(buf)
-            .map_err(|e| Error::Decompress("gzip", e).into_io())
+        self.0.read(buf).map_err(|e| {
+            if is_wrapped_ureq_error(&e) {
+                // If this already is a ureq::Error, like Timeout, pass it along.
+                e
+            } else {
+                Error::Decompress("gzip", e).into_io()
+            }
+        })
     }
 }
 
diff --git a/src/error.rs b/src/error.rs
index 9fe616d5..337fa6fe 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -169,11 +169,13 @@ impl Error {
     }
 }
 
+pub(crate) fn is_wrapped_ureq_error(e: &io::Error) -> bool {
+    e.get_ref().map(|x| x.is::<Error>()).unwrap_or(false)
+}
+
 impl From<io::Error> for Error {
     fn from(e: io::Error) -> Self {
-        let is_wrapped_ureq_error = e.get_ref().map(|x| x.is::<Error>()).unwrap_or(false);
-
-        if is_wrapped_ureq_error {
+        if is_wrapped_ureq_error(&e) {
             // unwraps are ok, see above.
             let boxed = e.into_inner().unwrap();
             let ureq = boxed.downcast::<Error>().unwrap();