1use super::*;
2use crate::common::SearchSuggestion;
3use crate::parse::SearchResults;
4pub use filteredsearch::*;
5use std::borrow::Cow;
6
7pub mod filteredsearch;
8
9const SPECIALIZED_PLAYLIST_EXACT_MATCH_PARAMS: &str = "BagwQDhAKEAMQBBAJEAU%3D";
10const SPECIALIZED_PLAYLIST_WITH_SUGGESTIONS_PARAMS: &str = "BQgIIAWoMEA4QChADEAQQCRAF";
11const SPECIALIZED_PLAYLIST_PREFIX_PARAMS: &str = "EgeKAQQoA";
12const SEARCH_QUERY_PATH: &str = "search";
13
14pub trait SearchType: Default {
18 fn specialised_params(&self, spelling_mode: &SpellingMode) -> Option<Cow<'_, str>>;
19}
20
21pub trait UnfilteredSearchType: SearchType {}
24
25#[derive(PartialEq, Debug, Clone)]
27pub struct SearchQuery<'a, S: SearchType> {
28 query: Cow<'a, str>,
29 spelling_mode: SpellingMode,
30 search_type: S,
31}
32
33#[derive(PartialEq, Debug, Clone, Default)]
37pub enum SpellingMode {
38 #[default]
41 ExactMatch,
42 WithSuggestions,
43}
44
45#[derive(Default, Debug, Clone, PartialEq)]
47pub struct BasicSearch;
48#[derive(Default, Debug, Clone, PartialEq)]
50pub struct LibrarySearch;
51#[derive(Default, Debug, Clone, PartialEq)]
53pub struct UploadSearch;
54
55impl SearchType for BasicSearch {
56 fn specialised_params(&self, spelling_mode: &SpellingMode) -> Option<Cow<'_, str>> {
57 match spelling_mode {
58 SpellingMode::ExactMatch => Some("EhGKAQ4IARABGAEgASgAOAFAAUICCAE%3D".into()),
59 SpellingMode::WithSuggestions => None,
60 }
61 }
62}
63impl SearchType for UploadSearch {
64 fn specialised_params(&self, _: &SpellingMode) -> Option<Cow<'_, str>> {
65 Some("agIYAw%3D%3D".into())
67 }
68}
69impl SearchType for LibrarySearch {
70 fn specialised_params(&self, _: &SpellingMode) -> Option<Cow<'_, str>> {
71 Some("agIYBA%3D%3D".into())
74 }
75}
76
77impl UnfilteredSearchType for BasicSearch {}
78impl UnfilteredSearchType for UploadSearch {}
79impl UnfilteredSearchType for LibrarySearch {}
80
81impl<S: UnfilteredSearchType, A: AuthToken> Query<A> for SearchQuery<'_, S> {
82 type Output = SearchResults;
83 type Method = PostMethod;
84}
85impl<S: UnfilteredSearchType> PostQuery for SearchQuery<'_, S> {
86 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
87 search_query_header(self)
88 }
89 fn path(&self) -> &str {
90 SEARCH_QUERY_PATH
91 }
92 fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
93 vec![]
94 }
95}
96
97impl<'a, Q: Into<Cow<'a, str>>, S: SearchType> From<Q> for SearchQuery<'a, S> {
100 fn from(value: Q) -> SearchQuery<'a, S> {
101 SearchQuery {
102 query: value.into(),
103 spelling_mode: SpellingMode::default(),
104 search_type: S::default(),
105 }
106 }
107}
108
109impl<'a> SearchQuery<'a, BasicSearch> {
111 #[deprecated = "To be removed in future release - see issue #353"]
112 pub fn new<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, BasicSearch> {
113 SearchQuery {
114 query: q.into(),
115 spelling_mode: SpellingMode::default(),
116 search_type: BasicSearch {},
117 }
118 }
119}
120
121impl<'a, S: SearchType> SearchQuery<'a, S> {
122 pub fn with_spelling_mode(mut self, spelling_mode: SpellingMode) -> Self {
124 self.spelling_mode = spelling_mode;
125 self
126 }
127 pub fn with_query<Q: Into<Cow<'a, str>>>(mut self, query: Q) -> Self {
129 self.query = query.into();
130 self
131 }
132}
133
134impl<'a> SearchQuery<'a, BasicSearch> {
135 #[deprecated = "To be removed in future release - see issue #353"]
137 pub fn with_filter<F: FilteredSearchType>(
138 self,
139 filter: F,
140 ) -> SearchQuery<'a, FilteredSearch<F>> {
141 SearchQuery {
142 query: self.query,
143 spelling_mode: self.spelling_mode,
144 search_type: FilteredSearch { filter },
145 }
146 }
147 #[deprecated = "To be removed in future release - see issue #353"]
149 pub fn uploads(self) -> SearchQuery<'a, UploadSearch> {
150 SearchQuery {
151 query: self.query,
152 spelling_mode: self.spelling_mode,
153 search_type: UploadSearch,
154 }
155 }
156 #[deprecated = "To be removed in future release - see issue #353"]
158 pub fn library(self) -> SearchQuery<'a, LibrarySearch> {
159 SearchQuery {
160 query: self.query,
161 spelling_mode: self.spelling_mode,
162 search_type: LibrarySearch,
163 }
164 }
165}
166
167impl<'a, F: FilteredSearchType> SearchQuery<'a, FilteredSearch<F>> {
168 pub fn new_filtered<Q: Into<Cow<'a, str>>>(
169 q: Q,
170 filter: F,
171 ) -> SearchQuery<'a, FilteredSearch<F>> {
172 SearchQuery {
173 query: q.into(),
174 spelling_mode: SpellingMode::default(),
175 search_type: FilteredSearch { filter },
176 }
177 }
178 pub fn with_filter<F2: FilteredSearchType>(
180 self,
181 filter: F2,
182 ) -> SearchQuery<'a, FilteredSearch<F2>> {
183 SearchQuery {
184 query: self.query,
185 spelling_mode: self.spelling_mode,
186 search_type: FilteredSearch { filter },
187 }
188 }
189 #[deprecated = "To be removed in future release - see issue #353"]
191 pub fn unfiltered(self) -> SearchQuery<'a, BasicSearch> {
192 SearchQuery {
193 query: self.query,
194 spelling_mode: self.spelling_mode,
195 search_type: BasicSearch,
196 }
197 }
198}
199
200impl<'a> SearchQuery<'a, UploadSearch> {
201 pub fn new_uploads<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, UploadSearch> {
203 SearchQuery {
204 query: q.into(),
205 spelling_mode: SpellingMode::default(),
206 search_type: UploadSearch,
207 }
208 }
209 pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
211 SearchQuery {
212 query: self.query,
213 spelling_mode: self.spelling_mode,
214 search_type: BasicSearch,
215 }
216 }
217}
218impl<'a> SearchQuery<'a, LibrarySearch> {
219 pub fn new_library<Q: Into<Cow<'a, str>>>(q: Q) -> SearchQuery<'a, LibrarySearch> {
221 SearchQuery {
222 query: q.into(),
223 spelling_mode: SpellingMode::default(),
224 search_type: LibrarySearch,
225 }
226 }
227 pub fn with_scope_public(self) -> SearchQuery<'a, BasicSearch> {
229 SearchQuery {
230 query: self.query,
231 spelling_mode: self.spelling_mode,
232 search_type: BasicSearch,
233 }
234 }
235}
236
237#[derive(PartialEq, Debug, Clone)]
238pub struct GetSearchSuggestionsQuery<'a> {
239 query: Cow<'a, str>,
240}
241
242impl<'a> GetSearchSuggestionsQuery<'a> {
243 pub fn new<S: Into<Cow<'a, str>>>(value: S) -> GetSearchSuggestionsQuery<'a> {
244 GetSearchSuggestionsQuery {
245 query: value.into(),
246 }
247 }
248}
249
250impl<'a, S: Into<Cow<'a, str>>> From<S> for GetSearchSuggestionsQuery<'a> {
251 fn from(value: S) -> GetSearchSuggestionsQuery<'a> {
252 GetSearchSuggestionsQuery::new(value)
253 }
254}
255
256impl<A: AuthToken> Query<A> for GetSearchSuggestionsQuery<'_> {
257 type Output = Vec<SearchSuggestion>;
258 type Method = PostMethod;
259}
260impl PostQuery for GetSearchSuggestionsQuery<'_> {
261 fn header(&self) -> serde_json::Map<String, serde_json::Value> {
262 let value = self.query.as_ref().into();
263 serde_json::Map::from_iter([("input".into(), value)])
264 }
265 fn path(&self) -> &str {
266 "music/get_search_suggestions"
267 }
268 fn params(&self) -> Vec<(&str, Cow<'_, str>)> {
269 vec![]
270 }
271}
272
273fn search_query_header<S: SearchType>(
274 query: &SearchQuery<S>,
275) -> serde_json::Map<String, serde_json::Value> {
276 let value = query.query.as_ref().into();
277 let params = search_query_params(query);
278 if let Some(params) = params {
279 serde_json::Map::from_iter([
280 ("query".to_string(), value),
281 ("params".to_string(), params.into()),
282 ])
283 } else {
284 serde_json::Map::from_iter([("query".to_string(), value)])
285 }
286}
287fn search_query_params<'a, S: SearchType>(query: &'a SearchQuery<'a, S>) -> Option<Cow<'a, str>> {
288 query.search_type.specialised_params(&query.spelling_mode)
289}