1use serde::Serializer;
6
7use crate::smo::Item;
8
9bitflags::bitflags! {
10 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
11 pub struct SearchType: u8 {
12 const MOVIE = 0b0000_0001;
13 const SHOW = 0b0000_0010;
14 const EPISODE = 0b0000_0100;
15 const PERSON = 0b0000_1000;
16 const LIST = 0b0001_0000;
17 }
18}
19
20impl serde::Serialize for SearchType {
21 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
22 const FLAGS: [&str; 5] = ["movie", "show", "episode", "person", "list"];
23
24 if self.is_empty() {
25 serializer.serialize_none()
26 } else if self.bits().count_ones() == 1 {
27 let idx = self.bits().trailing_zeros() as usize;
31 serializer.serialize_str(FLAGS[idx])
32 } else {
33 let iter = self.iter().map(|flag| {
38 let idx = flag.bits().trailing_zeros() as usize;
39 FLAGS[idx]
40 });
41
42 let joined = iter.collect::<Vec<_>>().join(",");
44
45 serializer.serialize_str(&joined)
46 }
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, serde::Deserialize)]
51pub struct SearchResult {
52 #[serde(flatten)]
53 pub item: Item,
54 pub score: Option<f64>,
55}
56
57pub mod text_query {
58 use trakt_core::{Pagination, PaginationResponse};
63
64 use super::{SearchResult, SearchType};
65
66 #[derive(Debug, Clone, PartialEq, Eq, Hash, trakt_macros::Request)]
67 #[trakt(
68 response = Response,
69 endpoint = "/search/{tp}"
70 )]
71 pub struct Request {
72 pub tp: SearchType,
73 pub query: String,
74 #[serde(flatten)]
75 pub pagination: Pagination,
76 }
77
78 #[derive(Debug, Clone, PartialEq, trakt_macros::Response)]
79 pub struct Response {
80 #[trakt(pagination)]
81 pub items: PaginationResponse<SearchResult>,
82 }
83}
84
85pub mod id_lookup {
86 use bytes::BufMut;
91 use serde::Serialize;
92 use trakt_core::{error::IntoHttpError, Context, Metadata, Pagination, PaginationResponse};
93
94 use super::{SearchResult, SearchType};
95 use crate::smo::Id;
96
97 #[derive(Debug, Clone, Eq, PartialEq, Hash)]
98 pub struct Request {
99 pub id: Id,
100 pub tp: SearchType,
101 pub pagination: Pagination,
102 }
103
104 #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
105 struct RequestPathParams {
106 id_type: &'static str,
107 id: Id,
108 }
109
110 #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize)]
111 struct RequestQueryParams {
112 #[serde(rename = "type")]
113 tp: SearchType,
114 #[serde(flatten)]
115 pagination: Pagination,
116 }
117
118 impl TryFrom<Request> for (RequestPathParams, RequestQueryParams) {
119 type Error = IntoHttpError;
120
121 fn try_from(value: Request) -> Result<Self, Self::Error> {
122 Ok((
123 RequestPathParams {
124 id_type: match &value.id {
125 Id::Trakt(_) => "trakt",
126 Id::Slug(_) => {
127 return Err(IntoHttpError::Validation(String::from(
128 "Slug IDs are not supported",
129 )));
130 }
131 Id::Tvdb(_) => "tvdb",
132 Id::Imdb(_) => "imdb",
133 Id::Tmdb(_) => "tmdb",
134 },
135 id: value.id,
136 },
137 RequestQueryParams {
138 tp: value.tp,
139 pagination: value.pagination,
140 },
141 ))
142 }
143 }
144
145 impl trakt_core::Request for Request {
146 type Response = Response;
147 const METADATA: Metadata = Metadata {
148 endpoint: "/search/{id_type}/{id}",
149 method: http::Method::GET,
150 auth: trakt_core::AuthRequirement::None,
151 };
152
153 fn try_into_http_request<T: Default + BufMut>(
154 self,
155 ctx: Context,
156 ) -> Result<http::Request<T>, IntoHttpError> {
157 let (path, query) = self.try_into()?;
158 trakt_core::construct_req(&ctx, &Self::METADATA, &path, &query, T::default())
159 }
160 }
161
162 #[derive(Debug, Clone, PartialEq, trakt_macros::Response)]
163 pub struct Response {
164 #[trakt(pagination)]
165 pub items: PaginationResponse<SearchResult>,
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use trakt_core::{construct_url, error::IntoHttpError, Context, Pagination, Request};
172
173 use super::*;
174 use crate::{smo::Id, test::assert_request};
175
176 const CTX: Context = Context {
177 base_url: "https://api.trakt.tv",
178 client_id: "client_id",
179 oauth_token: None,
180 };
181
182 #[test]
183 fn test_type_ser() {
184 let tp = SearchType::MOVIE;
185 assert_eq!(serde_json::to_string(&tp).unwrap(), r#""movie""#);
186
187 let tp = SearchType::MOVIE | SearchType::SHOW;
188 assert_eq!(serde_json::to_string(&tp).unwrap(), r#""movie,show""#);
189
190 let tp = SearchType::empty();
191 assert_eq!(serde_json::to_string(&tp).unwrap(), "null");
192 }
193
194 #[test]
195 fn test_type_ser_url() {
196 #[derive(Debug, serde::Serialize)]
197 struct Test {
198 tp: SearchType,
199 }
200
201 let test = Test {
202 tp: SearchType::MOVIE,
203 };
204 let url = construct_url("", "/search/{tp}", &test, &()).unwrap();
205 assert_eq!(url, "/search/movie");
206
207 let test = Test {
208 tp: SearchType::MOVIE | SearchType::SHOW,
209 };
210 let url = construct_url("", "/search/{tp}", &test, &()).unwrap();
211 assert_eq!(url, "/search/movie,show");
212
213 let test = Test {
214 tp: SearchType::empty(),
215 };
216 let url = construct_url("", "/search/{tp}", &test, &()).unwrap();
217 assert_eq!(url, "/search/");
218 }
219
220 #[test]
221 fn test_id_lookup_request() {
222 let req = id_lookup::Request {
223 id: Id::Trakt(1),
224 tp: SearchType::MOVIE,
225 pagination: Pagination::default(),
226 };
227 assert_request(
228 CTX,
229 req,
230 "https://api.trakt.tv/search/trakt/1?type=movie&page=1&limit=10",
231 "",
232 );
233
234 let req = id_lookup::Request {
235 id: Id::Tvdb(1),
236 tp: SearchType::EPISODE | SearchType::SHOW,
237 pagination: Pagination::default(),
238 };
239 assert_request(
240 CTX,
241 req,
242 "https://api.trakt.tv/search/tvdb/1?type=show%2Cepisode&page=1&limit=10",
243 "",
244 );
245
246 let req = id_lookup::Request {
247 id: Id::Imdb("tt12345".into()),
248 tp: SearchType::empty(),
249 pagination: Pagination::default(),
250 };
251 assert_request(
252 CTX,
253 req,
254 "https://api.trakt.tv/search/imdb/tt12345?page=1&limit=10",
255 "",
256 );
257
258 let req = id_lookup::Request {
259 id: Id::Slug("slug".into()),
260 tp: SearchType::PERSON,
261 pagination: Pagination::default(),
262 };
263 assert!(matches!(
264 req.try_into_http_request::<Vec<u8>>(CTX),
265 Err(IntoHttpError::Validation(_))
266 ));
267 }
268}