Skip to content

Commit

Permalink
refactor(crates-io): use thiserror
Browse files Browse the repository at this point in the history
Optionally use `thiserror` to reduce boilerplates but this part can
be dropped if we don't want.
  • Loading branch information
weihanglo committed Jun 28, 2023
1 parent d99f4c1 commit 53521cd
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 88 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,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"
Expand Down
1 change: 1 addition & 0 deletions crates/crates-io/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ log.workspace = true
percent-encoding.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
thiserror.workspace = true
url.workspace = true
120 changes: 34 additions & 86 deletions crates/crates-io/lib.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -140,123 +139,63 @@ 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,
errors: Vec<String>,
challenge: Option<Challenge>,
},

/// 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>,
body: String,
},

/// 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`.
///
Expand Down Expand Up @@ -610,6 +549,15 @@ impl Challenge {
}
}

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 {
Expand Down
4 changes: 2 additions & 2 deletions tests/testsuite/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 53521cd

Please sign in to comment.