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