Skip to main content

flix_tmdb/api/
mod.rs

1//! TMDB API clients
2
3use core::ops::Deref;
4use core::time::Duration;
5use std::sync::RwLock;
6
7use governor::Jitter;
8use reqwest::Request;
9use reqwest::header;
10use serde::de::DeserializeOwned;
11
12use crate::{Cache, CachePolicy, Config};
13
14pub mod collections;
15pub mod episodes;
16pub mod movies;
17pub mod seasons;
18pub mod shows;
19
20/// A generic error wrapping Url and Reqwest errors
21#[derive(Debug, thiserror::Error)]
22pub enum Error {
23	/// Url error wrapper
24	#[error("url parse error: {0}")]
25	Url(#[from] url::ParseError),
26	/// Reqwest error wrapper
27	#[error("reqwest error: {0}")]
28	Reqwest(#[from] reqwest::Error),
29	/// Json error wrapper
30	#[error("json error: {0}")]
31	Json(#[from] serde_json::Error),
32}
33
34fn make_request(config: &Config, path: &str, language: Option<&str>) -> Result<Request, Error> {
35	let url = config.base_url.join(path)?;
36
37	let mut builder = config.client.get(url).header(
38		header::AUTHORIZATION,
39		format!("Bearer {}", config.bearer_token),
40	);
41	if let Some(ref user_agent) = config.user_agent {
42		builder = builder.header(header::USER_AGENT, user_agent);
43	}
44	if let Some(language) = language {
45		builder = builder.query(&[("language", language)]);
46	}
47
48	Ok(builder.build()?)
49}
50
51async fn exec_request<T: DeserializeOwned>(
52	config: &Config,
53	cache: &dyn Cache,
54	policy: &RwLock<CachePolicy>,
55	request: Request,
56) -> Result<T, Error> {
57	let (read_cache, write_cache) = if let Ok(guard) = policy.read() {
58		match guard.deref() {
59			CachePolicy::None => (None, None),
60			CachePolicy::Full => (Some(cache), Some(cache)),
61			CachePolicy::Read => (Some(cache), None),
62			CachePolicy::Update => (None, Some(cache)),
63		}
64	} else {
65		(None, None)
66	};
67
68	let path = request.url().path().to_owned();
69
70	// read the cache and fall back to reqwest
71	let mut response = None;
72	if let Some(cache) = read_cache {
73		response = cache.get(&path);
74	}
75	let needs_cache_write = response.is_none();
76	let response = match response {
77		Some(response) => response,
78		None => {
79			config
80				.limiter
81				.until_ready_with_jitter(Jitter::new(
82					Duration::from_millis(0),
83					Duration::from_millis(50),
84				))
85				.await;
86			config
87				.client
88				.execute(request)
89				.await?
90				.error_for_status()?
91				.bytes()
92				.await?
93		}
94	};
95
96	// write to the cache if needed
97	if let Some(cache) = write_cache
98		&& needs_cache_write
99	{
100		cache.set(&path, &response);
101	}
102
103	Ok(serde_json::from_slice(&response)?)
104}