refactor(crates-io): use thiserror

Optionally use `thiserror` to reduce boilerplates but this part can
be dropped if we don't want.
This commit is contained in:
Weihang Lo 2023-06-25 14:27:58 +01:00
parent b4499af4e9
commit 31b500c7c0
No known key found for this signature in database
GPG key ID: D7DBF189825E82E7
5 changed files with 39 additions and 88 deletions

1
Cargo.lock generated
View file

@ -591,6 +591,7 @@ dependencies = [
"percent-encoding",
"serde",
"serde_json",
"thiserror",
"url",
]

View file

@ -87,6 +87,7 @@ syn = { version = "2.0.14", features = ["extra-traits", "full"] }
tar = { version = "0.4.38", default-features = false }
tempfile = "3.1.0"
termcolor = "1.1.2"
thiserror = "1.0.40"
time = { version = "0.3", features = ["parsing", "formatting"] }
toml = "0.7.0"
toml_edit = "0.19.0"

View file

@ -17,4 +17,5 @@ curl.workspace = true
percent-encoding.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
thiserror.workspace = true
url.workspace = true

View file

@ -1,7 +1,6 @@
#![allow(clippy::all)]
use std::collections::BTreeMap;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
use std::io::{Cursor, SeekFrom};
@ -127,22 +126,31 @@ struct Crates {
}
/// Error returned when interacting with a registry.
#[derive(Debug)]
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// Error from libcurl.
Curl(curl::Error),
#[error(transparent)]
Curl(#[from] curl::Error),
/// Error from seriailzing the request payload and deserialzing the
/// response body (like response body didn't match expected structure).
Json(serde_json::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
/// Error from IO. Mostly from reading the tarball to upload.
Io(std::io::Error),
#[error("failed to seek tarball")]
Io(#[from] std::io::Error),
/// Response body was not valid utf8.
Utf8(std::string::FromUtf8Error),
#[error("invalid response body from server")]
Utf8(#[from] std::string::FromUtf8Error),
/// Error from API response containing JSON field `errors.details`.
#[error(
"the remote server responded with an error{}: {}",
status(*code),
errors.join(", "),
)]
Api {
code: u32,
headers: Vec<String>,
@ -150,6 +158,10 @@ pub enum Error {
},
/// Error from API response which didn't have pre-programmed `errors.details`.
#[error(
"failed to get a 200 OK response, got {code}\nheaders:\n\t{}\nbody:\n{body}",
headers.join("\n\t"),
)]
Code {
code: u32,
headers: Vec<String>,
@ -157,93 +169,20 @@ pub enum Error {
},
/// Reason why the token was invalid.
#[error("{0}")]
InvalidToken(&'static str),
/// Server was unavailable and timeouted. Happened when uploading a way
/// too large tarball to crates.io.
#[error(
"Request timed out after 30 seconds. If you're trying to \
upload a crate it may be too large. If the crate is under \
10MB in size, you can email help@crates.io for assistance.\n\
Total size was {0}."
)]
Timeout(u64),
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Curl(e) => Some(e),
Self::Json(e) => Some(e),
Self::Io(e) => Some(e),
Self::Utf8(e) => Some(e),
Self::Api { .. } => None,
Self::Code { .. } => None,
Self::InvalidToken(..) => None,
Self::Timeout(..) => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::Curl(e) => write!(f, "{e}"),
Self::Json(e) => write!(f, "{e}"),
Self::Io(e) => write!(f, "{e}"),
Self::Utf8(e) => write!(f, "{e}"),
Self::Api { code, errors, .. } => {
f.write_str("the remote server responded with an error")?;
if *code != 200 {
write!(f, " (status {} {})", code, reason(*code))?;
};
write!(f, ": {}", errors.join(", "))
}
Self::Code {
code,
headers,
body,
} => write!(
f,
"failed to get a 200 OK response, got {}\n\
headers:\n\
\t{}\n\
body:\n\
{}",
code,
headers.join("\n\t"),
body
),
Self::InvalidToken(e) => write!(f, "{e}"),
Self::Timeout(tarball_size) => write!(
f,
"Request timed out after 30 seconds. If you're trying to \
upload a crate it may be too large. If the crate is under \
10MB in size, you can email help@crates.io for assistance.\n\
Total size was {tarball_size}."
),
}
}
}
impl From<curl::Error> for Error {
fn from(error: curl::Error) -> Self {
Self::Curl(error)
}
}
impl From<serde_json::Error> for Error {
fn from(error: serde_json::Error) -> Self {
Self::Json(error)
}
}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Self::Io(error)
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(error: std::string::FromUtf8Error) -> Self {
Self::Utf8(error)
}
}
impl Registry {
/// Creates a new `Registry`.
///
@ -500,6 +439,15 @@ impl Registry {
}
}
fn status(code: u32) -> String {
if code == 200 {
String::new()
} else {
let reason = reason(code);
format!(" (status {code} {reason})")
}
}
fn reason(code: u32) -> &'static str {
// Taken from https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
match code {

View file

@ -2023,10 +2023,10 @@ fn api_other_error() {
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
Caused by:
invalid response from server
invalid response body from server
Caused by:
response body was not valid utf-8
invalid utf-8 sequence of [..]
",
)
.run();