fix(api): give an expired subtitle candidate a typed error

A grab naming a stale candidate_id now fails as ApiError::SubtitleCandidateExpired (404, code candidate_expired) instead of the generic upstream 503 string the panel had to pattern-match for 'not found'.
This commit is contained in:
Miguel Palhas
2026-08-25 05:33:43 +01:00
parent e0e7ddf6de
commit 2357e72113
5 changed files with 100 additions and 23 deletions
+26 -1
View File
@@ -968,7 +968,10 @@ pub async fn grab(
let fetched = provider
.download(&CandidateId::new(input.candidate_id.clone()))
.await
.map_err(|error| ApiError::SubtitleUpstream(error.to_string()))?;
.map_err(|error| match error {
arr_subs::Error::NotFound { .. } => ApiError::SubtitleCandidateExpired,
other => ApiError::SubtitleUpstream(other.to_string()),
})?;
let text = srt_text(&fetched)?;
write_sidecar(&destination, &text).await?;
@@ -2301,6 +2304,28 @@ mod tests {
.exists());
}
/// A candidate id does not outlive the search that produced it (issue
/// #221): the panel gets a typed `code` to switch on, not a string it has
/// to pattern-match against a provider-chosen message.
#[tokio::test]
async fn a_grab_of_an_expired_candidate_is_a_typed_404() {
let fixture = application(vec![Arc::new(DeadProvider)], vec![]).await;
let (status, body) = grab(
&fixture,
serde_json::json!({
"provider": "dead",
"candidate_id": "stale",
"language": "pt-PT"
}),
)
.await;
assert_eq!(status, StatusCode::NOT_FOUND, "{body}");
assert_eq!(body["code"], "candidate_expired");
assert!(fixture.subtitle_rows().await.is_empty());
}
#[tokio::test]
async fn a_grab_naming_an_unconfigured_provider_is_a_422() {
let fixture = stub_application().await;