mirror of
https://github.com/astral-sh/uv.git
synced 2025-11-23 04:55:28 +00:00
Better offline error message (#2110)
Error for `uv pip compile scripts/requirements/jupyter.in` without internet: **Before** ``` error: error sending request for url (https://pypi.org/simple/jupyter/): error trying to connect: dns error: failed to lookup address information: No such host is known. (os error 11001) Caused by: error trying to connect: dns error: failed to lookup address information: No such host is known. (os error 11001) Caused by: dns error: failed to lookup address information: No such host is known. (os error 11001) Caused by: failed to lookup address information: No such host is known. (os error 11001) ``` **After** ``` error: Could not connect, are you offline? Caused by: error sending request for url (https://pypi.org/simple/django/): error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution Caused by: error trying to connect: dns error: failed to lookup address information: Temporary failure in name resolution Caused by: dns error: failed to lookup address information: Temporary failure in name resolution Caused by: failed to lookup address information: Temporary failure in name resolution ``` On linux, it would be "Temporary failure in name resolution" instead of "No such host is known. (os error 11001)". The implementation checks for "dne error" stringly as hyper errors are opaque. The danger is that this breaks with a hyper update. We still get the complete error trace since reqwest eagerly inlines errors (https://github.com/seanmonstar/reqwest/issues/2147). No test since i wouldn't know how to simulate this in cargo test. Fixes #1971
This commit is contained in:
parent
bc0345a1fd
commit
898c3f6bcf
7 changed files with 106 additions and 36 deletions
|
|
@ -1,3 +1,6 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::Deref;
|
||||
|
||||
use async_http_range_reader::AsyncHttpRangeReaderError;
|
||||
use async_zip::error::ZipError;
|
||||
use url::Url;
|
||||
|
|
@ -74,17 +77,17 @@ pub enum ErrorKind {
|
|||
|
||||
/// The metadata file was not found in the registry.
|
||||
#[error("File `{0}` was not found in the registry at {1}.")]
|
||||
FileNotFound(String, #[source] reqwest::Error),
|
||||
FileNotFound(String, #[source] BetterReqwestError),
|
||||
|
||||
/// A generic request error happened while making a request. Refer to the
|
||||
/// error message for more details.
|
||||
#[error(transparent)]
|
||||
RequestError(#[from] reqwest::Error),
|
||||
ReqwestError(#[from] BetterReqwestError),
|
||||
|
||||
/// A generic request middleware error happened while making a request.
|
||||
/// Refer to the error message for more details.
|
||||
#[error(transparent)]
|
||||
RequestMiddlewareError(#[from] reqwest_middleware::Error),
|
||||
ReqwestMiddlewareError(#[from] anyhow::Error),
|
||||
|
||||
#[error("Received some unexpected JSON from {url}")]
|
||||
BadJson { source: serde_json::Error, url: Url },
|
||||
|
|
@ -155,20 +158,6 @@ impl ErrorKind {
|
|||
matches!(err.kind(), std::io::ErrorKind::NotFound)
|
||||
}
|
||||
|
||||
pub(crate) fn from_middleware(err: reqwest_middleware::Error) -> Self {
|
||||
if let reqwest_middleware::Error::Middleware(ref underlying) = err {
|
||||
if let Some(err) = underlying.downcast_ref::<OfflineError>() {
|
||||
return Self::Offline(err.url().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
if let reqwest_middleware::Error::Reqwest(err) = err {
|
||||
return Self::RequestError(err);
|
||||
}
|
||||
|
||||
Self::RequestMiddlewareError(err)
|
||||
}
|
||||
|
||||
/// Returns `true` if the error is due to the server not supporting HTTP range requests.
|
||||
pub(crate) fn is_http_range_requests_unsupported(&self) -> bool {
|
||||
match self {
|
||||
|
|
@ -179,7 +168,7 @@ impl ErrorKind {
|
|||
|
||||
// The server returned a "Method Not Allowed" error, indicating it doesn't support
|
||||
// HEAD requests, so we can't check for range requests.
|
||||
Self::RequestError(err) => {
|
||||
Self::ReqwestError(err) => {
|
||||
if let Some(status) = err.status() {
|
||||
if status == reqwest::StatusCode::METHOD_NOT_ALLOWED {
|
||||
return true;
|
||||
|
|
@ -208,3 +197,76 @@ impl ErrorKind {
|
|||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for ErrorKind {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
Self::ReqwestError(BetterReqwestError::from(error))
|
||||
}
|
||||
}
|
||||
impl From<reqwest_middleware::Error> for ErrorKind {
|
||||
fn from(error: reqwest_middleware::Error) -> Self {
|
||||
if let reqwest_middleware::Error::Middleware(ref underlying) = error {
|
||||
if let Some(err) = underlying.downcast_ref::<OfflineError>() {
|
||||
return Self::Offline(err.url().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
match error {
|
||||
reqwest_middleware::Error::Middleware(err) => Self::ReqwestMiddlewareError(err),
|
||||
reqwest_middleware::Error::Reqwest(err) => Self::from(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle the case with no internet by explicitly telling the user instead of showing an obscure
|
||||
/// DNS error.
|
||||
#[derive(Debug)]
|
||||
pub struct BetterReqwestError(reqwest::Error);
|
||||
|
||||
impl BetterReqwestError {
|
||||
fn is_likely_offline(&self) -> bool {
|
||||
if !self.0.is_connect() {
|
||||
return false;
|
||||
}
|
||||
// Self is "error sending request for url", the first source is "error trying to connect",
|
||||
// the second source is "dns error". We have to check for the string because hyper errors
|
||||
// are opaque.
|
||||
std::error::Error::source(&self.0)
|
||||
.and_then(|err| err.source())
|
||||
.is_some_and(|err| err.to_string().starts_with("dns error: "))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for BetterReqwestError {
|
||||
fn from(error: reqwest::Error) -> Self {
|
||||
Self(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BetterReqwestError {
|
||||
type Target = reqwest::Error;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for BetterReqwestError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
if self.is_likely_offline() {
|
||||
f.write_str("Could not connect, are you offline?")
|
||||
} else {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for BetterReqwestError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
if self.is_likely_offline() {
|
||||
Some(&self.0)
|
||||
} else {
|
||||
self.0.source()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue