1use serde::{Deserialize, Serialize, ser::SerializeStruct};
2
3extern crate alloc;
4use alloc::{borrow::Cow, string::String, vec::Vec};
5
6#[derive(Debug, Clone, PartialEq)]
11pub struct Filter {
12 pub id: Cow<'static, str>,
13 pub title: Option<Cow<'static, str>>,
14 pub hide_from_header: Option<bool>,
15 pub kind: FilterKind,
16}
17#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct SortFilterDefault {
20 pub index: i32,
21 pub ascending: bool,
22}
23
24#[derive(Debug, Clone, PartialEq)]
26pub enum FilterKind {
27 Text {
29 placeholder: Option<Cow<'static, str>>,
30 },
31 Sort {
33 can_ascend: bool,
34 options: Vec<Cow<'static, str>>,
35 default: Option<SortFilterDefault>,
36 },
37 Check {
39 name: Option<Cow<'static, str>>,
40 can_exclude: bool,
41 default: Option<bool>,
42 },
43 Select {
45 is_genre: bool,
46 uses_tag_style: bool,
47 options: Vec<Cow<'static, str>>,
48 ids: Option<Vec<Cow<'static, str>>>,
49 default: Option<Cow<'static, str>>,
50 },
51 MultiSelect {
53 is_genre: bool,
54 can_exclude: bool,
55 uses_tag_style: bool,
56 options: Vec<Cow<'static, str>>,
57 ids: Option<Vec<Cow<'static, str>>>,
58 default_included: Option<Vec<Cow<'static, str>>>,
59 default_excluded: Option<Vec<Cow<'static, str>>>,
60 },
61 Note(Cow<'static, str>),
63 Range {
65 min: Option<f32>,
66 max: Option<f32>,
67 decimal: bool,
68 },
69}
70
71impl Filter {
72 pub fn note<A: Into<Cow<'static, str>>>(text: A) -> Self {
74 Self {
75 id: Cow::Borrowed("note"),
76 title: None,
77 hide_from_header: None,
78 kind: FilterKind::Note(text.into()),
79 }
80 }
81}
82
83macro_rules! create_filter_struct {
84 (
85 $struct_name:ident,
86 $filter_kind:ident,
87 $doc_comment:expr,
88 { $($(#[$field_meta:meta])* $field_name:ident: $field_type:ty),* $(,)? },
89 { $($def_field_name:ident: $default_value:expr),* $(,)? }
90 ) => {
91 #[doc = $doc_comment]
92 #[derive(Debug, Clone, PartialEq)]
95 pub struct $struct_name {
96 pub id: Cow<'static, str>,
98 pub title: Option<Cow<'static, str>>,
100 pub hide_from_header: Option<bool>,
102 $(
103 $(#[$field_meta])*
104 pub $field_name: $field_type
105 ),*
106 }
107
108 impl From<$struct_name> for Filter {
109 fn from(value: $struct_name) -> Filter {
110 Filter {
111 id: value.id,
112 title: value.title,
113 hide_from_header: value.hide_from_header,
114 kind: FilterKind::$filter_kind {
115 $($field_name: value.$field_name),*
116 },
117 }
118 }
119 }
120
121 impl Default for $struct_name {
122 fn default() -> Self {
123 Self {
124 id: Cow::Borrowed(stringify!($struct_name)),
125 title: None,
126 hide_from_header: None,
127 $($def_field_name: $default_value),*
128 }
129 }
130 }
131 };
132}
133
134create_filter_struct!(
135 TextFilter,
136 Text,
137 "A text field.",
138 {
139 placeholder: Option<Cow<'static, str>>,
141 },
142 {
143 placeholder: None,
144 }
145);
146
147create_filter_struct!(
148 SortFilter,
149 Sort,
150 "A list of sort options.",
151 {
152 can_ascend: bool,
154 options: Vec<Cow<'static, str>>,
156 default: Option<SortFilterDefault>,
158 },
159 {
160 can_ascend: true,
161 options: Vec::new(),
162 default: None,
163 }
164);
165
166create_filter_struct!(
167 CheckFilter,
168 Check,
169 "A checkbox.",
170 {
171 name: Option<Cow<'static, str>>,
173 can_exclude: bool,
175 default: Option<bool>,
177 },
178 {
179 name: None,
180 can_exclude: false,
181 default: None,
182 }
183);
184
185create_filter_struct!(
186 SelectFilter,
187 Select,
188 "A list of values that allows a single selection.",
189 {
190 is_genre: bool,
192 uses_tag_style: bool,
194 options: Vec<Cow<'static, str>>,
196 ids: Option<Vec<Cow<'static, str>>>,
198 default: Option<Cow<'static, str>>,
200 },
201 {
202 is_genre: false,
203 uses_tag_style: false,
204 options: Vec::new(),
205 ids: None,
206 default: None,
207 }
208);
209
210create_filter_struct!(
211 MultiSelectFilter,
212 MultiSelect,
213 "A list of values that allows multiple selections.",
214 {
215 is_genre: bool,
217 can_exclude: bool,
219 uses_tag_style: bool,
221 options: Vec<Cow<'static, str>>,
223 ids: Option<Vec<Cow<'static, str>>>,
225 default_included: Option<Vec<Cow<'static, str>>>,
226 default_excluded: Option<Vec<Cow<'static, str>>>,
227 },
228 {
229 is_genre: false,
230 can_exclude: false,
231 uses_tag_style: false,
232 options: Vec::new(),
233 ids: None,
234 default_included: None,
235 default_excluded: None,
236 }
237);
238
239create_filter_struct!(
240 RangeFilter,
241 Range,
242 "A range filter.",
243 {
244 min: Option<f32>,
246 max: Option<f32>,
248 decimal: bool,
250 },
251 {
252 min: None,
253 max: None,
254 decimal: false,
255 }
256);
257
258impl FilterKind {
259 fn raw_value(&self) -> &str {
260 match self {
261 FilterKind::Text { .. } => "text",
262 FilterKind::Sort { .. } => "sort",
263 FilterKind::Check { .. } => "check",
264 FilterKind::Select { .. } => "select",
265 FilterKind::MultiSelect { .. } => "multi-select",
266 FilterKind::Note(_) => "note",
267 FilterKind::Range { .. } => "range",
268 }
269 }
270}
271
272impl Serialize for Filter {
273 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
274 where
275 S: serde::Serializer,
276 {
277 let mut state = serializer.serialize_struct("Filter", 0)?;
278 state.serialize_field("id", &Some(self.id.clone()))?;
279 state.serialize_field("title", &self.title)?;
280 state.serialize_field("hide_from_header", &self.hide_from_header)?;
281 state.serialize_field("type", &self.kind.raw_value())?;
282 match &self.kind {
283 FilterKind::Text { placeholder } => {
284 state.serialize_field("placeholder", &placeholder)?
285 }
286 FilterKind::Sort {
287 can_ascend,
288 options,
289 default,
290 } => {
291 state.serialize_field("can_ascend", &Some(can_ascend))?;
292 state.serialize_field("options", &options)?;
293 state.serialize_field("default", &default)?
294 }
295 FilterKind::Check {
296 name,
297 can_exclude,
298 default,
299 } => {
300 state.serialize_field("name", &name)?;
301 state.serialize_field("can_exclude", &Some(can_exclude))?;
302 state.serialize_field("default", &default)?
303 }
304 FilterKind::Select {
305 is_genre,
306 uses_tag_style,
307 options,
308 ids,
309 default,
310 } => {
311 state.serialize_field("is_genre", &Some(is_genre))?;
312 state.serialize_field("uses_tag_style", &Some(uses_tag_style))?;
313 state.serialize_field("options", &options)?;
314 state.serialize_field("ids", &ids)?;
315 state.serialize_field("default", &default)?
316 }
317 FilterKind::MultiSelect {
318 is_genre,
319 can_exclude,
320 uses_tag_style,
321 options,
322 ids,
323 default_included,
324 default_excluded,
325 } => {
326 state.serialize_field("is_genre", &Some(is_genre))?;
327 state.serialize_field("can_exclude", &Some(can_exclude))?;
328 state.serialize_field("uses_tag_style", &Some(uses_tag_style))?;
329 state.serialize_field("options", &options)?;
330 state.serialize_field("ids", &ids)?;
331 state.serialize_field("default_included", &default_included)?;
332 state.serialize_field("default_excluded", &default_excluded)?;
333 }
334 FilterKind::Note(text) => state.serialize_field("text", &text)?,
335 FilterKind::Range { min, max, decimal } => {
336 state.serialize_field("min", &min)?;
337 state.serialize_field("max", &max)?;
338 state.serialize_field("decimal", &Some(decimal))?;
339 }
340 };
341 state.end()
342 }
343}
344
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
347pub enum FilterValue {
348 Text {
350 id: String,
352 value: String,
354 },
355 Sort {
357 id: String,
359 index: i32,
361 ascending: bool,
363 },
364 Check {
366 id: String,
368 value: i32,
370 },
371 Select {
373 id: String,
375 value: String,
377 },
378 MultiSelect {
380 id: String,
382 included: Vec<String>,
384 excluded: Vec<String>,
386 },
387 Range {
389 id: String,
391 from: Option<f32>,
393 to: Option<f32>,
395 },
396}