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

Code before:
    /// Create a new error from an HTML parsing error.
    pub(crate) fn from_html_err(err: html::Error, url: DisplaySafeUrl) -> Self {
        ErrorKind::BadHtml { source: err, url }.into()
    }

    /// Returns `true` if this error corresponds to an offline error.
    pub(crate) fn is_offline(&self) -> bool {
        matches!(self.kind.base(), ErrorKind::Offline(_))
    }

    /// Returns `true` if this error corresponds to an I/O "not found" error.
    pub(crate) fn is_file_not_exists(&self) -> bool {
        let ErrorKind::Io(err) = self.kind.base() else {
            return false;
        };
        matches!(err.kind(), std::io::ErrorKind::NotFound)
    }

    /// Returns `true` if the error is due to an SSL error.
    pub fn is_ssl(&self) -> bool {
        matches!(self.kind.base(), ErrorKind::WrappedReqwestError(.., err) if err.is_ssl())
    }

    /// Returns `true` if the error is due to the server not supporting HTTP range requests.
    pub fn is_http_range_requests_unsupported(&self) -> bool {
        match self.kind.base() {
            // The server doesn't support range requests (as reported by the `HEAD` check).
            ErrorKind::AsyncHttpRangeReader(
                _,
                AsyncHttpRangeReaderError::HttpRangeRequestUnsupported,
            ) => {
                return true;
            }

            // The server doesn't support range requests (it doesn't return the necessary headers).
            ErrorKind::AsyncHttpRangeReader(
                _,
                AsyncHttpRangeReaderError::ContentLengthMissing
                | AsyncHttpRangeReaderError::ContentRangeMissing,
            ) => {
                return true;
            }

            // The server returned a "Method Not Allowed" error, indicating it doesn't support
            // HEAD requests, so we can't check for range requests.
            ErrorKind::WrappedReqwestError(_url, err) => {
                if let Some(status) = err.status() {
                    // If the server doesn't support HEAD requests, we can't check for range
                    // requests.
                    if status == reqwest::StatusCode::METHOD_NOT_ALLOWED {
                        return true;

Review comment (suggestion):
There's some risk here. If you do, like, `Error::from(err.into_kind())`, you end up losing the retry wrapper. We do that in at least one place (`get_or_build_wheel_metadata`), which I guess I need to fix.

Code after:
        if self.retries > 0 {
            Some(&self.kind)
        } else {
            self.kind.source()
        }
    }
}

impl Error {
    /// Create a new [`Error`] with the given [`ErrorKind`] and number of retries.
    pub fn new(kind: ErrorKind, retries: u32) -> Self {
        Self {
            kind: Box::new(kind),
            retries,
        }
    }

    /// Return the number of retries that were attempted before this error was returned.
    pub fn retries(&self) -> u32 {
        self.retries
    }

    /// Convert this error into an [`ErrorKind`].
    pub fn into_kind(self) -> ErrorKind {
        *self.kind
    }

    /// Return the [`ErrorKind`] of this error.
    pub fn kind(&self) -> &ErrorKind {
        &self.kind
    }

    /// Create a new error from a JSON parsing error.
    pub(crate) fn from_json_err(err: serde_json::Error, url: DisplaySafeUrl) -> Self {
        ErrorKind::BadJson { source: err, url }.into()
    }

    /// Create a new error from an HTML parsing error.
    pub(crate) fn from_html_err(err: html::Error, url: DisplaySafeUrl) -> Self {
        ErrorKind::BadHtml { source: err, url }.into()
    }

    /// Returns `true` if this error corresponds to an offline error.
    pub(crate) fn is_offline(&self) -> bool {
        matches!(&*self.kind, ErrorKind::Offline(_))
    }

    /// Returns `true` if this error corresponds to an I/O "not found" error.
    pub(crate) fn is_file_not_exists(&self) -> bool {
        let ErrorKind::Io(err) = &*self.kind else {
            return false;