tmc_mooc_client/
exercise.rs1use chrono::{DateTime, Utc};
2use mooc_langs_api as api;
3use serde::{Deserialize, Serialize};
4use tmc_langs_util::{JsonError, deserialize};
5#[cfg(feature = "ts-rs")]
6use ts_rs::TS;
7use uuid::Uuid;
8
9#[derive(Debug, Serialize, Deserialize)]
10#[cfg_attr(feature = "ts-rs", derive(TS))]
11pub struct TmcExerciseSlide {
12 pub slide_id: Uuid,
13 pub exercise_id: Uuid,
14 pub exercise_name: String,
15 pub exercise_order_number: i32,
16 pub deadline: Option<DateTime<Utc>>,
17 pub tasks: Vec<TmcExerciseTask>,
18}
19
20impl TryFrom<api::ExerciseSlide> for TmcExerciseSlide {
21 type Error = JsonError;
22 fn try_from(value: api::ExerciseSlide) -> Result<Self, Self::Error> {
23 let slide = Self {
24 slide_id: value.slide_id,
25 exercise_id: value.exercise_id,
26 exercise_name: value.exercise_name,
27 exercise_order_number: value.exercise_order_number,
28 deadline: value.deadline,
29 tasks: value
30 .tasks
31 .into_iter()
32 .map(TryFrom::try_from)
33 .collect::<Result<_, _>>()?,
34 };
35 Ok(slide)
36 }
37}
38
39#[derive(Debug, Serialize, Deserialize)]
40#[cfg_attr(feature = "ts-rs", derive(TS))]
41pub struct TmcExerciseTask {
42 pub task_id: Uuid,
43 pub order_number: i32,
44 pub assignment: serde_json::Value,
45 pub public_spec: Option<PublicSpec>,
46 pub model_solution_spec: Option<ModelSolutionSpec>,
47}
48
49impl TryFrom<api::ExerciseTask> for TmcExerciseTask {
50 type Error = JsonError;
51 fn try_from(value: api::ExerciseTask) -> Result<Self, Self::Error> {
52 let task = Self {
53 task_id: value.task_id,
54 order_number: value.order_number,
55 assignment: value.assignment,
56 public_spec: value
57 .public_spec
58 .map(deserialize::json_from_value)
59 .transpose()?,
60 model_solution_spec: value
61 .model_solution_spec
62 .map(deserialize::json_from_value)
63 .transpose()?,
64 };
65 Ok(task)
66 }
67}
68
69#[derive(Debug, Serialize, Deserialize)]
70#[serde(tag = "type")]
71#[cfg_attr(feature = "ts-rs", derive(TS))]
72pub enum PublicSpec {
73 Browser {
74 files: Vec<ExerciseFile>,
75 },
76 Editor {
77 archive_name: String,
78 archive_download_url: String,
79 checksum: String,
80 },
81}
82
83#[derive(Debug, Serialize, Deserialize)]
84#[serde(tag = "type")]
85pub enum UserAnswer {
86 Browser { files: Vec<ExerciseFile> },
87 Editor { archive_download_url: String },
88}
89
90#[derive(Debug, Serialize, Deserialize)]
91#[serde(tag = "type")]
92#[cfg_attr(feature = "ts-rs", derive(TS))]
93pub enum ModelSolutionSpec {
94 Browser { solution_files: Vec<ExerciseFile> },
95 Editor { download_url: String },
96}
97
98#[derive(Debug, Serialize, Deserialize)]
99#[cfg_attr(feature = "ts-rs", derive(TS))]
100pub struct ExerciseFile {
101 filepath: String,
102 contents: String,
103}
104
105#[cfg(test)]
106mod test {
107 use super::*;
108
109 #[test]
110 fn deserializes_browser_public_spec() {
111 let browser_task = r#"
112{
113 "type": "Browser",
114 "files": [
115 {
116 "filepath": "1",
117 "contents": "2"
118 },
119 {
120 "filepath": "3",
121 "contents": "4"
122 }
123 ]
124}
125"#;
126 serde_json::from_str::<PublicSpec>(browser_task).unwrap();
127 }
128
129 #[test]
130 fn deserializes_editor_public_spec() {
131 let editor_task = r#"
132{
133 "type": "Editor",
134 "archive_name": "1",
135 "archive_download_url": "2",
136 "checksum": "abcd"
137}
138"#;
139 serde_json::from_str::<PublicSpec>(editor_task).unwrap();
140 }
141}