Repository: astral-sh/uv
File: crates/uv-client/src/base_client.rs (Rust)

Code before:
    /// Build a `Request`.
    pub fn build(self) -> reqwest::Result<Request> {
        self.builder.build()
    }

    /// Constructs the Request and sends it to the target URL, returning a
    /// future Response.
    pub async fn send(self) -> reqwest_middleware::Result<Response> {
        self.client.execute(self.build()?).await
    }

    pub fn raw_builder(&self) -> &reqwest_middleware::RequestBuilder {
        &self.builder
    }
}

/// An extension to [`DefaultRetryableStrategy`], that log transient request failures and
/// adds additional retry cases.
pub struct UvRetryableStrategy;

impl RetryableStrategy for UvRetryableStrategy {
    fn handle(&self, res: &Result<Response, reqwest_middleware::Error>) -> Option<Retryable> {
        let retryable = match res {
            Ok(success) => default_on_request_success(success),
            // The uv retryable strategy contains `default_on_request_failure`
            Err(err) => uv_retryable_strategy(err),
        };

        // Log on transient errors
        if retryable == Some(Retryable::Transient) {
            match res {
                Ok(response) => {
                    debug!("Transient request failure for: {}", response.url());
                }
                Err(err) => {
                    let context = iter::successors(err.source(), |&err| err.source())
                        .map(|err| format!("  Caused by: {err}"))
                        .join("\n");
                    debug!(
                        "Transient request failure for {}, retrying: {err}\n{context}",
                        err.url().map(Url::as_str).unwrap_or("unknown URL")
                    );
                }
            }
        }
        retryable
    }
}

/// Whether the error looks like a network error that should be retried.
///

Review comment (bug):
Can we rename this to `retryable_on_request_failure` for consistency with the `reqwest_retry` naming?

Code after:
    /// Build a `Request`.
    pub fn build(self) -> reqwest::Result<Request> {
        self.builder.build()
    }

    /// Constructs the Request and sends it to the target URL, returning a
    /// future Response.
    pub async fn send(self) -> reqwest_middleware::Result<Response> {
        self.client.execute(self.build()?).await
    }

    pub fn raw_builder(&self) -> &reqwest_middleware::RequestBuilder {
        &self.builder
    }
}

/// An extension over [`DefaultRetryableStrategy`] that logs transient request failures and
/// adds additional retry cases.
pub struct UvRetryableStrategy;

impl RetryableStrategy for UvRetryableStrategy {
    fn handle(&self, res: &Result<Response, reqwest_middleware::Error>) -> Option<Retryable> {
        let retryable = match res {
            Ok(success) => default_on_request_success(success),
            Err(err) => retryable_on_request_failure(err),
        };

        // Log on transient errors
        if retryable == Some(Retryable::Transient) {
            match res {
                Ok(response) => {
                    debug!("Transient request failure for: {}", response.url());
                }
                Err(err) => {
                    let context = iter::successors(err.source(), |&err| err.source())
                        .map(|err| format!("  Caused by: {err}"))
                        .join("\n");
                    debug!(
                        "Transient request failure for {}, retrying: {err}\n{context}",
                        err.url().map(Url::as_str).unwrap_or("unknown URL")
                    );
                }
            }
        }
        retryable
    }
}

/// Whether the error looks like a network error that should be retried.
///
/// This is an extension over [`reqwest_middleware::default_on_request_failure`], which is missing