From 680594471527e4fa0e0bff5fb76348039584d3e6 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Thu, 6 Jul 2023 11:55:02 +0100 Subject: [PATCH 1/6] feat(crates-io): expose headers for `ResponseError::Api` In response to RFC 3231 [^1], our registry client need to return headers to caller, so that the caller (cargo binary) can continue parsing challenge headers. [^1]: https://rust-lang.github.io/rfcs/3231-cargo-asymmetric-tokens.html#the-authentication-process --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/crates-io/Cargo.toml | 2 +- crates/crates-io/lib.rs | 5 +++-- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee6bcaee389..07f6320a968 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -585,7 +585,7 @@ dependencies = [ [[package]] name = "crates-io" -version = "0.37.0" +version = "0.38.0" dependencies = [ "anyhow", "curl", diff --git a/Cargo.toml b/Cargo.toml index 63e06adb53a..b924178a194 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ cargo-util = { version = "0.2.5", path = "crates/cargo-util" } cargo_metadata = "0.14.0" clap = "4.2.0" core-foundation = { version = "0.9.0", features = ["mac_os_10_7_support"] } -crates-io = { version = "0.37.0", path = "crates/crates-io" } +crates-io = { version = "0.38.0", path = "crates/crates-io" } criterion = { version = "0.5.1", features = ["html_reports"] } curl = "0.4.44" curl-sys = "0.4.63" diff --git a/crates/crates-io/Cargo.toml b/crates/crates-io/Cargo.toml index a82b1d126f7..9896a7f4f67 100644 --- a/crates/crates-io/Cargo.toml +++ b/crates/crates-io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "crates-io" -version = "0.37.0" +version = "0.38.0" edition.workspace = true license.workspace = true repository = "https://github.com/rust-lang/cargo" diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 243808098c4..7e3d3361c4a 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -130,6 +130,7 @@ pub enum ResponseError { Curl(curl::Error), Api { code: u32, + headers: Vec, errors: Vec, }, Code { @@ -155,7 +156,7 @@ impl fmt::Display for ResponseError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ResponseError::Curl(e) => write!(f, "{}", e), - ResponseError::Api { code, errors } => { + ResponseError::Api { code, errors, .. } => { f.write_str("the remote server responded with an error")?; if *code != 200 { write!(f, " (status {} {})", code, reason(*code))?; @@ -447,7 +448,7 @@ impl Registry { match (self.handle.response_code()?, errors) { (0, None) | (200, None) => Ok(body), - (code, Some(errors)) => Err(ResponseError::Api { code, errors }), + (code, Some(errors)) => Err(ResponseError::Api { code, headers, errors }), (code, None) => Err(ResponseError::Code { code, headers, From 00a872740ae09456b0a3127a5eb922e7678a76fb Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Thu, 6 Jul 2023 13:25:34 +0100 Subject: [PATCH 2/6] fix(crates-io): don't let server sneak extra lines anywhere From https://github.com/rust-lang/cargo/blob/5febbe5587b74108165f748e79a4f8badbdf5e0e/src/cargo/sources/registry/http_remote.rs#L234-L237 --- crates/crates-io/lib.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 7e3d3361c4a..af1f491a04f 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -428,6 +428,10 @@ impl Registry { // Headers contain trailing \r\n, trim them to make it easier // to work with. let s = String::from_utf8_lossy(data).trim().to_string(); + // Don't let server sneak extra lines anywhere. + if s.contains('\n') { + return true; + } headers.push(s); true })?; @@ -448,7 +452,11 @@ impl Registry { match (self.handle.response_code()?, errors) { (0, None) | (200, None) => Ok(body), - (code, Some(errors)) => Err(ResponseError::Api { code, headers, errors }), + (code, Some(errors)) => Err(ResponseError::Api { + code, + headers, + errors, + }), (code, None) => Err(ResponseError::Code { code, headers, From ed6692b1c697219ad9083e9f5c57a4915b014154 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Thu, 6 Jul 2023 13:36:23 +0100 Subject: [PATCH 3/6] refactor(crates-io): rename `ResponseError` to `Error` --- crates/crates-io/lib.rs | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index af1f491a04f..43965c218ae 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -126,7 +126,7 @@ struct Crates { } #[derive(Debug)] -pub enum ResponseError { +pub enum Error { Curl(curl::Error), Api { code: u32, @@ -141,29 +141,29 @@ pub enum ResponseError { Other(anyhow::Error), } -impl std::error::Error for ResponseError { +impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - ResponseError::Curl(..) => None, - ResponseError::Api { .. } => None, - ResponseError::Code { .. } => None, - ResponseError::Other(e) => Some(e.as_ref()), + Self::Curl(..) => None, + Self::Api { .. } => None, + Self::Code { .. } => None, + Self::Other(e) => Some(e.as_ref()), } } } -impl fmt::Display for ResponseError { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - ResponseError::Curl(e) => write!(f, "{}", e), - ResponseError::Api { code, errors, .. } => { + Self::Curl(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(", ")) } - ResponseError::Code { + Self::Code { code, headers, body, @@ -178,14 +178,14 @@ impl fmt::Display for ResponseError { headers.join("\n\t"), body ), - ResponseError::Other(..) => write!(f, "invalid response from server"), + Self::Other(..) => write!(f, "invalid response from server"), } } } -impl From for ResponseError { +impl From for Error { fn from(error: curl::Error) -> Self { - ResponseError::Curl(error) + Self::Curl(error) } } @@ -301,7 +301,7 @@ impl Registry { let body = self .handle(&mut |buf| body.read(buf).unwrap_or(0)) .map_err(|e| match e { - ResponseError::Code { code, .. } + Error::Code { code, .. } if code == 503 && started.elapsed().as_secs() >= 29 && self.host_is_crates_io() => @@ -414,7 +414,7 @@ impl Registry { fn handle( &mut self, read: &mut dyn FnMut(&mut [u8]) -> usize, - ) -> std::result::Result { + ) -> std::result::Result { let mut headers = Vec::new(); let mut body = Vec::new(); { @@ -441,7 +441,7 @@ impl Registry { let body = match String::from_utf8(body) { Ok(body) => body, Err(..) => { - return Err(ResponseError::Other(format_err!( + return Err(Error::Other(format_err!( "response body was not valid utf-8" ))) } @@ -452,12 +452,12 @@ impl Registry { match (self.handle.response_code()?, errors) { (0, None) | (200, None) => Ok(body), - (code, Some(errors)) => Err(ResponseError::Api { + (code, Some(errors)) => Err(Error::Api { code, headers, errors, }), - (code, None) => Err(ResponseError::Code { + (code, None) => Err(Error::Code { code, headers, body, From a561e8b781c1736374ce6f2c359d7509952e9888 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Sun, 25 Jun 2023 09:42:26 +0100 Subject: [PATCH 4/6] feat(crates-io): use our own `Result` wrapper everywhere --- crates/crates-io/lib.rs | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 43965c218ae..9608a60946b 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -7,12 +7,14 @@ use std::io::prelude::*; use std::io::{Cursor, SeekFrom}; use std::time::Instant; -use anyhow::{bail, format_err, Context, Result}; +use anyhow::{format_err, Context}; use curl::easy::{Easy, List}; use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; use serde::{Deserialize, Serialize}; use url::Url; +pub type Result = std::result::Result; + pub struct Registry { /// The base URL for issuing API requests. host: String, @@ -189,6 +191,24 @@ impl From for Error { } } +impl From for Error { + fn from(error: anyhow::Error) -> Self { + Self::Other(error) + } +} + +impl From for Error { + fn from(error: serde_json::Error) -> Self { + Self::Other(error.into()) + } +} + +impl From for Error { + fn from(error: url::ParseError) -> Self { + Self::Other(error.into()) + } +} + impl Registry { /// Creates a new `Registry`. /// @@ -224,7 +244,9 @@ impl Registry { fn token(&self) -> Result<&str> { let token = match self.token.as_ref() { Some(s) => s, - None => bail!("no upload token found, please run `cargo login`"), + None => { + return Err(format_err!("no upload token found, please run `cargo login`").into()) + } }; check_token(token)?; Ok(token) @@ -411,10 +433,7 @@ impl Registry { } } - fn handle( - &mut self, - read: &mut dyn FnMut(&mut [u8]) -> usize, - ) -> std::result::Result { + fn handle(&mut self, read: &mut dyn FnMut(&mut [u8]) -> usize) -> Result { let mut headers = Vec::new(); let mut body = Vec::new(); { @@ -529,7 +548,7 @@ pub fn is_url_crates_io(url: &str) -> bool { /// registries only create tokens in that format so that is as less restricted as possible. pub fn check_token(token: &str) -> Result<()> { if token.is_empty() { - bail!("please provide a non-empty token"); + return Err(format_err!("please provide a non-empty token").into()); } if token.bytes().all(|b| { // This is essentially the US-ASCII limitation of @@ -540,9 +559,10 @@ pub fn check_token(token: &str) -> Result<()> { }) { Ok(()) } else { - Err(anyhow::anyhow!( + Err(format_err!( "token contains invalid characters.\nOnly printable ISO-8859-1 characters \ are allowed as it is sent in a HTTPS header." - )) + ) + .into()) } } From b4499af4e9832f2318d8b53c82c1378a2154a4de Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Sun, 25 Jun 2023 10:58:35 +0100 Subject: [PATCH 5/6] 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. --- Cargo.lock | 1 - crates/crates-io/Cargo.toml | 1 - crates/crates-io/lib.rs | 108 ++++++++++++++++++++---------------- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 07f6320a968..bca1f82fe3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -587,7 +587,6 @@ dependencies = [ name = "crates-io" version = "0.38.0" dependencies = [ - "anyhow", "curl", "percent-encoding", "serde", diff --git a/crates/crates-io/Cargo.toml b/crates/crates-io/Cargo.toml index 9896a7f4f67..e1ae836b7b9 100644 --- a/crates/crates-io/Cargo.toml +++ b/crates/crates-io/Cargo.toml @@ -13,7 +13,6 @@ name = "crates_io" path = "lib.rs" [dependencies] -anyhow.workspace = true curl.workspace = true percent-encoding.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/crates/crates-io/lib.rs b/crates/crates-io/lib.rs index 9608a60946b..9f363997b65 100644 --- a/crates/crates-io/lib.rs +++ b/crates/crates-io/lib.rs @@ -7,7 +7,6 @@ use std::io::prelude::*; use std::io::{Cursor, SeekFrom}; use std::time::Instant; -use anyhow::{format_err, Context}; use curl::easy::{Easy, List}; use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; use serde::{Deserialize, Serialize}; @@ -127,29 +126,55 @@ struct Crates { meta: TotalCrates, } +/// Error returned when interacting with a registry. #[derive(Debug)] pub enum Error { + /// Error from libcurl. Curl(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 from IO. Mostly from reading the tarball to upload. + Io(std::io::Error), + + /// Response body was not valid utf8. + Utf8(std::string::FromUtf8Error), + + /// Error from API response containing JSON field `errors.details`. Api { code: u32, headers: Vec, errors: Vec, }, + + /// Error from API response which didn't have pre-programmed `errors.details`. Code { code: u32, headers: Vec, body: String, }, - Other(anyhow::Error), + + /// Reason why the token was invalid. + InvalidToken(&'static str), + + /// Server was unavailable and timeouted. Happened when uploading a way + /// too large tarball to crates.io. + Timeout(u64), } impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { - Self::Curl(..) => None, + 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::Other(e) => Some(e.as_ref()), + Self::InvalidToken(..) => None, + Self::Timeout(..) => None, } } } @@ -157,7 +182,10 @@ impl std::error::Error for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::Curl(e) => write!(f, "{}", e), + 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 { @@ -180,7 +208,14 @@ impl fmt::Display for Error { headers.join("\n\t"), body ), - Self::Other(..) => write!(f, "invalid response from server"), + 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}." + ), } } } @@ -191,21 +226,21 @@ impl From for Error { } } -impl From for Error { - fn from(error: anyhow::Error) -> Self { - Self::Other(error) +impl From for Error { + fn from(error: serde_json::Error) -> Self { + Self::Json(error) } } -impl From for Error { - fn from(error: serde_json::Error) -> Self { - Self::Other(error.into()) +impl From for Error { + fn from(error: std::io::Error) -> Self { + Self::Io(error) } } -impl From for Error { - fn from(error: url::ParseError) -> Self { - Self::Other(error.into()) +impl From for Error { + fn from(error: std::string::FromUtf8Error) -> Self { + Self::Utf8(error) } } @@ -242,12 +277,9 @@ impl Registry { } fn token(&self) -> Result<&str> { - let token = match self.token.as_ref() { - Some(s) => s, - None => { - return Err(format_err!("no upload token found, please run `cargo login`").into()) - } - }; + let token = self.token.as_ref().ok_or_else(|| { + Error::InvalidToken("no upload token found, please run `cargo login`") + })?; check_token(token)?; Ok(token) } @@ -293,12 +325,8 @@ impl Registry { // This checks the length using seeking instead of metadata, because // on some filesystems, getting the metadata will fail because // the file was renamed in ops::package. - let tarball_len = tarball - .seek(SeekFrom::End(0)) - .with_context(|| "failed to seek tarball")?; - tarball - .seek(SeekFrom::Start(0)) - .with_context(|| "failed to seek tarball")?; + let tarball_len = tarball.seek(SeekFrom::End(0))?; + tarball.seek(SeekFrom::Start(0))?; let header = { let mut w = Vec::new(); w.extend(&(json.len() as u32).to_le_bytes()); @@ -328,13 +356,7 @@ impl Registry { && started.elapsed().as_secs() >= 29 && self.host_is_crates_io() => { - format_err!( - "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_len - ) + Error::Timeout(tarball_len) } _ => e.into(), })?; @@ -457,14 +479,7 @@ impl Registry { handle.perform()?; } - let body = match String::from_utf8(body) { - Ok(body) => body, - Err(..) => { - return Err(Error::Other(format_err!( - "response body was not valid utf-8" - ))) - } - }; + let body = String::from_utf8(body)?; let errors = serde_json::from_str::(&body) .ok() .map(|s| s.errors.into_iter().map(|s| s.detail).collect::>()); @@ -548,7 +563,7 @@ pub fn is_url_crates_io(url: &str) -> bool { /// registries only create tokens in that format so that is as less restricted as possible. pub fn check_token(token: &str) -> Result<()> { if token.is_empty() { - return Err(format_err!("please provide a non-empty token").into()); + return Err(Error::InvalidToken("please provide a non-empty token")); } if token.bytes().all(|b| { // This is essentially the US-ASCII limitation of @@ -559,10 +574,9 @@ pub fn check_token(token: &str) -> Result<()> { }) { Ok(()) } else { - Err(format_err!( + Err(Error::InvalidToken( "token contains invalid characters.\nOnly printable ISO-8859-1 characters \ - are allowed as it is sent in a HTTPS header." - ) - .into()) + are allowed as it is sent in a HTTPS header.", + )) } } From 31b500c7c07d48c7964cb97a412cbfd1b7e6b596 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Sun, 25 Jun 2023 14:27:58 +0100 Subject: [PATCH 6/6] 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 bca1f82fe3a..8c0eeafb220 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 b924178a194..8cdb6404f47 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 e1ae836b7b9..139b8aa9740 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 9f363997b65..6ce39cefd4b 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 45b7c7da5ab..eb749ee206b 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();