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