tmdb_api/genre/
list.rs

1//! https://developer.themoviedb.org/reference/genre-movie-list
2//! https://developer.themoviedb.org/reference/genre-tv-list
3
4use std::borrow::Cow;
5
6use crate::client::Executor;
7
8use super::Genre;
9
10const TV_PATH: &str = "/genre/tv/list";
11const MOVIE_PATH: &str = "/genre/movie/list";
12
13#[derive(Clone, Debug, Serialize, Deserialize)]
14pub(crate) struct GenreResult {
15    pub genres: Vec<Genre>,
16}
17
18/// Command to list genres
19///
20/// ```rust
21/// use tmdb_api::prelude::Command;
22/// use tmdb_api::Client;
23/// use tmdb_api::client::reqwest::ReqwestExecutor;
24/// use tmdb_api::genre::list::GenreList;
25///
26/// #[tokio::main]
27/// async fn main() {
28///     let client = Client::<ReqwestExecutor>::new("this-is-my-secret-token".into());
29///     let cmd = GenreList::tv();
30///     let result = cmd.execute(&client).await;
31///     match result {
32///         Ok(res) => println!("found: {:#?}", res),
33///         Err(err) => eprintln!("error: {:?}", err),
34///     };
35/// }
36/// ```
37#[derive(Clone, Debug, Default)]
38pub struct GenreList {
39    path: &'static str,
40    /// ISO 639-1 value to display translated data for the fields that support it.
41    pub language: Option<String>,
42}
43
44impl GenreList {
45    pub fn tv() -> Self {
46        Self {
47            path: TV_PATH,
48            language: None,
49        }
50    }
51
52    pub fn movie() -> Self {
53        Self {
54            path: MOVIE_PATH,
55            language: None,
56        }
57    }
58
59    pub fn with_language(mut self, value: Option<String>) -> Self {
60        self.language = value;
61        self
62    }
63}
64
65impl crate::prelude::Command for GenreList {
66    type Output = Vec<Genre>;
67
68    fn path(&self) -> Cow<'static, str> {
69        Cow::Borrowed(self.path)
70    }
71
72    fn params(&self) -> Vec<(&'static str, Cow<'_, str>)> {
73        if let Some(language) = self.language.as_ref() {
74            vec![("language", Cow::Borrowed(language.as_str()))]
75        } else {
76            Vec::new()
77        }
78    }
79
80    async fn execute<E: Executor>(
81        &self,
82        client: &crate::Client<E>,
83    ) -> Result<Self::Output, crate::error::Error> {
84        client
85            .execute::<GenreResult>(self.path().as_ref(), self.params())
86            .await
87            .map(|res| res.genres)
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use mockito::Matcher;
94
95    use crate::client::Client;
96    use crate::client::reqwest::ReqwestExecutor;
97    use crate::prelude::Command;
98
99    use super::GenreList;
100
101    #[tokio::test]
102    async fn movie_works() {
103        let mut server = mockito::Server::new_async().await;
104        let client = Client::<ReqwestExecutor>::builder()
105            .with_api_key("secret".into())
106            .with_base_url(server.url())
107            .build()
108            .unwrap();
109        let cmd = GenreList::movie();
110
111        let _m = server
112            .mock("GET", super::MOVIE_PATH)
113            .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
114            .with_status(200)
115            .with_header("content-type", "application/json")
116            .with_body(include_str!("../../assets/genre-movie-list.json"))
117            .create_async()
118            .await;
119        let result = cmd.execute(&client).await.unwrap();
120        assert!(!result.is_empty());
121    }
122
123    #[tokio::test]
124    async fn tv_works() {
125        let mut server = mockito::Server::new_async().await;
126        let client = Client::<ReqwestExecutor>::builder()
127            .with_api_key("secret".into())
128            .with_base_url(server.url())
129            .build()
130            .unwrap();
131        let cmd = GenreList::tv();
132
133        let _m = server
134            .mock("GET", super::TV_PATH)
135            .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
136            .with_status(200)
137            .with_header("content-type", "application/json")
138            .with_body(include_str!("../../assets/genre-tv-list.json"))
139            .create_async()
140            .await;
141        let result = cmd.execute(&client).await.unwrap();
142        assert!(!result.is_empty());
143    }
144
145    #[tokio::test]
146    async fn invalid_api_key() {
147        let mut server = mockito::Server::new_async().await;
148        let client = Client::<ReqwestExecutor>::builder()
149            .with_api_key("secret".into())
150            .with_base_url(server.url())
151            .build()
152            .unwrap();
153        let cmd = GenreList::tv();
154
155        let _m = server
156            .mock("GET", super::TV_PATH)
157            .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
158            .with_status(401)
159            .with_header("content-type", "application/json")
160            .with_body(include_str!("../../assets/invalid-api-key.json"))
161            .create_async()
162            .await;
163        let err = cmd.execute(&client).await.unwrap_err();
164        let server_err = err.as_server_error().unwrap();
165        assert_eq!(server_err.status_code, 7);
166    }
167
168    #[tokio::test]
169    async fn resource_not_found() {
170        let mut server = mockito::Server::new_async().await;
171        let client = Client::<ReqwestExecutor>::builder()
172            .with_api_key("secret".into())
173            .with_base_url(server.url())
174            .build()
175            .unwrap();
176        let cmd = GenreList::tv();
177
178        let _m = server
179            .mock("GET", super::TV_PATH)
180            .match_query(Matcher::UrlEncoded("api_key".into(), "secret".into()))
181            .with_status(404)
182            .with_header("content-type", "application/json")
183            .with_body(include_str!("../../assets/resource-not-found.json"))
184            .create_async()
185            .await;
186        let err = cmd.execute(&client).await.unwrap_err();
187        let server_err = err.as_server_error().unwrap();
188        assert_eq!(server_err.status_code, 34);
189    }
190}
191
192#[cfg(all(test, feature = "integration"))]
193mod integration_tests {
194    use crate::client::Client;
195    use crate::client::reqwest::ReqwestExecutor;
196    use crate::prelude::Command;
197
198    use super::GenreList;
199
200    #[tokio::test]
201    async fn execute_tv() {
202        let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
203        let client = Client::<ReqwestExecutor>::new(secret);
204        let mut cmd = GenreList::tv();
205        cmd.language = Some("en-US".into());
206
207        let result = cmd.execute(&client).await.unwrap();
208        assert!(!result.is_empty());
209    }
210
211    #[tokio::test]
212    async fn execute_movie() {
213        let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
214        let client = Client::<ReqwestExecutor>::new(secret);
215        let mut cmd = GenreList::movie();
216        cmd.language = Some("en-US".into());
217
218        let result = cmd.execute(&client).await.unwrap();
219        assert!(!result.is_empty());
220    }
221}