celcat/fetchable/
event.rs

1use std::fmt;
2
3use serde::{
4    de::{self, SeqAccess, Visitor},
5    Deserialize, Deserializer, Serialize,
6};
7use serde_json::Value;
8
9use super::Fetchable;
10use crate::entities::{CourseId, EntityType, Module, Room, Staff, Unknown, UnknownId};
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct Elements(pub Vec<Element>);
14
15#[derive(Debug, Clone, PartialEq, Deserialize)]
16#[serde(rename_all = "camelCase")]
17pub struct Event {
18    pub federation_id: UnknownId,
19    pub entity_type: Unknown,
20    pub elements: Elements,
21}
22
23#[derive(Debug, Clone, PartialEq, Deserialize)]
24#[serde(rename_all = "camelCase")]
25#[non_exhaustive]
26pub struct RawElement<T: EntityType> {
27    pub content: Option<String>,
28    #[serde(bound(deserialize = "T: EntityType"))]
29    pub federation_id: T::Id,
30    #[serde(bound(deserialize = "T: EntityType"))]
31    pub entity_type: T,
32    pub assignment_context: Option<String>,
33    pub contains_hyperlinks: bool,
34    pub is_notes: bool,
35    pub is_student_specific: bool,
36}
37
38#[derive(Debug, Clone, PartialEq)]
39#[non_exhaustive]
40pub enum Element {
41    Time(RawElement<Unknown>),
42    Category(RawElement<Unknown>),
43    Module(RawElement<Module>),
44    Room(RawElement<Room>),
45    Teacher(RawElement<Staff>),
46    Grade(RawElement<Unknown>),
47    Name(RawElement<Unknown>),
48}
49
50impl<'de> Deserialize<'de> for Elements {
51    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
52    where
53        D: Deserializer<'de>,
54    {
55        struct ElementsVisitor;
56
57        impl<'de> Visitor<'de> for ElementsVisitor {
58            type Value = Vec<Element>;
59
60            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
61                formatter.write_str("a sequence of side bar events")
62            }
63
64            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
65            where
66                A: SeqAccess<'de>,
67            {
68                #[derive(Copy, Clone, PartialEq, Deserialize)]
69                #[serde(field_identifier)]
70                enum Tag {
71                    #[serde(rename = "Date")]
72                    Time,
73                    #[serde(rename = "Catégorie")]
74                    Category,
75                    #[serde(rename = "Matière")]
76                    Module,
77                    #[serde(rename = "Salle", alias = "Salles")]
78                    Room,
79                    #[serde(rename = "Enseignant", alias = "Enseignants")]
80                    Teacher,
81                    #[serde(rename = "Note", alias = "Notes")]
82                    Grade,
83                    Name,
84                }
85
86                fn deserialize_element<'de, A>(tag: Tag, v: Value) -> Result<Element, A::Error>
87                where
88                    A: SeqAccess<'de>,
89                {
90                    Ok(match tag {
91                        Tag::Time => Element::Time(
92                            RawElement::<Unknown>::deserialize(v).map_err(de::Error::custom)?,
93                        ),
94                        Tag::Category => Element::Category(
95                            RawElement::<Unknown>::deserialize(v).map_err(de::Error::custom)?,
96                        ),
97                        Tag::Module => Element::Module(
98                            RawElement::<Module>::deserialize(v).map_err(de::Error::custom)?,
99                        ),
100                        Tag::Room => Element::Room(
101                            RawElement::<Room>::deserialize(v).map_err(de::Error::custom)?,
102                        ),
103                        Tag::Teacher => Element::Teacher(
104                            RawElement::<Staff>::deserialize(v).map_err(de::Error::custom)?,
105                        ),
106                        Tag::Grade => Element::Grade(
107                            RawElement::<Unknown>::deserialize(v).map_err(de::Error::custom)?,
108                        ),
109                        Tag::Name => Element::Name(
110                            RawElement::<Unknown>::deserialize(v).map_err(de::Error::custom)?,
111                        ),
112                    })
113                }
114
115                let mut elements = Vec::with_capacity(seq.size_hint().unwrap_or(0));
116
117                let mut last_tag = None;
118                while let Some(v) = seq.next_element::<Value>()? {
119                    elements.push({
120                        match Option::deserialize(&v["label"]).map_err(de::Error::custom)? {
121                            Some(tag) => {
122                                last_tag = Some(tag);
123                                deserialize_element::<A>(tag, v)?
124                            }
125                            None => match last_tag {
126                                Some(tag) => deserialize_element::<A>(tag, v)?,
127                                None => {
128                                    return Err(de::Error::custom("first element needs a label"));
129                                }
130                            },
131                        }
132                    });
133                }
134
135                Ok(elements)
136            }
137        }
138
139        Ok(Elements(deserializer.deserialize_seq(ElementsVisitor)?))
140    }
141}
142
143#[derive(Debug, Clone, PartialEq, Serialize)]
144#[serde(rename_all = "camelCase")]
145pub struct EventRequest {
146    pub event_id: CourseId,
147}
148
149impl Fetchable for Event {
150    type Request = EventRequest;
151
152    const METHOD_NAME: &'static str = "GetSideBarEvent";
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use serde_json::{from_value, json};
159
160    #[test]
161    fn deserialize_element() {
162        use Element::*;
163        assert!(matches!(
164            from_value::<Elements>(json!([
165                {
166                    "label": "Date",
167                    "content": "11/9/2021 2:01 PM-5:16 PM",
168                    "federationId": null,
169                    "entityType": 0,
170                    "assignmentContext": null,
171                    "containsHyperlinks": false,
172                    "isNotes": false,
173                    "isStudentSpecific": false
174                },
175                {
176                    "label": "Catégorie",
177                    "content": "TD",
178                    "federationId": null,
179                    "entityType": 0,
180                    "assignmentContext": null,
181                    "containsHyperlinks": false,
182                    "isNotes": false,
183                    "isStudentSpecific": false
184                },
185                {
186                    "label": "Matière",
187                    "content": "Anglais [DPGANG3D]",
188                    "federationId": "DPGANG3D",
189                    "entityType": 100,
190                    "assignmentContext": "a-start-end",
191                    "containsHyperlinks": false,
192                    "isNotes": false,
193                    "isStudentSpecific": false
194                },
195                {
196                    "label": "Salles",
197                    "content": "A ROOM",
198                    "federationId": "1172982",
199                    "entityType": 102,
200                    "assignmentContext": "a-start",
201                    "containsHyperlinks": false,
202                    "isNotes": false,
203                    "isStudentSpecific": false
204                },
205                {
206                    "label": null,
207                    "content": "AN ANOTHER ROOM",
208                    "federationId": "1172981",
209                    "entityType": 102,
210                    "assignmentContext": "a",
211                    "containsHyperlinks": false,
212                    "isNotes": false,
213                    "isStudentSpecific": false
214                },
215                {
216                    "label": null,
217                    "content": "YET AN ANOTHER ROOM",
218                    "federationId": "1172977",
219                    "entityType": 102,
220                    "assignmentContext": "a-end-0",
221                    "containsHyperlinks": false,
222                    "isNotes": false,
223                    "isStudentSpecific": false
224                },
225                {
226                    "label": "Enseignants",
227                    "content": "SOME BODY",
228                    "federationId": "012345",
229                    "entityType": 101,
230                    "assignmentContext": "a-start",
231                    "containsHyperlinks": false,
232                    "isNotes": false,
233                    "isStudentSpecific": false
234                },
235                {
236                    "label": null,
237                    "content": "SOMEBODY ELSE",
238                    "federationId": "54321",
239                    "entityType": 101,
240                    "assignmentContext": "a-end-0",
241                    "containsHyperlinks": false,
242                    "isNotes": false,
243                    "isStudentSpecific": false
244                },
245                {
246                    "label": "Notes",
247                    "content": null,
248                    "federationId": null,
249                    "entityType": 0,
250                    "assignmentContext": null,
251                    "containsHyperlinks": false,
252                    "isNotes": true,
253                    "isStudentSpecific": false
254                },
255                {
256                    "label": "Name",
257                    "content": null,
258                    "federationId": null,
259                    "entityType": 0,
260                    "assignmentContext": null,
261                    "containsHyperlinks": false,
262                    "isNotes": false,
263                    "isStudentSpecific": false
264                }
265            ]))
266            .unwrap()
267            .0[..],
268            [
269                Time(_),
270                Category(_),
271                Module(_),
272                Room(_),
273                Room(_),
274                Room(_),
275                Teacher(_),
276                Teacher(_),
277                Grade(_),
278                Name(_),
279            ]
280        ));
281    }
282
283    #[test]
284    fn deserialize_event() {
285        from_value::<Event>(json!({
286            "federationId": null,
287            "entityType": 0,
288            "elements": []
289        }))
290        .unwrap();
291    }
292}