konnektoren_core/challenges/
challenge_result.rs

1use crate::challenges::error::{ChallengeError, Result};
2use crate::challenges::{
3    ChallengeInput, ContextItemChoiceAnswers, CustomChallengeResult, GapFillAnswer,
4    MultipleChoiceOption, OrderingResult, SortTableRow,
5};
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
9pub enum ChallengeResult {
10    MultipleChoice(Vec<MultipleChoiceOption>),
11    ContextualChoice(Vec<ContextItemChoiceAnswers>),
12    GapFill(Vec<GapFillAnswer>),
13    SortTable(Vec<SortTableRow>),
14    Informative,
15    Ordering(Vec<OrderingResult>),
16    Custom(CustomChallengeResult),
17    Vocabulary,
18}
19
20impl Default for ChallengeResult {
21    fn default() -> Self {
22        ChallengeResult::MultipleChoice(Vec::new())
23    }
24}
25
26impl ChallengeResult {
27    pub fn add_input(&mut self, input: ChallengeInput) -> Result<()> {
28        match self {
29            ChallengeResult::MultipleChoice(options) => match input {
30                ChallengeInput::MultipleChoice(option) => {
31                    options.push(option);
32                    Ok(())
33                }
34                _ => Err(ChallengeError::InvalidInput(
35                    "Expected MultipleChoice input".to_string(),
36                )),
37            },
38            ChallengeResult::ContextualChoice(answers) => match input {
39                ChallengeInput::ContextualChoice(answer) => {
40                    answers.push(answer);
41                    Ok(())
42                }
43                _ => Err(ChallengeError::InvalidInput(
44                    "Expected ContextualChoice input".to_string(),
45                )),
46            },
47            ChallengeResult::GapFill(answers) => match input {
48                ChallengeInput::GapFill(answer) => {
49                    answers.push(answer);
50                    Ok(())
51                }
52                _ => Err(ChallengeError::InvalidInput(
53                    "Expected GapFill input".to_string(),
54                )),
55            },
56            ChallengeResult::SortTable(rows) => match input {
57                ChallengeInput::SortTable(row) => {
58                    rows.push(row);
59                    Ok(())
60                }
61                _ => Err(ChallengeError::InvalidInput(
62                    "Expected SortTable input".to_string(),
63                )),
64            },
65            ChallengeResult::Ordering(results) => match input {
66                ChallengeInput::Ordering(result) => {
67                    results.push(result);
68                    Ok(())
69                }
70                _ => Err(ChallengeError::InvalidInput(
71                    "Expected Ordering input".to_string(),
72                )),
73            },
74            ChallengeResult::Informative => Ok(()),
75            ChallengeResult::Custom(_) => Ok(()),
76            ChallengeResult::Vocabulary => Ok(()),
77        }
78    }
79
80    /// Sets the input at a specific index, filling gaps with default values if needed
81    pub fn set_input(&mut self, index: usize, input: ChallengeInput) -> Result<()> {
82        match self {
83            ChallengeResult::MultipleChoice(options) => match input {
84                ChallengeInput::MultipleChoice(option) => {
85                    // Ensure we have enough slots, fill with default if needed
86                    while options.len() <= index {
87                        options.push(MultipleChoiceOption {
88                            id: 0,
89                            name: String::new(),
90                        });
91                    }
92                    options[index] = option;
93                    Ok(())
94                }
95                _ => Err(ChallengeError::InvalidInput(
96                    "Expected MultipleChoice input".to_string(),
97                )),
98            },
99            ChallengeResult::ContextualChoice(answers) => match input {
100                ChallengeInput::ContextualChoice(answer) => {
101                    while answers.len() <= index {
102                        answers.push(ContextItemChoiceAnswers { ids: vec![] });
103                    }
104                    answers[index] = answer;
105                    Ok(())
106                }
107                _ => Err(ChallengeError::InvalidInput(
108                    "Expected ContextualChoice input".to_string(),
109                )),
110            },
111            ChallengeResult::GapFill(answers) => match input {
112                ChallengeInput::GapFill(answer) => {
113                    while answers.len() <= index {
114                        answers.push(GapFillAnswer {
115                            question_index: 0,
116                            answers: vec![],
117                        });
118                    }
119                    answers[index] = answer;
120                    Ok(())
121                }
122                _ => Err(ChallengeError::InvalidInput(
123                    "Expected GapFill input".to_string(),
124                )),
125            },
126            ChallengeResult::SortTable(rows) => match input {
127                ChallengeInput::SortTable(row) => {
128                    while rows.len() <= index {
129                        rows.push(SortTableRow {
130                            id: 0,
131                            values: vec![],
132                        });
133                    }
134                    rows[index] = row;
135                    Ok(())
136                }
137                _ => Err(ChallengeError::InvalidInput(
138                    "Expected SortTable input".to_string(),
139                )),
140            },
141            ChallengeResult::Ordering(results) => match input {
142                ChallengeInput::Ordering(result) => {
143                    while results.len() <= index {
144                        results.push(OrderingResult { order: vec![] });
145                    }
146                    results[index] = result;
147                    Ok(())
148                }
149                _ => Err(ChallengeError::InvalidInput(
150                    "Expected Ordering input".to_string(),
151                )),
152            },
153            ChallengeResult::Informative => Ok(()),
154            ChallengeResult::Custom(_) => Ok(()),
155            ChallengeResult::Vocabulary => Ok(()),
156        }
157    }
158
159    pub fn len(&self) -> usize {
160        match self {
161            ChallengeResult::MultipleChoice(options) => options.len(),
162            ChallengeResult::ContextualChoice(items) => items.len(),
163            ChallengeResult::GapFill(answers) => answers.len(),
164            ChallengeResult::SortTable(rows) => rows.len(),
165            ChallengeResult::Ordering(results) => results.len(),
166            ChallengeResult::Informative => 0,
167            ChallengeResult::Custom(_) => 0,
168            ChallengeResult::Vocabulary => 0,
169        }
170    }
171
172    pub fn is_empty(&self) -> bool {
173        match self {
174            ChallengeResult::MultipleChoice(options) => options.is_empty(),
175            ChallengeResult::ContextualChoice(items) => items.is_empty(),
176            ChallengeResult::GapFill(answers) => answers.is_empty(),
177            ChallengeResult::SortTable(rows) => rows.is_empty(),
178            ChallengeResult::Ordering(results) => results.is_empty(),
179            ChallengeResult::Informative => true,
180            ChallengeResult::Custom(_) => true,
181            ChallengeResult::Vocabulary => true,
182        }
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189
190    #[test]
191    fn default_challenge_result() {
192        let challenge_result = ChallengeResult::default();
193        match challenge_result {
194            ChallengeResult::MultipleChoice(options) => {
195                assert!(options.is_empty());
196            }
197            _ => panic!("Invalid challenge result"),
198        }
199    }
200
201    #[test]
202    fn add_multiple_choice() {
203        let mut challenge_result = ChallengeResult::default();
204        let input = ChallengeInput::MultipleChoice(MultipleChoiceOption {
205            id: 1,
206            name: "Option 1".to_string(),
207        });
208        let result = challenge_result.add_input(input);
209        assert!(result.is_ok());
210        match challenge_result {
211            ChallengeResult::MultipleChoice(options) => {
212                assert_eq!(options.len(), 1);
213            }
214            _ => panic!("Invalid challenge result"),
215        }
216    }
217
218    #[test]
219    fn add_sort_table() {
220        let mut challenge_result = ChallengeResult::SortTable(Vec::new());
221        let input = ChallengeInput::SortTable(SortTableRow {
222            id: 1,
223            values: vec!["Value 1".to_string()],
224        });
225        let result = challenge_result.add_input(input);
226        assert!(result.is_ok());
227        match challenge_result {
228            ChallengeResult::SortTable(rows) => {
229                assert_eq!(rows.len(), 1);
230            }
231            _ => panic!("Invalid challenge result"),
232        }
233    }
234
235    #[test]
236    fn test_is_empty() {
237        let challenge_result = ChallengeResult::default();
238        assert!(challenge_result.is_empty());
239    }
240
241    #[test]
242    fn test_len() {
243        let challenge_result = ChallengeResult::MultipleChoice(vec![
244            MultipleChoiceOption {
245                id: 1,
246                name: "Option 1".to_string(),
247            },
248            MultipleChoiceOption {
249                id: 2,
250                name: "Option 2".to_string(),
251            },
252        ]);
253        assert_eq!(challenge_result.len(), 2);
254    }
255
256    #[test]
257    fn add_ordering() {
258        let mut challenge_result = ChallengeResult::Ordering(Vec::new());
259        let input = ChallengeInput::Ordering(OrderingResult {
260            order: vec![2, 0, 1],
261        });
262        let result = challenge_result.add_input(input);
263        assert!(result.is_ok());
264        match challenge_result {
265            ChallengeResult::Ordering(results) => {
266                assert_eq!(results.len(), 1);
267                assert_eq!(results[0].order, vec![2, 0, 1]);
268            }
269            _ => panic!("Invalid challenge result"),
270        }
271    }
272
273    #[test]
274    fn test_ordering_len() {
275        let challenge_result = ChallengeResult::Ordering(vec![
276            OrderingResult {
277                order: vec![0, 1, 2],
278            },
279            OrderingResult {
280                order: vec![2, 1, 0],
281            },
282        ]);
283        assert_eq!(challenge_result.len(), 2);
284    }
285
286    #[test]
287    fn test_ordering_is_empty() {
288        let challenge_result = ChallengeResult::Ordering(Vec::new());
289        assert!(challenge_result.is_empty());
290    }
291
292    #[test]
293    fn test_add_input_wrong_type() {
294        let mut result = ChallengeResult::MultipleChoice(vec![]);
295        let input = ChallengeInput::SortTable(SortTableRow::default());
296        let err = result.add_input(input);
297        assert!(err.is_err());
298    }
299
300    #[test]
301    fn test_set_input_multiple_choice() {
302        let mut challenge_result = ChallengeResult::default();
303        let input = ChallengeInput::MultipleChoice(MultipleChoiceOption {
304            id: 2,
305            name: "Option 2".to_string(),
306        });
307
308        // Set at index 2 (will fill 0, 1 with defaults)
309        let result = challenge_result.set_input(2, input);
310        assert!(result.is_ok());
311
312        match challenge_result {
313            ChallengeResult::MultipleChoice(options) => {
314                assert_eq!(options.len(), 3);
315                assert_eq!(options[0].id, 0); // Default
316                assert_eq!(options[1].id, 0); // Default
317                assert_eq!(options[2].id, 2); // Our input
318                assert_eq!(options[2].name, "Option 2");
319            }
320            _ => panic!("Invalid challenge result"),
321        }
322    }
323
324    #[test]
325    fn test_set_input_replace_existing() {
326        let mut challenge_result = ChallengeResult::MultipleChoice(vec![
327            MultipleChoiceOption {
328                id: 1,
329                name: "Option 1".to_string(),
330            },
331            MultipleChoiceOption {
332                id: 2,
333                name: "Option 2".to_string(),
334            },
335        ]);
336
337        let new_input = ChallengeInput::MultipleChoice(MultipleChoiceOption {
338            id: 3,
339            name: "Option 3".to_string(),
340        });
341
342        // Replace index 1
343        let result = challenge_result.set_input(1, new_input);
344        assert!(result.is_ok());
345
346        match challenge_result {
347            ChallengeResult::MultipleChoice(options) => {
348                assert_eq!(options.len(), 2);
349                assert_eq!(options[0].id, 1); // Unchanged
350                assert_eq!(options[1].id, 3); // Replaced
351                assert_eq!(options[1].name, "Option 3");
352            }
353            _ => panic!("Invalid challenge result"),
354        }
355    }
356
357    #[test]
358    fn test_set_input_wrong_type() {
359        let mut result = ChallengeResult::MultipleChoice(vec![]);
360        let input = ChallengeInput::SortTable(SortTableRow::default());
361        let err = result.set_input(0, input);
362        assert!(err.is_err());
363    }
364
365    #[test]
366    fn test_set_input_ordering() {
367        let mut challenge_result = ChallengeResult::Ordering(Vec::new());
368        let input = ChallengeInput::Ordering(OrderingResult {
369            order: vec![2, 0, 1],
370        });
371
372        let result = challenge_result.set_input(1, input);
373        assert!(result.is_ok());
374
375        match challenge_result {
376            ChallengeResult::Ordering(results) => {
377                assert_eq!(results.len(), 2);
378                assert!(results[0].order.is_empty()); // Default
379                assert_eq!(results[1].order, vec![2, 0, 1]);
380            }
381            _ => panic!("Invalid challenge result"),
382        }
383    }
384
385    #[test]
386    fn test_set_input_contextual_choice() {
387        let mut result = ChallengeResult::ContextualChoice(Vec::new());
388        let input = ChallengeInput::ContextualChoice(ContextItemChoiceAnswers { ids: vec![0, 1] });
389
390        let res = result.set_input(0, input);
391        assert!(res.is_ok());
392
393        match result {
394            ChallengeResult::ContextualChoice(answers) => {
395                assert_eq!(answers.len(), 1);
396                assert_eq!(answers[0].ids, vec![0, 1]);
397            }
398            _ => panic!("Expected ContextualChoice result"),
399        }
400    }
401
402    #[test]
403    fn test_set_input_fills_gaps_contextual_choice() {
404        let mut result = ChallengeResult::ContextualChoice(Vec::new());
405        let input = ChallengeInput::ContextualChoice(ContextItemChoiceAnswers { ids: vec![2] });
406
407        // Set at index 2, should fill 0 and 1 with defaults
408        let res = result.set_input(2, input);
409        assert!(res.is_ok());
410
411        match result {
412            ChallengeResult::ContextualChoice(answers) => {
413                assert_eq!(answers.len(), 3);
414                assert!(answers[0].ids.is_empty()); // Default
415                assert!(answers[1].ids.is_empty()); // Default
416                assert_eq!(answers[2].ids, vec![2]);
417            }
418            _ => panic!("Expected ContextualChoice result"),
419        }
420    }
421
422    #[test]
423    fn test_set_input_wrong_type_contextual_choice() {
424        let mut result = ChallengeResult::ContextualChoice(Vec::new());
425        let input = ChallengeInput::MultipleChoice(MultipleChoiceOption::default());
426
427        let res = result.set_input(0, input);
428        assert!(res.is_err());
429        assert!(matches!(res.unwrap_err(), ChallengeError::InvalidInput(_)));
430    }
431}