tmdb_api/tvshow/
search.rs1use std::borrow::Cow;
2
3const PATH: &str = "/search/tv";
4
5#[derive(Clone, Debug, Default, serde::Serialize)]
6pub struct Params<'a> {
7 #[serde(skip_serializing_if = "Option::is_none")]
10 pub language: Option<Cow<'a, str>>,
11 #[serde(skip_serializing_if = "Option::is_none")]
13 pub page: Option<u32>,
14 #[serde(skip_serializing_if = "crate::util::is_false")]
16 pub include_adult: bool,
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub region: Option<Cow<'a, str>>,
20 #[serde(skip_serializing_if = "Option::is_none")]
23 pub year: Option<u16>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub first_air_date_year: Option<u16>,
27}
28
29impl<'a> Params<'a> {
30 pub fn set_language(&mut self, value: impl Into<Cow<'a, str>>) {
31 self.language = Some(value.into());
32 }
33
34 pub fn with_language(mut self, value: impl Into<Cow<'a, str>>) -> Self {
35 self.set_language(value);
36 self
37 }
38
39 pub fn set_page(&mut self, value: u32) {
40 self.page = Some(value);
41 }
42
43 pub fn with_page(mut self, value: u32) -> Self {
44 self.set_page(value);
45 self
46 }
47
48 pub fn set_include_adult(&mut self, value: bool) {
49 self.include_adult = value;
50 }
51
52 pub fn with_include_adult(mut self, value: bool) -> Self {
53 self.set_include_adult(value);
54 self
55 }
56
57 pub fn set_region(&mut self, value: impl Into<Cow<'a, str>>) {
58 self.region = Some(value.into());
59 }
60
61 pub fn with_region(mut self, value: impl Into<Cow<'a, str>>) -> Self {
62 self.set_region(value);
63 self
64 }
65
66 pub fn set_year(&mut self, value: u16) {
67 self.year = Some(value);
68 }
69
70 pub fn with_year(mut self, value: u16) -> Self {
71 self.set_year(value);
72 self
73 }
74
75 pub fn set_first_air_date_year(&mut self, value: u16) {
76 self.first_air_date_year = Some(value);
77 }
78
79 pub fn with_first_air_date_year(mut self, value: u16) -> Self {
80 self.set_first_air_date_year(value);
81 self
82 }
83}
84
85#[derive(serde::Serialize)]
86struct WithQuery<'a, V> {
87 query: Cow<'a, str>,
88 #[serde(flatten)]
89 inner: V,
90}
91
92impl<E: crate::client::Executor> crate::Client<E> {
93 pub async fn search_tvshows<'a>(
109 &self,
110 query: impl Into<Cow<'a, str>>,
111 params: &Params<'a>,
112 ) -> crate::Result<crate::common::PaginatedResult<super::TVShowShort>> {
113 self.execute(
114 PATH,
115 &WithQuery {
116 query: query.into(),
117 inner: params,
118 },
119 )
120 .await
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use mockito::Matcher;
127
128 use crate::client::Client;
129 use crate::client::reqwest::Client as ReqwestClient;
130
131 #[tokio::test]
132 async fn it_works() {
133 let mut server = mockito::Server::new_async().await;
134 let client = Client::<ReqwestClient>::builder()
135 .with_api_key("secret".into())
136 .with_base_url(server.url())
137 .build()
138 .unwrap();
139
140 let _m = server
141 .mock("GET", super::PATH)
142 .match_query(Matcher::AllOf(vec![
143 Matcher::UrlEncoded("api_key".into(), "secret".into()),
144 Matcher::UrlEncoded("query".into(), "Whatever".into()),
145 ]))
146 .with_status(200)
147 .with_header("content-type", "application/json")
148 .with_body(include_str!("../../assets/search-tv.json"))
149 .create_async()
150 .await;
151 let result = client
152 .search_tvshows("Whatever", &Default::default())
153 .await
154 .unwrap();
155 assert_eq!(result.page, 1);
156 assert!(!result.results.is_empty());
157 assert!(result.total_pages > 0);
158 assert!(result.total_results > 0);
159 let item = result.results.first().unwrap();
160 assert_eq!(item.inner.name, "Game of Thrones");
161 }
162
163 #[tokio::test]
165 async fn fix_issue_25() {
166 let mut server = mockito::Server::new_async().await;
167 let client = Client::<ReqwestClient>::builder()
168 .with_api_key("secret".into())
169 .with_base_url(server.url())
170 .build()
171 .unwrap();
172
173 let _m = server
174 .mock("GET", super::PATH)
175 .match_query(Matcher::AllOf(vec![
176 Matcher::UrlEncoded("api_key".into(), "secret".into()),
177 Matcher::UrlEncoded("query".into(), "rick and morty".into()),
178 ]))
179 .with_status(200)
180 .with_header("content-type", "application/json")
181 .with_body(include_str!("../../assets/search-tv-rick-and-morty.json"))
182 .create_async()
183 .await;
184 let result = client
185 .search_tvshows("rick and morty", &Default::default())
186 .await
187 .unwrap();
188 assert_eq!(result.page, 1);
189 assert_eq!(result.total_pages, 1);
190 assert_eq!(result.total_results, 2);
191 let item = result.results.first().unwrap();
192 assert_eq!(item.inner.name, "Rick and Morty");
193 }
194
195 #[tokio::test]
196 async fn invalid_api_key() {
197 let mut server = mockito::Server::new_async().await;
198 let client = Client::<ReqwestClient>::builder()
199 .with_api_key("secret".into())
200 .with_base_url(server.url())
201 .build()
202 .unwrap();
203
204 let _m = server
205 .mock("GET", super::PATH)
206 .match_query(Matcher::AllOf(vec![
207 Matcher::UrlEncoded("api_key".into(), "secret".into()),
208 Matcher::UrlEncoded("query".into(), "Whatever".into()),
209 ]))
210 .with_status(401)
211 .with_header("content-type", "application/json")
212 .with_body(include_str!("../../assets/invalid-api-key.json"))
213 .create_async()
214 .await;
215 let err = client
216 .search_tvshows("Whatever", &Default::default())
217 .await
218 .unwrap_err();
219 let server_err = err.as_server_error().unwrap();
220 assert_eq!(server_err.status_code, 7);
221 }
222
223 #[tokio::test]
224 async fn resource_not_found() {
225 let mut server = mockito::Server::new_async().await;
226 let client = Client::<ReqwestClient>::builder()
227 .with_api_key("secret".into())
228 .with_base_url(server.url())
229 .build()
230 .unwrap();
231 let _m = server
232 .mock("GET", super::PATH)
233 .match_query(Matcher::AllOf(vec![
234 Matcher::UrlEncoded("api_key".into(), "secret".into()),
235 Matcher::UrlEncoded("query".into(), "Whatever".into()),
236 ]))
237 .with_status(404)
238 .with_header("content-type", "application/json")
239 .with_body(include_str!("../../assets/resource-not-found.json"))
240 .create_async()
241 .await;
242 let err = client
243 .search_tvshows("Whatever", &Default::default())
244 .await
245 .unwrap_err();
246 let server_err = err.as_server_error().unwrap();
247 assert_eq!(server_err.status_code, 34);
248 }
249}
250
251#[cfg(all(test, feature = "integration"))]
252mod integration_tests {
253 use crate::client::Client;
254 use crate::client::reqwest::Client as ReqwestClient;
255
256 #[tokio::test]
257 async fn search_simpsons() {
258 let secret = std::env::var("TMDB_TOKEN_V3").unwrap();
259 let client = Client::<ReqwestClient>::new(secret);
260 let result = client
261 .search_tvshows("simpsons", &Default::default())
262 .await
263 .unwrap();
264 assert_eq!(result.page, 1);
265 assert!(result.results.len() > 1);
266 assert!(result.total_pages > 0);
267 assert!(result.total_results > 1);
268 let item = result.results.first().unwrap();
269 assert_eq!(item.inner.name, "The Simpsons");
270 }
271}