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}