mirror of
https://github.com/narrrl/proton-sdk-rs.git
synced 2026-11-03 16:41:26 +01:00
feat(http): log the raw response code of failed API requests
An envelope code that ResponseCode does not know deserializes to Unknown, so the error a caller sees has lost the number Proton sent. Log path, HTTP status and the raw code at debug level before mapping it.
This commit is contained in:
1 parent
325c326c49
commit
d3f4090772
1 file changed
+33
-5
@@ -708,23 +708,44 @@ pub async fn post_unauthenticated_verified<B: Serialize, T: DeserializeOwned>(
|
||||
/// the typed success payload.
|
||||
async fn parse_response<T: DeserializeOwned>(response: reqwest::Response) -> Result<T> {
|
||||
let status = response.status();
|
||||
let path = response.url().path().to_owned();
|
||||
let bytes = response.bytes().await?;
|
||||
|
||||
// Every Proton response embeds the envelope; a missing/non-success code or a
|
||||
// non-2xx HTTP status is an API error. `MultipleResponses` (1001) is a batch
|
||||
// multi-status, not a failure: the real per-item codes live in the body, so
|
||||
// the caller (e.g. trash/restore/delete) inspects them itself.
|
||||
if let Ok(envelope) = serde_json::from_slice::<ApiResponse>(&bytes) {
|
||||
if !envelope.is_success() && envelope.code != ResponseCode::MultipleResponses {
|
||||
return Err(api_error(status, &bytes));
|
||||
}
|
||||
} else if !status.is_success() {
|
||||
let failed = match serde_json::from_slice::<ApiResponse>(&bytes) {
|
||||
Ok(envelope) => !envelope.is_success() && envelope.code != ResponseCode::MultipleResponses,
|
||||
Err(_) => !status.is_success(),
|
||||
};
|
||||
if failed {
|
||||
// A code missing from `ResponseCode` deserializes to `Unknown`, so this
|
||||
// line is the only place its number survives.
|
||||
tracing::debug!(
|
||||
path,
|
||||
http_status = status.as_u16(),
|
||||
code = raw_code(&bytes),
|
||||
"proton api request failed"
|
||||
);
|
||||
return Err(api_error(status, &bytes));
|
||||
}
|
||||
|
||||
Ok(serde_json::from_slice::<T>(&bytes)?)
|
||||
}
|
||||
|
||||
/// The envelope's `Code` exactly as sent, before [`ResponseCode`] maps it.
|
||||
fn raw_code(bytes: &[u8]) -> Option<i64> {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct RawCode {
|
||||
#[serde(rename = "Code")]
|
||||
code: i64,
|
||||
}
|
||||
serde_json::from_slice::<RawCode>(bytes)
|
||||
.ok()
|
||||
.map(|raw| raw.code)
|
||||
}
|
||||
|
||||
fn api_error(status: StatusCode, bytes: &[u8]) -> ProtonError {
|
||||
let envelope = serde_json::from_slice::<ApiResponse>(bytes).ok();
|
||||
let code = envelope
|
||||
@@ -845,6 +866,13 @@ fn ensure_trailing_slash(url: &str) -> String {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn raw_code_keeps_codes_response_code_does_not_know() {
|
||||
let body = br#"{"Code":5099,"Error":"This version of the app is no longer supported"}"#;
|
||||
assert_eq!(raw_code(body), Some(5099));
|
||||
assert_eq!(raw_code(b"<html>bad gateway</html>"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retryable_statuses() {
|
||||
for code in [408u16, 429, 502, 503, 504] {
|
||||
|
||||
Reference in new issue
Block a user