tmdb_api/watch_provider/
list.rs1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use crate::client::Executor;
5use crate::common::MediaType;
6
7use super::WatchProvider;
8
9#[derive(Clone, Debug)]
30pub struct WatchProviderList {
31 pub media_type: MediaType,
32 pub watch_region: Option<String>,
34 pub language: Option<String>,
36}
37
38impl WatchProviderList {
39 pub fn new(media_type: MediaType) -> Self {
40 Self {
41 media_type,
42 watch_region: None,
43 language: None,
44 }
45 }
46
47 pub fn with_watch_region(mut self, watch_region: String) -> Self {
48 self.watch_region = Some(watch_region);
49 self
50 }
51
52 pub fn with_language(mut self, language: String) -> Self {
53 self.language = Some(language);
54 self
55 }
56}
57
58#[derive(Clone, Debug, Deserialize, Serialize)]
59pub struct WatchProviderListResult {
60 pub display_priorities: HashMap<String, u64>,
62 #[serde(flatten)]
63 pub inner: WatchProvider,
64}
65
66impl crate::prelude::Command for WatchProviderList {
67 type Output = Vec<WatchProviderListResult>;
68
69 fn path(&self) -> Cow<'static, str> {
70 format!("/watch/providers/{}", self.media_type).into()
71 }
72
73 fn params(&self) -> Vec<(&'static str, Cow<'_, str>)> {
74 let mut params = Vec::new();
75
76 if let Some(watch_region) = self.watch_region.as_ref() {
77 params.push(("watch_region", Cow::Borrowed(watch_region.as_str())));
78 }
79 if let Some(language) = self.language.as_ref() {
80 params.push(("language", Cow::Borrowed(language.as_str())));
81 }
82 params
83 }
84
85 async fn execute<E: Executor>(
86 &self,
87 client: &crate::Client<E>,
88 ) -> Result<Self::Output, crate::error::Error> {
89 #[derive(Deserialize)]
90 struct Result {
91 pub results: Vec<WatchProviderListResult>,
92 }
93
94 client
95 .execute::<Result>(self.path().as_ref(), self.params())
96 .await
97 .map(|res| res.results)
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use mockito::Matcher;
104
105 use crate::client::Client;
106 use crate::client::reqwest::ReqwestExecutor;
107 use crate::common::MediaType;
108 use crate::prelude::Command;
109
110 use super::WatchProviderList;
111
112 #[tokio::test]
113 async fn movie_works() {
114 let mut server = mockito::Server::new_async().await;
115 let client = Client::<ReqwestExecutor>::builder()
116 .with_api_key("secret".into())
117 .with_base_url(server.url())
118 .build()
119 .unwrap();
120 let cmd = WatchProviderList::new(MediaType::Movie);
121
122 let _m = server
123 .mock("GET", "/watch/providers/movie")
124 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
125 .with_status(200)
126 .with_header("content-type", "application/json")
127 .with_body(include_str!("../../assets/watch-provider-movie-list.json"))
128 .create_async()
129 .await;
130 let result = cmd.execute(&client).await.unwrap();
131 assert!(!result.is_empty());
132 }
133
134 #[tokio::test]
135 async fn tv_works() {
136 let mut server = mockito::Server::new_async().await;
137 let client = Client::<ReqwestExecutor>::builder()
138 .with_api_key("secret".into())
139 .with_base_url(server.url())
140 .build()
141 .unwrap();
142 let cmd = WatchProviderList::new(MediaType::Tv);
143
144 let _m = server
145 .mock("GET", "/watch/providers/tv")
146 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
147 .with_status(200)
148 .with_header("content-type", "application/json")
149 .with_body(include_str!("../../assets/watch-provider-tv-list.json"))
150 .create_async()
151 .await;
152 let result = cmd.execute(&client).await.unwrap();
153 assert!(!result.is_empty());
154 }
155
156 #[tokio::test]
157 async fn invalid_api_key() {
158 let mut server = mockito::Server::new_async().await;
159 let client = Client::<ReqwestExecutor>::builder()
160 .with_api_key("secret".into())
161 .with_base_url(server.url())
162 .build()
163 .unwrap();
164 let cmd = WatchProviderList::new(MediaType::Tv);
165
166 let _m = server
167 .mock("GET", "/watch/providers/tv")
168 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
169 .with_status(401)
170 .with_header("content-type", "application/json")
171 .with_body(include_str!("../../assets/invalid-api-key.json"))
172 .create_async()
173 .await;
174 let err = cmd.execute(&client).await.unwrap_err();
175 let server_err = err.as_server_error().unwrap();
176 assert_eq!(server_err.status_code, 7);
177 }
178
179 #[tokio::test]
180 async fn resource_not_found() {
181 let mut server = mockito::Server::new_async().await;
182 let client = Client::<ReqwestExecutor>::builder()
183 .with_api_key("secret".into())
184 .with_base_url(server.url())
185 .build()
186 .unwrap();
187 let cmd = WatchProviderList::new(MediaType::Tv);
188
189 let _m = server
190 .mock("GET", "/watch/providers/tv")
191 .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
192 .with_status(404)
193 .with_header("content-type", "application/json")
194 .with_body(include_str!("../../assets/resource-not-found.json"))
195 .create_async()
196 .await;
197 let err = cmd.execute(&client).await.unwrap_err();
198 let server_err = err.as_server_error().unwrap();
199 assert_eq!(server_err.status_code, 34);
200 }
201}
202
203#[cfg(all(test, feature = "integration"))]
204mod integration_tests {
205 use crate::client::Client;
206 use crate::client::reqwest::ReqwestExecutor;
207 use crate::common::MediaType;
208 use crate::prelude::Command;
209
210 use super::WatchProviderList;
211
212 #[tokio::test]
213 async fn execute_tv() {
214 let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
215 let client = Client::<ReqwestExecutor>::new(secret);
216 let mut cmd = WatchProviderList::new(MediaType::Tv);
217 cmd.language = Some("en-US".into());
218
219 let result = cmd.execute(&client).await.unwrap();
220 assert!(!result.is_empty());
221 }
222
223 #[tokio::test]
224 async fn execute_movie() {
225 let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
226 let client = Client::<ReqwestExecutor>::new(secret);
227 let mut cmd = WatchProviderList::new(MediaType::Movie);
228 cmd.language = Some("en-US".into());
229
230 let result = cmd.execute(&client).await.unwrap();
231 assert!(!result.is_empty());
232 }
233}