mirror of
https://github.com/rust-lang/cargo.git
synced 2025-09-28 11:20:36 +00:00
Add more context to publish-failed error message (#15879)
## What does this PR try to resolve? This PR fixes issue #15754 where workspace publish failures provided non-actionable error messages. Previously, when publishing a workspace failed (due to rate limiting or other errors), users only saw generic errors like `[ERROR] failed to publish to registry` without knowing which package failed or what packages remained to be published. ## How to test and review this PR? **Testing**: Run `cargo test workspace_publish_rate_limit_error` to see the improved behavior, then `cargo test publish` to ensure all tests pass. **Review**: - **Commit 1**: Adds test demonstrating current problematic behavior - **Commit 2**: Implements fix and updates test to expect improved output The fix transforms error messages from generic `failed to publish to registry` to actionable `failed to publish 'package_a' v0.1.0; the following crates have not been published yet: package_b v0.1.0, package_c v0.1.0`. This improvement applies consistently across all publish error scenarios, giving users clear information about what went wrong and what remains to be done.
This commit is contained in:
commit
0bfd820f83
@ -210,7 +210,8 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
|
||||
// `b`, and we uploaded `a` and `b` but only confirmed `a`, then on
|
||||
// the following pass through the outer loop nothing will be ready for
|
||||
// upload.
|
||||
for pkg_id in plan.take_ready() {
|
||||
let mut ready = plan.take_ready();
|
||||
while let Some(pkg_id) = ready.pop_first() {
|
||||
let (pkg, (_features, tarball)) = &pkg_dep_graph.packages[&pkg_id];
|
||||
opts.gctx.shell().status("Uploading", pkg.package_id())?;
|
||||
|
||||
@ -236,6 +237,19 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
|
||||
)?));
|
||||
}
|
||||
|
||||
let workspace_context = || {
|
||||
let mut remaining = ready.clone();
|
||||
remaining.extend(plan.iter());
|
||||
if !remaining.is_empty() {
|
||||
format!(
|
||||
"\n\nnote: the following crates have not been published yet:\n {}",
|
||||
remaining.into_iter().join("\n ")
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
};
|
||||
|
||||
transmit(
|
||||
opts.gctx,
|
||||
ws,
|
||||
@ -244,6 +258,7 @@ pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
|
||||
&mut registry,
|
||||
source_ids.original,
|
||||
opts.dry_run,
|
||||
workspace_context,
|
||||
)?;
|
||||
to_confirm.insert(pkg_id);
|
||||
|
||||
@ -632,6 +647,7 @@ fn transmit(
|
||||
registry: &mut Registry,
|
||||
registry_id: SourceId,
|
||||
dry_run: bool,
|
||||
workspace_context: impl Fn() -> String,
|
||||
) -> CargoResult<()> {
|
||||
let new_crate = prepare_transmit(gctx, ws, pkg, registry_id)?;
|
||||
|
||||
@ -641,9 +657,15 @@ fn transmit(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let warnings = registry
|
||||
.publish(&new_crate, tarball)
|
||||
.with_context(|| format!("failed to publish to registry at {}", registry.host()))?;
|
||||
let warnings = registry.publish(&new_crate, tarball).with_context(|| {
|
||||
format!(
|
||||
"failed to publish {} v{} to registry at {}{}",
|
||||
pkg.name(),
|
||||
pkg.version(),
|
||||
registry.host(),
|
||||
workspace_context()
|
||||
)
|
||||
})?;
|
||||
|
||||
if !warnings.invalid_categories.is_empty() {
|
||||
let msg = format!(
|
||||
|
@ -2260,7 +2260,7 @@ fn api_error_json() {
|
||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
||||
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||
|
||||
Caused by:
|
||||
the remote server responded with an error (status 403 Forbidden): you must be logged in
|
||||
@ -2308,7 +2308,7 @@ fn api_error_200() {
|
||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
||||
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||
|
||||
Caused by:
|
||||
the remote server responded with an [ERROR] max upload size is 123
|
||||
@ -2356,7 +2356,7 @@ fn api_error_code() {
|
||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
||||
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||
|
||||
Caused by:
|
||||
failed to get a 200 OK response, got 400
|
||||
@ -2413,7 +2413,7 @@ fn api_curl_error() {
|
||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
||||
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||
|
||||
Caused by:
|
||||
[52] Server returned nothing (no headers, no data) (Empty reply from server)
|
||||
@ -2461,7 +2461,7 @@ fn api_other_error() {
|
||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
||||
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||
|
||||
Caused by:
|
||||
invalid response body from server
|
||||
@ -3608,7 +3608,7 @@ fn invalid_token() {
|
||||
[PACKAGING] foo v0.0.1 ([ROOT]/foo)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[UPLOADING] foo v0.0.1 ([ROOT]/foo)
|
||||
[ERROR] failed to publish to registry at http://127.0.0.1:[..]/
|
||||
[ERROR] failed to publish foo v0.0.1 to registry at http://127.0.0.1:[..]/
|
||||
|
||||
Caused by:
|
||||
token contains invalid characters.
|
||||
@ -4402,3 +4402,110 @@ See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for
|
||||
"#]])
|
||||
.run();
|
||||
}
|
||||
|
||||
#[cargo_test]
|
||||
fn workspace_publish_rate_limit_error() {
|
||||
let registry = registry::RegistryBuilder::new()
|
||||
.http_api()
|
||||
.http_index()
|
||||
.add_responder("/api/v1/crates/new", |_req, _| {
|
||||
// For simplicity, let's just return rate limit error for all requests
|
||||
// This simulates hitting rate limit during workspace publish
|
||||
Response {
|
||||
code: 429,
|
||||
headers: vec!["Retry-After: 3600".to_string()],
|
||||
body: format!(
|
||||
"You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email help@crates.io to have your limit increased."
|
||||
).into_bytes(),
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
let p = project()
|
||||
.file(
|
||||
"Cargo.toml",
|
||||
r#"
|
||||
[workspace]
|
||||
members = ["package_a", "package_b", "package_c"]
|
||||
"#,
|
||||
)
|
||||
.file("src/lib.rs", "")
|
||||
.file(
|
||||
"package_a/Cargo.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "package_a"
|
||||
version = "0.1.0"
|
||||
edition = "2015"
|
||||
license = "MIT"
|
||||
description = "package a"
|
||||
repository = "https://github.com/test/package_a"
|
||||
"#,
|
||||
)
|
||||
.file("package_a/src/lib.rs", "")
|
||||
.file(
|
||||
"package_b/Cargo.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "package_b"
|
||||
version = "0.1.0"
|
||||
edition = "2015"
|
||||
license = "MIT"
|
||||
description = "package b"
|
||||
repository = "https://github.com/test/package_b"
|
||||
"#,
|
||||
)
|
||||
.file("package_b/src/lib.rs", "")
|
||||
.file(
|
||||
"package_c/Cargo.toml",
|
||||
r#"
|
||||
[package]
|
||||
name = "package_c"
|
||||
version = "0.1.0"
|
||||
edition = "2015"
|
||||
license = "MIT"
|
||||
description = "package c"
|
||||
repository = "https://github.com/test/package_c"
|
||||
|
||||
[dependencies]
|
||||
package_a = { version = "0.1.0", path = "../package_a" }
|
||||
"#,
|
||||
)
|
||||
.file("package_c/src/lib.rs", "")
|
||||
.build();
|
||||
|
||||
// This demonstrates the improved error message after the fix
|
||||
// The user now knows which package failed and what packages remain to be published
|
||||
p.cargo("publish --workspace --no-verify")
|
||||
.replace_crates_io(registry.index_url())
|
||||
.with_status(101)
|
||||
.with_stderr_data(str![[r#"
|
||||
[UPDATING] crates.io index
|
||||
[PACKAGING] package_a v0.1.0 ([ROOT]/foo/package_a)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[PACKAGING] package_b v0.1.0 ([ROOT]/foo/package_b)
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[PACKAGING] package_c v0.1.0 ([ROOT]/foo/package_c)
|
||||
[UPDATING] crates.io index
|
||||
[PACKAGED] 4 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
|
||||
[UPLOADING] package_a v0.1.0 ([ROOT]/foo/package_a)
|
||||
[ERROR] failed to publish package_a v0.1.0 to registry at http://127.0.0.1:[..]/
|
||||
|
||||
[NOTE] the following crates have not been published yet:
|
||||
package_b v0.1.0 ([ROOT]/foo/package_b)
|
||||
package_c v0.1.0 ([ROOT]/foo/package_c)
|
||||
|
||||
Caused by:
|
||||
failed to get a 200 OK response, got 429
|
||||
headers:
|
||||
HTTP/1.1 429
|
||||
Content-Length: 172
|
||||
Connection: close
|
||||
Retry-After: 3600
|
||||
|
||||
body:
|
||||
You have published too many new crates in a short period of time. Please try again after Fri, 18 Jul 2025 20:00:34 GMT or email help@crates.io to have your limit increased.
|
||||
|
||||
"#]])
|
||||
.run();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user