Files
arr/crates/arr-meta/src/error.rs
T
Miguel Palhas 0fa422e170
ci / web (pull_request) Successful in 6s
ci / rust (pull_request) Successful in 46s
feat(meta): TMDB search and movie lookup
Search and detail lookup, carrying the two fields the policy engine
needs and that are easy to lose at the edge.

`original_language` arrives as bare ISO 639-1, which has one code for
both Portuguese variants, so the dub rule in §5.2 cannot be expressed
against it alone. `Movie` therefore also carries `origin_countries`,
from `origin_country` where TMDB has it and `production_countries`
otherwise, which is what separates a Brazilian film from a Portuguese
one.

Digital release dates come back appended to the detail call rather than
costing a second round trip, reduced to the earliest type-4 entry across
every country: a release existing in one region is a release that exists
on the indexers. `Movie::is_digitally_released` puts the §6.2 gate in
one tested place.

Responses are cached in process for a day, matching the metadata refresh
tick. Failures are never cached, so an outage cannot pin a title into a
bad state until tomorrow.

Tests are wiremock-backed, per §12.
2026-08-22 19:39:21 +01:00

57 lines
1.8 KiB
Rust

//! Errors the TMDB client can produce.
use std::time::Duration;
/// Result alias for every fallible operation in this crate.
pub type Result<T> = std::result::Result<T, Error>;
/// Everything that can go wrong talking to TMDB.
///
/// The distinction that matters to callers is between "this title does not
/// exist" ([`Error::NotFound`]), "back off" ([`Error::RateLimited`]) and
/// "TMDB is unreachable" ([`Error::Transport`]) — the last is a §9.5 *broken*
/// notification, the first two are not.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// The request never completed: DNS, TLS, connection or timeout.
#[error("TMDB request failed")]
Transport(#[from] reqwest::Error),
/// TMDB rejected the API key.
#[error("TMDB rejected the API key")]
Unauthorized,
/// TMDB has no record of the thing that was asked for.
#[error("TMDB has no record of {resource}")]
NotFound {
/// The logical resource that was requested, e.g. `movie/693134`.
resource: String,
},
/// TMDB is rate limiting. `retry_after` is the `Retry-After` header when
/// TMDB sent one.
#[error("TMDB rate limit reached")]
RateLimited {
/// How long TMDB asked us to wait, when it said.
retry_after: Option<Duration>,
},
/// Any other non-success status.
#[error("TMDB returned HTTP {status}")]
Unexpected {
/// The HTTP status code.
status: u16,
/// The response body, truncated to something loggable.
body: String,
},
/// The response parsed as JSON but not into the shape expected.
#[error("TMDB response did not match the expected shape")]
Decode(#[from] serde_json::Error),
/// The configured base URL is not a URL.
#[error("invalid TMDB base URL: {0}")]
BaseUrl(String),
}