Skip to main content

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    #[must_use]
258    pub fn id(&self) -> &ArchiveId {
259        match self {
260            Self::Library { archive, .. } => archive,
261            Self::Book { file: uri, .. }
262            | Self::Paper { file: uri, .. }
263            | Self::Course { notes: uri, .. }
264            | Self::SelfStudy { notes: uri, .. } => uri.archive_id(),
265        }
266    }
267    #[must_use]
268    pub fn authors(&self) -> &[Box<str>] {
269        match self {
270            Self::Library { .. } => &[],
271            Self::Book { authors, .. }
272            | Self::Paper { authors, .. }
273            | Self::Course { authors, .. }
274            | Self::SelfStudy { authors, .. } => authors,
275        }
276    }
277    #[must_use]
278    pub fn title(&self) -> &str {
279        match self {
280            Self::Library { title, .. }
281            | Self::Book { title, .. }
282            | Self::Paper { title, .. }
283            | Self::Course { title, .. }
284            | Self::SelfStudy { title, .. } => title,
285        }
286    }
287    #[must_use]
288    pub fn thumbnail(&self) -> Option<&str> {
289        match self {
290            Self::Library { thumbnail, .. }
291            | Self::Book { thumbnail, .. }
292            | Self::Paper { thumbnail, .. }
293            | Self::Course { thumbnail, .. }
294            | Self::SelfStudy { thumbnail, .. } => thumbnail.as_deref(),
295        }
296    }
297
298    #[must_use]
299    pub fn teaser(&self) -> Option<&str> {
300        match self {
301            Self::Library { teaser, .. }
302            | Self::Book { teaser, .. }
303            | Self::Paper { teaser, .. }
304            | Self::Course { teaser, .. }
305            | Self::SelfStudy { teaser, .. } => teaser.as_deref(),
306        }
307    }
308    pub fn set_teaser(&mut self, new_teaser: Box<str>) {
309        match self {
310            Self::Library { teaser, .. }
311            | Self::Book { teaser, .. }
312            | Self::Paper { teaser, .. }
313            | Self::Course { teaser, .. }
314            | Self::SelfStudy { teaser, .. } => *teaser = Some(new_teaser),
315        }
316    }
317}
318impl Eq for ArchiveIndex {}
319impl PartialEq for ArchiveIndex {
320    fn eq(&self, other: &Self) -> bool {
321        match (self, other) {
322            (Self::Library { archive: a1, .. }, Self::Library { archive: a2, .. }) => a1 == a2,
323            (Self::Book { file: f1, .. }, Self::Book { file: f2, .. })
324            | (Self::Course { notes: f1, .. }, Self::Course { notes: f2, .. })
325            | (Self::Paper { file: f1, .. }, Self::Paper { file: f2, .. })
326            | (Self::SelfStudy { notes: f1, .. }, Self::SelfStudy { notes: f2, .. }) => f1 == f2,
327            _ => false,
328        }
329    }
330}
331
332#[derive(Clone, Debug)]
333#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
334#[cfg_attr(feature = "typescript", derive(tsify::Tsify))]
335#[cfg_attr(feature = "typescript", tsify(into_wasm_abi, from_wasm_abi))]
336pub struct Instance {
337    pub semester: Box<str>,
338    #[cfg_attr(feature = "serde", serde(default))]
339    pub instructors: Option<Box<[Box<str>]>>,
340    #[cfg_attr(feature = "serde", serde(rename = "TAs"))]
341    #[cfg_attr(feature = "serde", serde(default))]
342    pub tas: Option<Box<[Box<str>]>>,
343    #[cfg_attr(feature = "serde", serde(rename = "leadTAs"))]
344    #[cfg_attr(feature = "serde", serde(default))]
345    pub lead_tas: Option<Box<[Box<str>]>>,
346}
347
348#[derive(Debug, thiserror::Error)]
349pub enum IndexParseError {
350    #[error("invalid uri: {0}")]
351    Uri(#[from] UriParseError),
352}
353
354impl ArchiveIndex {
355    /// # Errors
356    #[allow(clippy::too_many_lines)]
357    pub fn from_kind(
358        d: DocumentKind,
359        a: &ArchiveUri,
360        images: impl FnMut(Box<str>) -> Box<str>,
361    ) -> Result<Self, IndexParseError> {
362        Ok(match d {
363            DocumentKind::Library {
364                title,
365                teaser,
366                thumbnail,
367            } => Self::Library {
368                archive: a.archive_id().clone(),
369                title,
370                teaser,
371                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
372                    None
373                } else {
374                    thumbnail.map(images)
375                },
376            },
377            DocumentKind::Book {
378                title,
379                authors,
380                file,
381                teaser,
382                thumbnail,
383            } => Self::Book {
384                title,
385                teaser,
386                file: DocumentUri::from_archive_relpath(a.clone(), &file)?,
387                authors: authors.into_iter().map(|is| is.name).collect(),
388                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
389                    None
390                } else {
391                    thumbnail.map(images)
392                },
393            },
394            DocumentKind::Paper {
395                title,
396                authors,
397                file,
398                teaser,
399                thumbnail,
400                venue,
401                venue_url,
402            } => Self::Paper {
403                title,
404                teaser,
405                venue,
406                venue_url,
407                file: DocumentUri::from_archive_relpath(a.clone(), &file)?,
408                authors: authors.into_iter().map(|is| is.name).collect(),
409                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
410                    None
411                } else {
412                    thumbnail.map(images)
413                },
414            },
415            DocumentKind::Course {
416                title,
417                landing,
418                acronym,
419                authors: instructors,
420                institution,
421                notes,
422                slides,
423                thumbnail,
424                //quizzes,
425                //homeworks,
426                //instances,
427                teaser,
428            } => Self::Course {
429                title,
430                acronym,
431                institution,
432                //quizzes,
433                //homeworks,
434                teaser,
435                landing: DocumentUri::from_archive_relpath(a.clone(), &landing)?,
436                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
437                    None
438                } else {
439                    thumbnail.map(images)
440                },
441                notes: DocumentUri::from_archive_relpath(a.clone(), &notes)?,
442                slides: if slides.as_ref().is_some_and(|s| s.is_empty()) {
443                    None
444                } else {
445                    slides
446                        .map(|s| DocumentUri::from_archive_relpath(a.clone(), &s))
447                        .transpose()?
448                },
449                /*instances: instances
450                .into_iter()
451                .map(|i| Instance {
452                    semester: i.semester,
453                    instructors: i
454                        .instructors
455                        .map(|is| is.into_iter().map(|i| i.name).collect()),
456                    tas: i.tas.map(|is| is.into_iter().map(|i| i.name).collect()),
457                    lead_tas: i
458                        .lead_tas
459                        .map(|is| is.into_iter().map(|i| i.name).collect()),
460                })
461                .collect(),*/
462                authors: instructors.into_iter().map(|is| is.name).collect(),
463            },
464            DocumentKind::SelfStudy {
465                title,
466                landing,
467                acronym,
468                notes,
469                slides,
470                thumbnail,
471                teaser,
472                authors,
473            } => Self::SelfStudy {
474                title,
475                acronym,
476                teaser,
477                landing: DocumentUri::from_archive_relpath(a.clone(), &landing)?,
478                thumbnail: if thumbnail.as_ref().is_some_and(|s| s.is_empty()) {
479                    None
480                } else {
481                    thumbnail.map(images)
482                },
483                notes: DocumentUri::from_archive_relpath(a.clone(), &notes)?,
484                slides: if slides.as_ref().is_some_and(|s| s.is_empty()) {
485                    None
486                } else {
487                    slides
488                        .map(|s| DocumentUri::from_archive_relpath(a.clone(), &s))
489                        .transpose()?
490                },
491                authors: authors.into_iter().map(|is| is.name).collect(),
492            },
493        })
494    }
495}