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.
57 lines
1.8 KiB
Rust
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),
|
|
}
|