Skip to content

Commit 45897e3

Browse files
committed
refactor(crates-io): remove anyhow
This removes the dependency `anyhow` and uses our own custom Error enum, so that crates-io consumer can access `Error::API::challenge` field.
1 parent 9409586 commit 45897e3

File tree

3 files changed

+61
-49
lines changed

3 files changed

+61
-49
lines changed

Cargo.lock

-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/crates-io/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ name = "crates_io"
1313
path = "lib.rs"
1414

1515
[dependencies]
16-
anyhow.workspace = true
1716
curl.workspace = true
1817
http-auth.workspace = true
1918
log.workspace = true

crates/crates-io/lib.rs

+61-47
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::io::prelude::*;
77
use std::io::{Cursor, SeekFrom};
88
use std::time::Instant;
99

10-
use anyhow::{format_err, Context};
1110
use curl::easy::{Easy, List};
1211
use http_auth::ChallengeParser;
1312
use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
@@ -140,37 +139,66 @@ struct Crates {
140139
meta: TotalCrates,
141140
}
142141

142+
/// Error returned when interacting with a registry.
143143
#[derive(Debug)]
144144
pub enum Error {
145+
/// Error from libcurl.
145146
Curl(curl::Error),
147+
148+
/// Error from seriailzing the request payload and deserialzing the
149+
/// response body (like response body didn't match expected structure).
150+
Json(serde_json::Error),
151+
152+
/// Error from IO. Mostly from reading the tarball to upload.
153+
Io(std::io::Error),
154+
155+
/// Response body was not valid utf8.
156+
Utf8(std::string::FromUtf8Error),
157+
158+
/// Error from API response containing JSON field `errors.details`.
146159
Api {
147160
code: u32,
148161
errors: Vec<String>,
149162
challenge: Option<Challenge>,
150163
},
164+
165+
/// Error from API response which didn't have pre-programmed `errors.details`.
151166
Code {
152167
code: u32,
153168
headers: Vec<String>,
154169
body: String,
155170
},
156-
Other(anyhow::Error),
171+
172+
/// Reason why the token was invalid.
173+
InvalidToken(&'static str),
174+
175+
/// Server was unavailable and timeouted. Happened when uploading a way
176+
/// too large tarball to crates.io.
177+
Timeout(u64),
157178
}
158179

159180
impl std::error::Error for Error {
160181
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
161182
match self {
162-
Self::Curl(..) => None,
183+
Self::Curl(e) => Some(e),
184+
Self::Json(e) => Some(e),
185+
Self::Io(e) => Some(e),
186+
Self::Utf8(e) => Some(e),
163187
Self::Api { .. } => None,
164188
Self::Code { .. } => None,
165-
Self::Other(e) => Some(e.as_ref()),
189+
Self::InvalidToken(..) => None,
190+
Self::Timeout(..) => None,
166191
}
167192
}
168193
}
169194

170195
impl fmt::Display for Error {
171196
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172197
match self {
173-
Self::Curl(e) => write!(f, "{}", e),
198+
Self::Curl(e) => write!(f, "{e}"),
199+
Self::Json(e) => write!(f, "{e}"),
200+
Self::Io(e) => write!(f, "{e}"),
201+
Self::Utf8(e) => write!(f, "{e}"),
174202
Self::Api { code, errors, .. } => {
175203
f.write_str("the remote server responded with an error")?;
176204
if *code != 200 {
@@ -193,7 +221,14 @@ impl fmt::Display for Error {
193221
headers.join("\n\t"),
194222
body
195223
),
196-
Self::Other(..) => write!(f, "invalid response from server"),
224+
Self::InvalidToken(e) => write!(f, "{e}"),
225+
Self::Timeout(tarball_size) => write!(
226+
f,
227+
"Request timed out after 30 seconds. If you're trying to \
228+
upload a crate it may be too large. If the crate is under \
229+
10MB in size, you can email [email protected] for assistance.\n\
230+
Total size was {tarball_size}."
231+
),
197232
}
198233
}
199234
}
@@ -204,21 +239,21 @@ impl From<curl::Error> for Error {
204239
}
205240
}
206241

207-
impl From<anyhow::Error> for Error {
208-
fn from(error: anyhow::Error) -> Self {
209-
Self::Other(error)
242+
impl From<serde_json::Error> for Error {
243+
fn from(error: serde_json::Error) -> Self {
244+
Self::Json(error)
210245
}
211246
}
212247

213-
impl From<serde_json::Error> for Error {
214-
fn from(error: serde_json::Error) -> Self {
215-
Self::Other(error.into())
248+
impl From<std::io::Error> for Error {
249+
fn from(error: std::io::Error) -> Self {
250+
Self::Io(error)
216251
}
217252
}
218253

219-
impl From<url::ParseError> for Error {
220-
fn from(error: url::ParseError) -> Self {
221-
Self::Other(error.into())
254+
impl From<std::string::FromUtf8Error> for Error {
255+
fn from(error: std::string::FromUtf8Error) -> Self {
256+
Self::Utf8(error)
222257
}
223258
}
224259

@@ -255,12 +290,9 @@ impl Registry {
255290
}
256291

257292
fn token(&self) -> Result<&str, Error> {
258-
let token = match self.token.as_ref() {
259-
Some(s) => s,
260-
None => {
261-
return Err(format_err!("no upload token found, please run `cargo login`").into())
262-
}
263-
};
293+
let token = self.token.as_ref().ok_or_else(|| {
294+
Error::InvalidToken("no upload token found, please run `cargo login`")
295+
})?;
264296
check_token(token)?;
265297
Ok(token)
266298
}
@@ -306,12 +338,8 @@ impl Registry {
306338
// This checks the length using seeking instead of metadata, because
307339
// on some filesystems, getting the metadata will fail because
308340
// the file was renamed in ops::package.
309-
let tarball_len = tarball
310-
.seek(SeekFrom::End(0))
311-
.with_context(|| "failed to seek tarball")?;
312-
tarball
313-
.seek(SeekFrom::Start(0))
314-
.with_context(|| "failed to seek tarball")?;
341+
let tarball_len = tarball.seek(SeekFrom::End(0))?;
342+
tarball.seek(SeekFrom::Start(0))?;
315343
let header = {
316344
let mut w = Vec::new();
317345
w.extend(&(json.len() as u32).to_le_bytes());
@@ -341,13 +369,7 @@ impl Registry {
341369
&& started.elapsed().as_secs() >= 29
342370
&& self.host_is_crates_io() =>
343371
{
344-
format_err!(
345-
"Request timed out after 30 seconds. If you're trying to \
346-
upload a crate it may be too large. If the crate is under \
347-
10MB in size, you can email [email protected] for assistance.\n\
348-
Total size was {}.",
349-
tarball_len
350-
)
372+
Error::Timeout(tarball_len)
351373
}
352374
_ => e.into(),
353375
})?;
@@ -470,14 +492,7 @@ impl Registry {
470492
handle.perform()?;
471493
}
472494

473-
let body = match String::from_utf8(body) {
474-
Ok(body) => body,
475-
Err(..) => {
476-
return Err(Error::Other(format_err!(
477-
"response body was not valid utf-8"
478-
)))
479-
}
480-
};
495+
let body = String::from_utf8(body)?;
481496
let errors = serde_json::from_str::<ApiErrorList>(&body)
482497
.ok()
483498
.map(|s| s.errors.into_iter().map(|s| s.detail).collect::<Vec<_>>());
@@ -623,7 +638,7 @@ pub fn is_url_crates_io(url: &str) -> bool {
623638
/// registries only create tokens in that format so that is as less restricted as possible.
624639
pub fn check_token(token: &str) -> Result<(), Error> {
625640
if token.is_empty() {
626-
return Err(format_err!("please provide a non-empty token").into());
641+
return Err(Error::InvalidToken("please provide a non-empty token"));
627642
}
628643
if token.bytes().all(|b| {
629644
// This is essentially the US-ASCII limitation of
@@ -634,11 +649,10 @@ pub fn check_token(token: &str) -> Result<(), Error> {
634649
}) {
635650
Ok(())
636651
} else {
637-
Err(format_err!(
652+
Err(Error::InvalidToken(
638653
"token contains invalid characters.\nOnly printable ISO-8859-1 characters \
639-
are allowed as it is sent in a HTTPS header."
640-
)
641-
.into())
654+
are allowed as it is sent in a HTTPS header.",
655+
))
642656
}
643657
}
644658

0 commit comments

Comments
 (0)