1pub mod prelude;
2
3pub use reqwest;
4#[cfg(feature = "reqwest-middleware")]
5pub use reqwest_middleware;
6
7mod reqwest_impl;
8
9use std::borrow::Cow;
10
11pub use self::prelude::Executor;
12pub type ReqwestClient = Client<::reqwest::Client>;
13
14const BASE_URL: &str = "https://api.themoviedb.org/3";
15
16#[derive(Debug, thiserror::Error)]
17pub enum ClientBuilderError {
18 #[error("missing api key")]
19 MissingApiKey,
20}
21
22pub struct ClientBuilder<E: prelude::Executor> {
23 base_url: Cow<'static, str>,
24 executor: Option<E>,
25 api_key: Option<String>,
26}
27
28impl<E: prelude::Executor> Default for ClientBuilder<E> {
29 fn default() -> Self {
30 Self {
31 base_url: Cow::Borrowed(BASE_URL),
32 executor: None,
33 api_key: None,
34 }
35 }
36}
37
38impl<E: prelude::Executor> ClientBuilder<E> {
39 pub fn with_base_url<U: Into<Cow<'static, str>>>(mut self, value: U) -> Self {
40 self.base_url = value.into();
41 self
42 }
43
44 pub fn set_base_url<U: Into<Cow<'static, str>>>(&mut self, value: U) {
45 self.base_url = value.into();
46 }
47
48 pub fn with_executor(mut self, executor: E) -> Self {
49 self.executor = Some(executor);
50 self
51 }
52
53 pub fn set_executor(mut self, executor: E) {
54 self.executor = Some(executor);
55 }
56
57 pub fn with_api_key(mut self, value: String) -> Self {
58 self.api_key = Some(value);
59 self
60 }
61
62 pub fn set_api_key(mut self, value: String) {
63 self.api_key = Some(value);
64 }
65
66 pub fn build(self) -> Result<Client<E>, ClientBuilderError> {
67 let base_url = self.base_url;
68 let executor = self.executor.unwrap_or_default();
69 let api_key = self.api_key.ok_or(ClientBuilderError::MissingApiKey)?;
70
71 Ok(Client {
72 executor,
73 base_url,
74 api_key,
75 })
76 }
77}
78
79#[derive(Serialize)]
80struct WithApiKey<'a, V> {
81 api_key: &'a str,
82 #[serde(flatten)]
83 inner: V,
84}
85
86pub struct Client<E> {
95 executor: E,
96 base_url: Cow<'static, str>,
97 api_key: String,
98}
99
100impl<E: std::fmt::Debug> std::fmt::Debug for Client<E> {
101 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
102 f.debug_struct(stringify!(Client))
103 .field("executor", &self.executor)
104 .field("base_url", &self.base_url)
105 .field("api_key", &"REDACTED")
106 .finish()
107 }
108}
109
110impl<E: Executor> Client<E> {
111 pub fn builder() -> ClientBuilder<E> {
112 ClientBuilder::default()
113 }
114
115 pub fn new(api_key: String) -> Self {
116 Self {
117 executor: E::default(),
118 base_url: Cow::Borrowed(BASE_URL),
119 api_key,
120 }
121 }
122
123 pub fn base_url(&self) -> &str {
124 &self.base_url
125 }
126
127 pub async fn execute<T: serde::de::DeserializeOwned, P: serde::Serialize>(
139 &self,
140 path: &str,
141 params: &P,
142 ) -> Result<T, crate::error::Error> {
143 let url = format!("{}{}", self.base_url, path);
144 self.executor
145 .execute(
146 &url,
147 WithApiKey {
148 api_key: &self.api_key,
149 inner: params,
150 },
151 )
152 .await
153 }
154}