lighthouse/common/sensitive_url/src/lib.rs
Paul Hauner 9072acbfa6 Tidy formatting of Reqwest errors (#4336)
## Issue Addressed

NA

## Proposed Changes

Implements the `PrettyReqwestError` to wrap a `reqwest::Error` and give nicer `Debug` formatting. It also wraps the `Url` component in a `SensitiveUrl` to avoid leaking sensitive info in logs.

### Before

```
Reqwest(reqwest::Error { kind: Request, url: Url { scheme: "http", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("localhost")), port: Some(9999), path: "/eth/v1/node/version", query: None, fragment: None }, source: hyper::Error(Connect, ConnectError("tcp connect error", Os { code: 61, kind: ConnectionRefused, message: "Connection refused" })) })
```

### After

```
HttpClient(url: http://localhost:9999/, kind: request, detail: error trying to connect: tcp connect error: Connection refused (os error 61))
```

## Additional Info

I've also renamed the `Reqwest` error enum variants to `HttpClient`, to give people a better chance at knowing what's going on. Reqwest is pretty odd and looks like a typo.

I've implemented it in the `eth2` and `execution_layer` crates. This should affect most logs in the VC and EE-related ones in the BN.

I think the last crate that could benefit from the is the `beacon_node/eth1` crate. I haven't updated it in this PR since its error type is not so amenable to it (everything goes into a `String`). I don't have a whole lot of time to jig around with that at the moment and I feel that this PR as it stands is a significant enough improvement to merge on its own. Leaving it as-is is fine for the time being and we can always come back for it later (or implement in-protocol deposits!).
2023-06-27 01:06:50 +00:00

121 lines
3.2 KiB
Rust

use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::str::FromStr;
use url::Url;
#[derive(Debug)]
pub enum SensitiveError {
InvalidUrl(String),
ParseError(url::ParseError),
RedactError(String),
}
impl fmt::Display for SensitiveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
// Wrapper around Url which provides a custom `Display` implementation to protect user secrets.
#[derive(Clone, PartialEq)]
pub struct SensitiveUrl {
pub full: Url,
pub redacted: String,
}
impl fmt::Display for SensitiveUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.redacted.fmt(f)
}
}
impl fmt::Debug for SensitiveUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.redacted.fmt(f)
}
}
impl AsRef<str> for SensitiveUrl {
fn as_ref(&self) -> &str {
self.redacted.as_str()
}
}
impl Serialize for SensitiveUrl {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.full.as_ref())
}
}
impl<'de> Deserialize<'de> for SensitiveUrl {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
SensitiveUrl::parse(&s)
.map_err(|e| de::Error::custom(format!("Failed to deserialize sensitive URL {:?}", e)))
}
}
impl FromStr for SensitiveUrl {
type Err = SensitiveError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s)
}
}
impl SensitiveUrl {
pub fn parse(url: &str) -> Result<Self, SensitiveError> {
let surl = Url::parse(url).map_err(SensitiveError::ParseError)?;
SensitiveUrl::new(surl)
}
pub fn new(full: Url) -> Result<Self, SensitiveError> {
let mut redacted = full.clone();
redacted
.path_segments_mut()
.map_err(|_| SensitiveError::InvalidUrl("URL cannot be a base.".to_string()))?
.clear();
redacted.set_query(None);
if redacted.has_authority() {
redacted.set_username("").map_err(|_| {
SensitiveError::RedactError("Unable to redact username.".to_string())
})?;
redacted.set_password(None).map_err(|_| {
SensitiveError::RedactError("Unable to redact password.".to_string())
})?;
}
Ok(Self {
full,
redacted: redacted.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn redact_remote_url() {
let full = "https://project:secret@example.com/example?somequery";
let surl = SensitiveUrl::parse(full).unwrap();
assert_eq!(surl.to_string(), "https://example.com/");
assert_eq!(surl.full.to_string(), full);
}
#[test]
fn redact_localhost_url() {
let full = "http://localhost:5052/";
let surl = SensitiveUrl::parse(full).unwrap();
assert_eq!(surl.to_string(), "http://localhost:5052/");
assert_eq!(surl.full.to_string(), full);
}
}