fix(indexer): preserve request status
ci / web (pull_request) Successful in 5s
ci / rust (pull_request) Successful in 42s
e2e / e2e (pull_request) Successful in 1m13s

This commit is contained in:
Miguel Palhas
2026-08-22 20:00:51 +01:00
parent 192afdf5d7
commit 2e1b0a60f8
2 changed files with 45 additions and 11 deletions
+44 -11
View File
@@ -3,7 +3,7 @@
use std::{collections::BTreeSet, time::Duration};
use quick_xml::events::Event;
use reqwest::Client;
use reqwest::{Client, StatusCode};
use serde::Deserialize;
use thiserror::Error;
@@ -95,9 +95,13 @@ impl ProwlarrClient {
.header("X-Api-Key", &self.api_key)
.send()
.await
.map_err(|_| Error::Request)?
.map_err(|error| Error::Request {
status: error.status(),
})?
.error_for_status()
.map_err(|_| Error::Request)?;
.map_err(|error| Error::Request {
status: error.status(),
})?;
let indexers: Vec<ProwlarrIndexer> = response.json().await.map_err(|_| Error::Response)?;
let mut result = Vec::with_capacity(indexers.len());
@@ -124,13 +128,19 @@ impl ProwlarrClient {
.query(&[("apikey", self.api_key.as_str()), ("t", "caps")])
.send()
.await
.map_err(|_| CapabilityError::Request)?
.map_err(|error| CapabilityError::Request {
status: error.status(),
})?
.error_for_status()
.map_err(|_| CapabilityError::Request)?;
.map_err(|error| CapabilityError::Request {
status: error.status(),
})?;
let body = response
.text()
.await
.map_err(|_| CapabilityError::Request)?;
.map_err(|error| CapabilityError::Request {
status: error.status(),
})?;
parse_capabilities(&body)
}
}
@@ -140,17 +150,20 @@ impl ProwlarrClient {
pub enum Error {
#[error("could not create Prowlarr HTTP client")]
Client,
#[error("Prowlarr indexer request failed")]
Request,
#[error("Prowlarr indexer request failed (status: {status:?})")]
Request { status: Option<StatusCode> },
#[error("could not parse Prowlarr indexer response")]
Response,
}
/// A failed capabilities request for an otherwise usable Prowlarr indexer.
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum CapabilityError {
Request,
#[error("capabilities request failed (status: {status:?})")]
Request { status: Option<StatusCode> },
#[error("capabilities response was not valid Torznab XML")]
InvalidResponse,
#[error("tracker returned Torznab error {code:?}: {description}")]
Torznab {
code: Option<u16>,
description: String,
@@ -273,6 +286,7 @@ fn is_available(value: &str) -> bool {
#[cfg(test)]
mod tests {
use super::{Capabilities, CapabilityError, ProwlarrClient};
use reqwest::StatusCode;
use wiremock::{
matchers::{header, method, path, query_param},
Mock, MockServer, ResponseTemplate,
@@ -306,6 +320,7 @@ mod tests {
include_str!("../tests/fixtures/broken-caps.xml"),
)
.await;
mount_failure(&server, 29).await;
let indexers = ProwlarrClient::new(server.uri(), API_KEY)
.expect("HTTP client can be created")
@@ -313,7 +328,7 @@ mod tests {
.await
.expect("recorded Prowlarr responses are valid");
assert_eq!(indexers.len(), 3);
assert_eq!(indexers.len(), 4);
assert_eq!(indexers[0].id, 3);
assert_eq!(indexers[0].name, "Alpha Tracker");
assert!(indexers[0].capabilities.movie.available);
@@ -337,6 +352,14 @@ mod tests {
description: "Indexer unavailable".to_owned(),
})
);
assert_eq!(indexers[3].id, 29);
assert_eq!(
indexers[3].capabilities_error,
Some(CapabilityError::Request {
status: Some(StatusCode::SERVICE_UNAVAILABLE),
})
);
}
async fn mount_capabilities(server: &MockServer, id: i64, body: &str) {
@@ -348,4 +371,14 @@ mod tests {
.mount(server)
.await;
}
async fn mount_failure(server: &MockServer, id: i64) {
Mock::given(method("GET"))
.and(path(format!("/{id}/api")))
.and(query_param("apikey", API_KEY))
.and(query_param("t", "caps"))
.respond_with(ResponseTemplate::new(503))
.mount(server)
.await;
}
}
+1
View File
@@ -2,5 +2,6 @@
{ "id": 3, "name": "Alpha Tracker", "enable": true },
{ "id": 17, "name": "Text Only", "enable": true },
{ "id": 23, "name": "Broken Tracker", "enable": true },
{ "id": 29, "name": "Unavailable Tracker", "enable": true },
{ "id": 42, "name": "Disabled Tracker", "enable": false }
]