-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Implement RFC9457 compliant messaging #16199
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Cool! cc @woodruffw |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// Try to read the response body and parse it as problem details | ||
match response.bytes().await { | ||
Ok(bytes) => ProblemDetails::from_json(&bytes).ok(), | ||
Err(_) => None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking out loud: is ignoring a malformed details response the right thing here? I think it is, but maybe it makes sense to emit a warning? I could see it being useful to notify upstream servers that they're emitting a malformed payload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I probably wouldn't add a "warning" since it's not something the user can fix, but it might be worth logging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.headers() | ||
.get("content-type") | ||
.and_then(|ct| ct.to_str().ok()) | ||
.map(|ct| ct.starts_with("application/problem+json")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an equality check rather than starts_with
? RFC 9457 says that problem detail responses are identified by exactly the application/problem+json
content-type, so we probably shouldn't accept application/problem+json+abc
or similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if !content_type.starts_with("application/problem+json") { | ||
return None; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it's okay to remove this check, since we do it at the callsite. We can document that a caller-upheld invariant of this API is that the content-type should be right.
(Alternatively we could keep this check, but remove it from the callsite. But that might be a little harder with the lifetimes/consumption.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/uv-client/src/error.rs
Outdated
/// Parse Problem Details from JSON bytes | ||
pub fn from_json(bytes: &[u8]) -> Result<Self, serde_json::Error> { | ||
serde_json::from_slice(bytes) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My 0.02c would be for removing this, and just calling serde_json::from_slice
directly wherever we use it 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pub extensions: std::collections::HashMap<String, serde_json::Value>, | ||
} | ||
|
||
/// Default problem type URI as per RFC 9457 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick, nonblocking: serde defaults sometimes benefit from inlining:
/// Default problem type URI as per RFC 9457 | |
/// Default problem type URI as per RFC 9457 | |
#[inline] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/uv-client/src/error.rs
Outdated
/// Additional problem-specific extension fields | ||
#[serde(flatten)] | ||
pub extensions: std::collections::HashMap<String, serde_json::Value>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
serde will ignore unknown fields by default, so my 0.02c would be for removing this until we actually need to process extension fields. It'll also make the parsing slightly faster (not that that matters much on the error path):
/// Additional problem-specific extension fields | |
#[serde(flatten)] | |
pub extensions: std::collections::HashMap<String, serde_json::Value>, |
(The tests should confirm that this works 🙂)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/uv-client/src/error.rs
Outdated
if let Some(detail) = &self.detail { | ||
detail.clone() | ||
} else if let Some(title) = &self.title { | ||
title.clone() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we render both of these, if present? I can imagine {title}: {detail}
being useful.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/uv-client/src/error.rs
Outdated
} else if let Some(title) = &self.title { | ||
title.clone() | ||
} else { | ||
format!("HTTP error {}", self.status.unwrap_or(0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to specialize here too -- if status
isn't given we should probably take it from the parent request, or maybe we could say "unknown error (server provided no information)" and rely on the error layer above us rendering the status code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test that shows the error message in context? index_has_no_requires_python
and the network.rs
have reference structures.
crates/uv-client/src/error.rs
Outdated
} | ||
_ => { | ||
// If no detail, title, or status is provided, return a generic message | ||
"An error occurred".to_string() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should return None
for this case and fall back to the default case in WrappedReqwestError
instead of using our own string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if let Ok(bytes) = response.bytes().await { | ||
serde_json::from_slice(&bytes).ok() | ||
} else { | ||
warn!("Failed to read response body for problem details"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should show the inner error in this warning (at least for serde), so it's clear why e.g. the data didn't match the schema.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/uv-client/src/error.rs
Outdated
f.write_str("Could not connect, are you offline?") | ||
} else if let Some(problem_details) = &self.problem_details { | ||
// Show problem details if available | ||
write!(f, "{}", problem_details.description()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we need to add something like Server says:
so it's clear that that's from the remote and not uv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the clippy failure, we can box the large variant. |
Some OS types are larger on Windows than on Unix, which can push the error type size over the clippy threshold just for Windows. You can run Windows clippy locally with cargo-xwin and |
7ed1074
to
b9175d5
Compare
I fixed the linter errors, I have experience with them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
I've changed the boxing to a different location because |
Thank you @konstin, I was struggling to understand this error being new to the code base |
Thanks @doddi! |
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.9.3` -> `0.9.5` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>astral-sh/uv (astral-sh/uv)</summary> ### [`v0.9.5`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#095) [Compare Source](astral-sh/uv@0.9.4...0.9.5) Released on 2025-10-21. This release contains an upgrade to `astral-tokio-tar`, which addresses a vulnerability in tar extraction on malformed archives with mismatching size information between the ustar header and PAX extensions. While the `astral-tokio-tar` advisory has been graded as "high" due its potential broader impact, the *specific* impact to uv is **low** due to a lack of novel attacker capability. Specifically, uv only processes tar archives from source distributions, which already possess the capability for full arbitrary code execution by design, meaning that an attacker gains no additional capabilities through `astral-tokio-tar`. Regardless, we take the hypothetical risk of parser differentials very seriously. Out of an abundance of caution, we have assigned this upgrade an advisory: <GHSA-w476-p2h3-79g9> ##### Security - Upgrade `astral-tokio-tar` to 0.5.6 to address a parsing differential ([#​16387](astral-sh/uv#16387)) ##### Enhancements - Add required environment marker example to hint ([#​16244](astral-sh/uv#16244)) - Fix typo in MissingTopLevel warning ([#​16351](astral-sh/uv#16351)) - Improve 403 Forbidden error message to indicate package may not exist ([#​16353](astral-sh/uv#16353)) - Add a hint on `uv pip install` failure if the `--system` flag is used to select an externally managed interpreter ([#​16318](astral-sh/uv#16318)) ##### Bug fixes - Fix backtick escaping for PowerShell ([#​16307](astral-sh/uv#16307)) ##### Documentation - Document metadata consistency expectation ([#​15683](astral-sh/uv#15683)) - Remove outdated aarch64 musl note ([#​16385](astral-sh/uv#16385)) ### [`v0.9.4`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#094) [Compare Source](astral-sh/uv@0.9.3...0.9.4) Released on 2025-10-17. ##### Enhancements - Add CUDA 13.0 support ([#​16321](astral-sh/uv#16321)) - Add auto-detection for Intel GPU on Windows ([#​16280](astral-sh/uv#16280)) - Implement display of RFC 9457 HTTP error contexts ([#​16199](astral-sh/uv#16199)) ##### Bug fixes - Avoid obfuscating pyx tokens in `uv auth token` output ([#​16345](astral-sh/uv#16345)) </details> --- ### Configuration 📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined). 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNTEuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE1Mi45IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
Summary
HTTP1.1 RFC 9112 - HTTP/1.1 section 4 defines the response status code to optionally include a text description (human readable) of the reason for the status code.
RFC9113 - HTTP/2 is the HTTP2 protocol standard and the response status only considers the status code and not the reason phrase, and as such important information can be lost in helping the client determine a route cause of a failure.
As per discussion on this PR the current feeling is that implementing the RFC9457 standard might be the preferred route. This PR makes those changes to aid the discussion which has also been moved to the PEP board
Test Plan
Pulling components that violate our policy over HTTP2 and without any RFC9457 implementation the following message is presented to the user:

With the RFC9457 standard implemented, below you can see the advantage in the extra context as to why the component has been blocked:
