From 31b500c7c07d48c7964cb97a412cbfd1b7e6b596 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Sun, 25 Jun 2023 14:27:58 +0100 Subject: [PATCH] refactor(crates-io): use `thiserror` Optionally use `thiserror` to reduce boilerplates but this part can be dropped if we don't want. --- Cargo.lock | 1 + Cargo.toml | 1 + crates/crates-io/Cargo.toml | 1 + crates/crates-io/lib.rs | 120 ++++++++++-------------------------- tests/testsuite/publish.rs | 4 +- 5 files changed, 39 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bca1f82fe..8c0eeafb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,6 +591,7 @@ dependencies = [ "percent-encoding", "serde", "serde_json", + "thiserror", "url", ] diff --git a/Cargo.toml b/Cargo.toml index b924178a1..8cdb6404f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/crates-io/Cargo.toml b/crates/crates-io/Cargo.toml index e1ae836b7..139b8aa97 100644 --- a/crates/crates-io/Cargo.toml +++ b/crates/crates-io/Cargo.toml @@ -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 diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 9f363997b..6ce39cefd 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -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, @@ -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, @@ -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 for Error { - fn from(error: curl::Error) -> Self { - Self::Curl(error) - } -} - -impl From for Error { - fn from(error: serde_json::Error) -> Self { - Self::Json(error) - } -} - -impl From for Error { - fn from(error: std::io::Error) -> Self { - Self::Io(error) - } -} - -impl From 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 { diff --git a/tests/testsuite/publish.rs b/tests/testsuite/publish.rs index 45b7c7da5..eb749ee20 100644 --- a/tests/testsuite/publish.rs +++ b/tests/testsuite/publish.rs @@ -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();