Skip to content

Commit 9cc73a1

Browse files
committed
Separate mooc api types from mooc client types
1 parent 72a7d60 commit 9cc73a1

File tree

5 files changed

+135
-23
lines changed

5 files changed

+135
-23
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ rust-version = "1.70.0"
2525
version = "0.33.0"
2626

2727
[workspace.dependencies]
28-
mooc-langs-api = { git = "https://github.com/rage/secret-project-331.git", rev = "df8bd2b0c12ba8652e0e46b7ccba25dea1547dc6" }
28+
mooc-langs-api = { git = "https://github.com/rage/secret-project-331.git", rev = "778a5820a8b7422cbf9f1c786da437833ced5ae9" }
2929
tmc-langs = { path = "crates/tmc-langs" }
3030
tmc-langs-csharp = { path = "crates/plugins/csharp" }
3131
tmc-langs-framework = { path = "crates/tmc-langs-framework" }
@@ -44,5 +44,5 @@ ts-rs = { git = "https://github.com/Heliozoa/ts-rs.git", rev = "07712bf04007472a
4444
# [patch.'https://github.com/Heliozoa/ts-rs.git']
4545
# ts-rs = { path = "../ts-rs/ts-rs" }
4646

47-
[patch.'https://github.com/rage/secret-project-331.git']
48-
mooc-langs-api = { path = "../secret-project-331/services/headless-lms/langs-api" }
47+
# [patch.'https://github.com/rage/secret-project-331.git']
48+
# mooc-langs-api = { path = "../secret-project-331/services/headless-lms/langs-api" }

crates/tmc-langs-cli/src/output.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use tmc_langs::{
1919
use tmc_langs_util::progress_reporter::StatusUpdate;
2020

2121
/// The format for all messages written to stdout by the CLI
22-
#[derive(Debug, Serialize, Deserialize)]
22+
#[derive(Debug, Serialize)]
2323
#[serde(rename_all = "kebab-case")]
2424
#[serde(tag = "output-kind")]
2525
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
@@ -55,7 +55,7 @@ impl CliOutput {
5555
}
5656
}
5757

58-
#[derive(Debug, Serialize, Deserialize)]
58+
#[derive(Debug, Serialize)]
5959
#[serde(rename_all = "kebab-case")]
6060
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
6161
pub struct OutputData {
@@ -65,7 +65,7 @@ pub struct OutputData {
6565
pub data: Option<DataKind>,
6666
}
6767

68-
#[derive(Debug, Serialize, Deserialize)]
68+
#[derive(Debug, Serialize)]
6969
#[serde(rename_all = "kebab-case")]
7070
#[serde(tag = "output-data-kind", content = "output-data")]
7171
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]

crates/tmc-mooc-client/Cargo.toml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,16 @@ reqwest = { version = "0.11.18", default-features = false, features = [
2424
serde = { version = "1.0.163", features = ["derive"] }
2525
serde_json = "1.0.96"
2626
thiserror = "1.0.40"
27-
ts-rs = { workspace = true, features = ["serde-compat"], optional = true }
27+
ts-rs = { workspace = true, features = [
28+
"chrono-impl",
29+
"serde-compat",
30+
"serde-json-impl",
31+
"uuid-impl",
32+
], optional = true }
2833
uuid = { version = "1.3.3", features = ["serde", "v4"] }
2934

3035
[dev-dependencies]
3136
simple_logger = "4.0.0"
3237

3338
[features]
34-
ts-rs = ["dep:ts-rs", "mooc-langs-api/ts_rs"]
39+
ts-rs = ["dep:ts-rs"]

crates/tmc-mooc-client/src/lib.rs

Lines changed: 121 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ pub use self::exercise::{
1010
};
1111
use crate::error::{MoocClientError, MoocClientResult};
1212
use bytes::Bytes;
13+
use chrono::{DateTime, Utc};
1314
use exercise::UserAnswer;
14-
pub use mooc_langs_api::*;
15+
pub use mooc_langs_api as api;
1516
use oauth2::TokenResponse;
1617
use reqwest::{
1718
blocking::{
@@ -23,6 +24,8 @@ use reqwest::{
2324
use serde::{de::DeserializeOwned, Serialize};
2425
use std::{borrow::Cow, path::Path, sync::Arc};
2526
use tmc_langs_util::{serialize, JsonError};
27+
#[cfg(feature = "ts-rs")]
28+
use ts_rs::TS;
2629
use uuid::Uuid;
2730

2831
/// Client for accessing the Courses MOOC API.
@@ -33,7 +36,7 @@ pub struct MoocClient(Arc<MoocClientInner>);
3336
struct MoocClientInner {
3437
client: Client,
3538
root_addr: Cow<'static, str>,
36-
token: Option<Token>,
39+
token: Option<api::Token>,
3740
}
3841

3942
/// Non-API methods.
@@ -78,7 +81,7 @@ impl MoocClient {
7881
}
7982
}
8083

81-
pub fn set_token(&mut self, token: Token) {
84+
pub fn set_token(&mut self, token: api::Token) {
8285
Arc::get_mut(&mut self.0)
8386
.expect("called when multiple clones exist")
8487
.token = Some(token);
@@ -90,8 +93,8 @@ impl MoocClient {
9093
pub fn course_instances(&self) -> MoocClientResult<Vec<CourseInstance>> {
9194
let res = self
9295
.request(Method::GET, "course-instances")
93-
.send_expect_json()?;
94-
Ok(res)
96+
.send_expect_json::<Vec<api::CourseInstance>>()?;
97+
Ok(res.into_iter().map(Into::into).collect())
9598
}
9699

97100
pub fn course_instance_exercise_slides(
@@ -101,7 +104,7 @@ impl MoocClient {
101104
let url = format!("course-instances/{course_instance}/exercises");
102105
let res = self
103106
.request(Method::GET, &url)
104-
.send_expect_json::<Vec<ExerciseSlide>>()?
107+
.send_expect_json::<Vec<api::ExerciseSlide>>()?
105108
.into_iter()
106109
.map(TryFrom::try_from)
107110
.collect::<Result<_, JsonError>>()
@@ -116,7 +119,7 @@ impl MoocClient {
116119
let url = format!("exercises/{exercise}");
117120
let res = self
118121
.request(Method::GET, &url)
119-
.send_expect_json::<ExerciseSlide>()?
122+
.send_expect_json::<api::ExerciseSlide>()?
120123
.try_into()
121124
.map_err(|err: JsonError| MoocClientError::DeserializingResponse {
122125
url,
@@ -145,7 +148,7 @@ impl MoocClient {
145148
archive: &Path,
146149
) -> MoocClientResult<ExerciseTaskSubmissionResult> {
147150
// upload archive
148-
let metadata = UploadMetadata { slide_id, task_id };
151+
let metadata = api::UploadMetadata { slide_id, task_id };
149152
let metadata = serialize::to_json_vec(&metadata)?;
150153
let form = Form::new()
151154
.part(
@@ -159,23 +162,23 @@ impl MoocClient {
159162
let res = self
160163
.request(Method::POST, &format!("exercises/{exercise_id}/upload"))
161164
.multipart(form)
162-
.send_expect_json::<UploadResult>()?;
165+
.send_expect_json::<api::UploadResult>()?;
163166

164167
// send submission
165168
let user_answer = UserAnswer::Editor {
166169
download_url: res.download_url,
167170
};
168171
let data_json = serialize::to_json_value(&user_answer)?;
169-
let exercise_slide_submission = ExerciseSlideSubmission {
172+
let exercise_slide_submission = api::ExerciseSlideSubmission {
170173
exercise_slide_id: slide_id,
171174
exercise_task_id: task_id,
172175
data_json,
173176
};
174177
let res = self
175178
.request(Method::POST, &format!("exercises/{exercise_id}/submit"))
176179
.json(&exercise_slide_submission)
177-
.send_expect_json()?;
178-
Ok(res)
180+
.send_expect_json::<api::ExerciseTaskSubmissionResult>()?;
181+
Ok(res.into())
179182
}
180183

181184
pub fn get_submission_grading(
@@ -184,8 +187,8 @@ impl MoocClient {
184187
) -> MoocClientResult<ExerciseTaskSubmissionStatus> {
185188
let res = self
186189
.request(Method::GET, &format!("submissions/{submission_id}/grading"))
187-
.send_expect_json()?;
188-
Ok(res)
190+
.send_expect_json::<api::ExerciseTaskSubmissionStatus>()?;
191+
Ok(res.into())
189192
}
190193
}
191194

@@ -272,3 +275,107 @@ impl MoocRequest {
272275
Ok(json)
273276
}
274277
}
278+
279+
#[derive(Debug, Serialize)]
280+
#[cfg_attr(feature = "ts-rs", derive(TS))]
281+
pub struct CourseInstance {
282+
pub id: Uuid,
283+
pub course_id: Uuid,
284+
pub course_slug: String,
285+
pub course_name: String,
286+
pub course_description: Option<String>,
287+
pub instance_name: Option<String>,
288+
pub instance_description: Option<String>,
289+
}
290+
291+
impl From<api::CourseInstance> for CourseInstance {
292+
fn from(value: api::CourseInstance) -> Self {
293+
Self {
294+
id: value.id,
295+
course_id: value.course_id,
296+
course_slug: value.course_slug,
297+
course_name: value.course_name,
298+
course_description: value.course_description,
299+
instance_name: value.instance_name,
300+
instance_description: value.instance_description,
301+
}
302+
}
303+
}
304+
305+
#[derive(Debug, Serialize)]
306+
#[cfg_attr(feature = "ts-rs", derive(TS))]
307+
pub struct ExerciseTaskSubmissionResult {
308+
pub submission_id: Uuid,
309+
}
310+
311+
impl From<api::ExerciseTaskSubmissionResult> for ExerciseTaskSubmissionResult {
312+
fn from(value: api::ExerciseTaskSubmissionResult) -> Self {
313+
Self {
314+
submission_id: value.submission_id,
315+
}
316+
}
317+
}
318+
319+
#[derive(Debug, Serialize)]
320+
#[cfg_attr(feature = "ts-rs", derive(TS))]
321+
pub enum ExerciseTaskSubmissionStatus {
322+
NoGradingYet,
323+
Grading {
324+
grading_progress: GradingProgress,
325+
score_given: Option<f32>,
326+
grading_started_at: Option<DateTime<Utc>>,
327+
grading_completed_at: Option<DateTime<Utc>>,
328+
feedback_json: Option<serde_json::Value>,
329+
feedback_text: Option<String>,
330+
},
331+
}
332+
333+
impl From<api::ExerciseTaskSubmissionStatus> for ExerciseTaskSubmissionStatus {
334+
fn from(value: api::ExerciseTaskSubmissionStatus) -> Self {
335+
match value {
336+
api::ExerciseTaskSubmissionStatus::NoGradingYet => Self::NoGradingYet,
337+
api::ExerciseTaskSubmissionStatus::Grading {
338+
grading_progress,
339+
score_given,
340+
grading_started_at,
341+
grading_completed_at,
342+
feedback_json,
343+
feedback_text,
344+
} => Self::Grading {
345+
grading_progress: grading_progress.into(),
346+
score_given,
347+
grading_started_at,
348+
grading_completed_at,
349+
feedback_json,
350+
feedback_text,
351+
},
352+
}
353+
}
354+
}
355+
356+
#[derive(Debug, Clone, Copy, Serialize)]
357+
#[cfg_attr(feature = "ts-rs", derive(TS))]
358+
pub enum GradingProgress {
359+
/// The grading could not complete.
360+
Failed,
361+
/// There is no grading process occurring; for example, the student has not yet made any submission.
362+
NotReady,
363+
/// Final Grade is pending, and it does require human intervention; if a Score value is present, it indicates the current value is partial and may be updated during the manual grading.
364+
PendingManual,
365+
/// Final Grade is pending, but does not require manual intervention; if a Score value is present, it indicates the current value is partial and may be updated.
366+
Pending,
367+
/// The grading process is completed; the score value, if any, represents the current Final Grade;
368+
FullyGraded,
369+
}
370+
371+
impl From<api::GradingProgress> for GradingProgress {
372+
fn from(value: api::GradingProgress) -> Self {
373+
match value {
374+
api::GradingProgress::Failed => Self::Failed,
375+
api::GradingProgress::NotReady => Self::NotReady,
376+
api::GradingProgress::PendingManual => Self::PendingManual,
377+
api::GradingProgress::Pending => Self::Pending,
378+
api::GradingProgress::FullyGraded => Self::FullyGraded,
379+
}
380+
}
381+
}

0 commit comments

Comments
 (0)