flams_backend_types/
archive_json.rs

1use ftml_uris::{ArchiveId, ArchiveUri, DocumentUri, UriWithArchive, errors::UriParseError};
2
3#[derive(Debug, Clone)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5#[cfg_attr(feature = "serde", serde(untagged))]
6pub enum ArchiveDatum {
7    Document(DocumentKind),
8    Institution(Institution),
9}
10
11#[derive(Debug, Clone)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "serde", serde(tag = "type"))]
14pub enum DocumentKind {
15    #[cfg_attr(feature = "serde", serde(rename = "library"))]
16    Library {
17        title: Box<str>,
18        teaser: Option<Box<str>>,
19        thumbnail: Option<Box<str>>,
20    },
21    #[cfg_attr(feature = "serde", serde(rename = "book"))]
22    Book {
23        title: Box<str>,
24        #[cfg_attr(feature = "serde", serde(default))]
25        authors: Vec<Person>,
26        file: Box<str>,
27        thumbnail: Option<Box<str>>,
28        teaser: Option<Box<str>>,
29    },
30    #[cfg_attr(feature = "serde", serde(rename = "paper"))]
31    Paper {
32        title: Box<str>,
33        #[cfg_attr(feature = "serde", serde(default))]
34        authors: Vec<Person>,
35        file: Box<str>,
36        thumbnail: Option<Box<str>>,
37        teaser: Option<Box<str>>,
38        venue: Option<Box<str>>,
39        venue_url: Option<Box<str>>,
40    },
41    #[cfg_attr(feature = "serde", serde(rename = "course"))]
42    Course {
43        title: Box<str>,
44        landing: Box<str>,
45        acronym: Option<Box<str>>,
46        #[cfg_attr(feature = "serde", serde(default))]
47        authors: Vec<Person>,
48        institution: Option<Box<str>>,
49        notes: Box<str>,
50        slides: Option<Box<str>>,
51        thumbnail: Option<Box<str>>,
52        //#[cfg_attr(feature = "serde", serde(default))]
53        //quizzes: bool,
54        //#[cfg_attr(feature = "serde", serde(default))]
55        //homeworks: bool,
56        //#[cfg_attr(feature = "serde", serde(default))]
57        //instances: Vec<PreInstance>,
58        teaser: Option<Box<str>>,
59    },
60    #[cfg_attr(feature = "serde", serde(rename = "self-study"))]
61    SelfStudy {
62        title: Box<str>,
63        landing: Box<str>,
64        #[cfg_attr(feature = "serde", serde(default))]
65        authors: Vec<Person>,
66        acronym: Option<Box<str>>,
67        notes: Box<str>,
68        slides: Option<Box<str>>,
69        teaser: Option<Box<str>>,
70        thumbnail: Option<Box<str>>,
71    },
72}
73impl DocumentKind {
74    #[inline]
75    #[must_use]
76    pub fn teaser(&self) -> Option<&str> {
77        match self {
78            Self::Library { teaser, .. }
79            | Self::Book { teaser, .. }
80            | Self::Paper { teaser, .. }
81            | Self::Course { teaser, .. }
82            | Self::SelfStudy { teaser, .. } => teaser.as_deref(),
83        }
84    }
85    pub fn set_teaser(&mut self, new_teaser: Box<str>) {
86        match self {
87            Self::Library { teaser, .. }
88            | Self::Book { teaser, .. }
89            | Self::Paper { teaser, .. }
90            | Self::Course { teaser, .. }
91            | Self::SelfStudy { teaser, .. } => *teaser = Some(new_teaser),
92        }
93    }
94}
95
96#[derive(Debug, Clone)]
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
98#[cfg_attr(feature = "typescript", derive(tsify::Tsify))]
99#[cfg_attr(feature = "typescript", tsify(into_wasm_abi, from_wasm_abi))]
100#[cfg_attr(feature = "serde", serde(tag = "type"))]
101pub enum Institution {
102    #[cfg_attr(feature = "serde", serde(rename = "university"))]
103    University {
104        title: Box<str>,
105        place: Box<str>,
106        country: Box<str>,
107        url: Box<str>,
108        acronym: Box<str>,
109        logo: Box<str>,
110    },
111    #[cfg_attr(feature = "serde", serde(rename = "school"))]
112    School {
113        title: Box<str>,
114        place: Box<str>,
115        country: Box<str>,
116        url: Box<str>,
117        acronym: Box<str>,
118        logo: Box<str>,
119    },
120}
121impl Institution {
122    #[inline]
123    #[must_use]
124    pub const fn acronym(&self) -> &str {
125        match self {
126            Self::University { acronym, .. } | Self::School { acronym, .. } => acronym,
127        }
128    }
129    #[inline]
130    #[must_use]
131    pub const fn url(&self) -> &str {
132        match self {
133            Self::University { url, .. } | Self::School { url, .. } => url,
134        }
135    }
136    #[inline]
137    #[must_use]
138    pub const fn title(&self) -> &str {
139        match self {
140            Self::University { title, .. } | Self::School { title, .. } => title,
141        }
142    }
143    #[inline]
144    #[must_use]
145    pub const fn logo(&self) -> &str {
146        match self {
147            Self::University { logo, .. } | Self::School { logo, .. } => logo,
148        }
149    }
150}
151impl PartialEq for Institution {
152    fn eq(&self, other: &Self) -> bool {
153        match (self, other) {
154            (Self::University { title: t1, .. }, Self::University { title: t2, .. })
155            | (Self::School { title: t1, .. }, Self::School { title: t2, .. }) => t1 == t2,
156            _ => false,
157        }
158    }
159}
160
161#[derive(Debug, Clone)]
162#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
163pub struct Person {
164    pub name: Box<str>,
165}
166/*
167#[derive(Debug, Clone)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
169pub struct PreInstance {
170    pub semester: Box<str>,
171    pub instructors: Option<Vec<Person>>,
172    #[cfg_attr(feature = "serde", serde(rename = "TAs"))]
173    pub tas: Option<Vec<Person>>,
174    #[cfg_attr(feature = "serde", serde(rename = "leadTAs"))]
175    pub lead_tas: Option<Vec<Person>>,
176}
177 */
178
179#[derive(Clone, Debug)]
180#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
181#[cfg_attr(feature = "typescript", derive(tsify::Tsify))]
182#[cfg_attr(feature = "typescript", tsify(into_wasm_abi, from_wasm_abi))]
183#[cfg_attr(feature = "serde", serde(tag = "type"))]
184pub enum ArchiveIndex {
185    #[cfg_attr(feature = "serde", serde(rename = "library"))]
186    Library {
187        archive: ArchiveId,
188        title: Box<str>,
189        #[cfg_attr(feature = "serde", serde(default))]
190        teaser: Option<Box<str>>,
191        #[cfg_attr(feature = "serde", serde(default))]
192        thumbnail: Option<Box<str>>,
193    },
194    #[cfg_attr(feature = "serde", serde(rename = "book"))]
195    Book {
196        title: Box<str>,
197        authors: Box<[Box<str>]>,
198        file: DocumentUri,
199        #[cfg_attr(feature = "serde", serde(default))]
200        teaser: Option<Box<str>>,
201        #[cfg_attr(feature = "serde", serde(default))]
202        thumbnail: Option<Box<str>>,
203    },
204    #[cfg_attr(feature = "serde", serde(rename = "paper"))]
205    Paper {
206        title: Box<str>,
207        authors: Box<[Box<str>]>,
208        file: DocumentUri,
209        #[cfg_attr(feature = "serde", serde(default))]
210        thumbnail: Option<Box<str>>,
211        #[cfg_attr(feature = "serde", serde(default))]
212        teaser: Option<Box<str>>,
213        #[cfg_attr(feature = "serde", serde(default))]
214        venue: Option<Box<str>>,
215        #[cfg_attr(feature = "serde", serde(default))]
216        venue_url: Option<Box<str>>,
217    },
218    #[cfg_attr(feature = "serde", serde(rename = "course"))]
219    Course {
220        title: Box<str>,
221        landing: DocumentUri,
222        acronym: Option<Box<str>>,
223        #[cfg_attr(feature = "serde", serde(default))]
224        authors: Box<[Box<str>]>,
225        institution: Option<Box<str>>,
226        //instances: Box<[Instance]>,
227        notes: DocumentUri,
228        #[cfg_attr(feature = "serde", serde(default))]
229        slides: Option<DocumentUri>,
230        #[cfg_attr(feature = "serde", serde(default))]
231        thumbnail: Option<Box<str>>,
232        //#[cfg_attr(feature = "serde", serde(default))]
233        //quizzes: bool,
234        //#[cfg_attr(feature = "serde", serde(default))]
235        //homeworks: bool,
236        #[cfg_attr(feature = "serde", serde(default))]
237        teaser: Option<Box<str>>,
238    },
239    #[cfg_attr(feature = "serde", serde(rename = "self-study"))]
240    SelfStudy {
241        title: Box<str>,
242        landing: DocumentUri,
243        notes: DocumentUri,
244        #[cfg_attr(feature = "serde", serde(default))]
245        authors: Box<[Box<str>]>,
246        #[cfg_attr(feature = "serde", serde(default))]
247        acronym: Option<Box<str>>,
248        #[cfg_attr(feature = "serde", serde(default))]
249        slides: Option<DocumentUri>,
250        #[cfg_attr(feature = "serde", serde(default))]
251        thumbnail: Option<Box<str>>,
252        #[cfg_attr(feature = "serde", serde(default))]
253        teaser: Option<Box<str>>,
254    },
255}
256impl ArchiveIndex {
257    #[inline]
258    #[must_use]
259    pub fn teaser(&self) -> Option<&str> {
260        match self {
261            Self::Library { teaser, .. }
262            | Self::Book { teaser, .. }
263            | Self::Paper { teaser, .. }
264            | Self::Course { teaser, .. }
265            | Self::SelfStudy { teaser, .. } => teaser.as_deref(),
266        }
267    }
268    pub fn set_teaser(&mut self, new_teaser: Box<str>) {
269        match self {
270            Self::Library { teaser, .. }
271            | Self::Book { teaser, .. }
272            | Self::Paper { teaser, .. }
273            | Self::Course { teaser, .. }
274            | Self::SelfStudy { teaser, .. } => *teaser = Some(new_teaser),
275        }
276    }
277}
278impl Eq for ArchiveIndex {}
279impl PartialEq for ArchiveIndex {
280    fn eq(&self, other: &Self) -> bool {
281        match (self, other) {
282            (Self::Library { archive: a1, .. }, Self::Library { archive: a2, .. }) => a1 == a2,
283            (Self::Book { file: f1, .. }, Self::Book { file: f2, .. })
284            | (Self::Course { notes: f1, .. }, Self::Course { notes: f2, .. })
285            | (Self::Paper { file: f1, .. }, Self::Paper { file: f2, .. })
286            | (Self::SelfStudy { notes: f1, .. }, Self::SelfStudy { notes: f2, .. }) => f1 == f2,
287            _ => false,
288        }
289    }
290}
291
292#[derive(Clone, Debug)]
293#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
294#[cfg_attr(feature = "typescript", derive(tsify::Tsify))]
295#[cfg_attr(feature = "typescript", tsify(into_wasm_abi, from_wasm_abi))]
296pub struct Instance {
297    pub semester: Box<str>,
298    #[cfg_attr(feature = "serde", serde(default))]
299    pub instructors: Option<Box<[Box<str>]>>,
300    #[cfg_attr(feature = "serde", serde(rename = "TAs"))]
301    #[cfg_attr(feature = "serde", serde(default))]
302    pub tas: Option<Box<[Box<str>]>>,
303    #[cfg_attr(feature = "serde", serde(rename = "leadTAs"))]
304    #[cfg_attr(feature = "serde", serde(default))]
305    pub lead_tas: Option<Box<[Box<str>]>>,
306}
307
308#[derive(Debug, thiserror::Error)]
309pub enum IndexParseError {
310    #[error("invalid uri: {0}")]
311    Uri(#[from] UriParseError),
312}
313
314impl ArchiveIndex {
315    /// # Errors
316    #[allow(clippy::too_many_lines)]
317    pub fn from_kind(
318        d: DocumentKind,
319        a: &ArchiveUri,
320        images: impl FnMut(Box<str>) -> Box<str>,
321    ) -> Result<Self, IndexParseError> {
322        Ok(match d {
323            DocumentKind::Library {
324                title,
325                teaser,
326                thumbnail,
327            } => Self::Library {
328                archive: a.archive_id().clone(),
329                title,
330                teaser,
331                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
332                    None
333                } else {
334                    thumbnail.map(images)
335                },
336            },
337            DocumentKind::Book {
338                title,
339                authors,
340                file,
341                teaser,
342                thumbnail,
343            } => Self::Book {
344                title,
345                teaser,
346                file: DocumentUri::from_archive_relpath(a.clone(), &file)?,
347                authors: authors.into_iter().map(|is| is.name).collect(),
348                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
349                    None
350                } else {
351                    thumbnail.map(images)
352                },
353            },
354            DocumentKind::Paper {
355                title,
356                authors,
357                file,
358                teaser,
359                thumbnail,
360                venue,
361                venue_url,
362            } => Self::Paper {
363                title,
364                teaser,
365                venue,
366                venue_url,
367                file: DocumentUri::from_archive_relpath(a.clone(), &file)?,
368                authors: authors.into_iter().map(|is| is.name).collect(),
369                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
370                    None
371                } else {
372                    thumbnail.map(images)
373                },
374            },
375            DocumentKind::Course {
376                title,
377                landing,
378                acronym,
379                authors: instructors,
380                institution,
381                notes,
382                slides,
383                thumbnail,
384                //quizzes,
385                //homeworks,
386                //instances,
387                teaser,
388            } => Self::Course {
389                title,
390                acronym,
391                institution,
392                //quizzes,
393                //homeworks,
394                teaser,
395                landing: DocumentUri::from_archive_relpath(a.clone(), &landing)?,
396                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
397                    None
398                } else {
399                    thumbnail.map(images)
400                },
401                notes: DocumentUri::from_archive_relpath(a.clone(), &notes)?,
402                slides: if slides.as_ref().is_some_and(|s| s.is_empty()) {
403                    None
404                } else {
405                    slides
406                        .map(|s| DocumentUri::from_archive_relpath(a.clone(), &s))
407                        .transpose()?
408                },
409                /*instances: instances
410                .into_iter()
411                .map(|i| Instance {
412                    semester: i.semester,
413                    instructors: i
414                        .instructors
415                        .map(|is| is.into_iter().map(|i| i.name).collect()),
416                    tas: i.tas.map(|is| is.into_iter().map(|i| i.name).collect()),
417                    lead_tas: i
418                        .lead_tas
419                        .map(|is| is.into_iter().map(|i| i.name).collect()),
420                })
421                .collect(),*/
422                authors: instructors.into_iter().map(|is| is.name).collect(),
423            },
424            DocumentKind::SelfStudy {
425                title,
426                landing,
427                acronym,
428                notes,
429                slides,
430                thumbnail,
431                teaser,
432                authors,
433            } => Self::SelfStudy {
434                title,
435                acronym,
436                teaser,
437                landing: DocumentUri::from_archive_relpath(a.clone(), &landing)?,
438                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
439                    None
440                } else {
441                    thumbnail.map(images)
442                },
443                notes: DocumentUri::from_archive_relpath(a.clone(), &notes)?,
444                slides: if slides.as_ref().is_some_and(|s| s.is_empty()) {
445                    None
446                } else {
447                    slides
448                        .map(|s| DocumentUri::from_archive_relpath(a.clone(), &s))
449                        .transpose()?
450                },
451                authors: authors.into_iter().map(|is| is.name).collect(),
452            },
453        })
454    }
455}