fix(pixiv): stop retrying permanent 4xx API errors

site::fetch retried every PixivError, so a bad/expired token (403) or a
deleted artwork (404) burned all 3 attempts with backoff against pixiv's
API for nothing. Add PixivError::Status(u16) — the app-API calls now
surface the HTTP status — and retry only the transient classes: network
errors, 429 and 5xx. 4xx / Api (token errors) / Json / NoAuth are
returned immediately. The classification is a pure helper
(fetch_error_is_retryable) with unit tests.
This commit is contained in:
2026-08-13 23:17:52 +08:00
parent 6911e9146e
commit 47935dd7c6
2 changed files with 81 additions and 10 deletions
+7 -2
View File
@@ -29,6 +29,10 @@ pub enum PixivError {
NoAuth,
Http(reqwest::Error),
Json(serde_json::Error),
/// Non-2xx HTTP status from the app API. The code lets [`crate::site::fetch`]
/// retry only transient classes (429 / 5xx) instead of burning attempts on
/// permanent 4xx (bad token, forbidden, not found).
Status(u16),
Api(String),
}
@@ -38,6 +42,7 @@ impl fmt::Display for PixivError {
PixivError::NoAuth => write!(f, "pixiv: no authentication"),
PixivError::Http(e) => write!(f, "pixiv http error: {e}"),
PixivError::Json(e) => write!(f, "pixiv json error: {e}"),
PixivError::Status(code) => write!(f, "pixiv status {code}"),
PixivError::Api(message) => write!(f, "pixiv api error: {message}"),
}
}
@@ -137,7 +142,7 @@ impl PixivAPI {
.send()
.await?;
if !response.status().is_success() {
return Err(PixivError::Api(format!("status {}", response.status())));
return Err(PixivError::Status(response.status().as_u16()));
}
let json: serde_json::Value = serde_json::from_str(&response.text().await?)?;
if json.get("error").is_some() {
@@ -190,7 +195,7 @@ impl PixivAPI {
.send()
.await?;
if !response.status().is_success() {
return Err(PixivError::Api(format!("status {}", response.status())));
return Err(PixivError::Status(response.status().as_u16()));
}
let json: serde_json::Value = serde_json::from_str(&response.text().await?)?;
if json.get("error").is_some() {