tmdb_api/watch_provider/
list.rs

1use std::borrow::Cow;
2use std::collections::HashMap;
3
4use crate::client::Executor;
5use crate::common::MediaType;
6
7use super::WatchProvider;
8
9/// Command to get the details of a collection
10///
11/// ```rust
12/// use tmdb_api::prelude::Command;
13/// use tmdb_api::Client;
14/// use tmdb_api::client::reqwest::ReqwestExecutor;
15/// use tmdb_api::watch_provider::list::WatchProviderList;
16///
17/// #[tokio::main]
18/// async fn main() {
19///     use tmdb_api::common::MediaType;
20///     let client = Client::<ReqwestExecutor>::new("this-is-my-secret-token".into());
21///     let cmd = WatchProviderList::new(MediaType::Tv);
22///     let result = cmd.execute(&client).await;
23///     match result {
24///         Ok(res) => println!("found: {:#?}", res),
25///         Err(err) => eprintln!("error: {:?}", err),
26///     };
27/// }
28/// ```
29#[derive(Clone, Debug)]
30pub struct WatchProviderList {
31    pub media_type: MediaType,
32    /// ISO 3166-1 alpha-2 value to filter the results for one country.
33    pub watch_region: Option<String>,
34    /// ISO 639-1 value to display translated data for the fields that support it.
35    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    /// A hash map of display priority by country code
61    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}