Skip to content

Commit

Permalink
Separate mooc api types from mooc client types
Browse files Browse the repository at this point in the history
  • Loading branch information
Heliozoa committed Aug 21, 2023
1 parent 72a7d60 commit 9cc73a1
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 23 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ rust-version = "1.70.0"
version = "0.33.0"

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

[patch.'https://github.com/rage/secret-project-331.git']
mooc-langs-api = { path = "../secret-project-331/services/headless-lms/langs-api" }
# [patch.'https://github.com/rage/secret-project-331.git']
# mooc-langs-api = { path = "../secret-project-331/services/headless-lms/langs-api" }
6 changes: 3 additions & 3 deletions crates/tmc-langs-cli/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use tmc_langs::{
use tmc_langs_util::progress_reporter::StatusUpdate;

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

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

#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "kebab-case")]
#[serde(tag = "output-data-kind", content = "output-data")]
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
Expand Down
9 changes: 7 additions & 2 deletions crates/tmc-mooc-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ reqwest = { version = "0.11.18", default-features = false, features = [
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
thiserror = "1.0.40"
ts-rs = { workspace = true, features = ["serde-compat"], optional = true }
ts-rs = { workspace = true, features = [
"chrono-impl",
"serde-compat",
"serde-json-impl",
"uuid-impl",
], optional = true }
uuid = { version = "1.3.3", features = ["serde", "v4"] }

[dev-dependencies]
simple_logger = "4.0.0"

[features]
ts-rs = ["dep:ts-rs", "mooc-langs-api/ts_rs"]
ts-rs = ["dep:ts-rs"]
135 changes: 121 additions & 14 deletions crates/tmc-mooc-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ pub use self::exercise::{
};
use crate::error::{MoocClientError, MoocClientResult};
use bytes::Bytes;
use chrono::{DateTime, Utc};
use exercise::UserAnswer;
pub use mooc_langs_api::*;
pub use mooc_langs_api as api;
use oauth2::TokenResponse;
use reqwest::{
blocking::{
Expand All @@ -23,6 +24,8 @@ use reqwest::{
use serde::{de::DeserializeOwned, Serialize};
use std::{borrow::Cow, path::Path, sync::Arc};
use tmc_langs_util::{serialize, JsonError};
#[cfg(feature = "ts-rs")]
use ts_rs::TS;
use uuid::Uuid;

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

/// Non-API methods.
Expand Down Expand Up @@ -78,7 +81,7 @@ impl MoocClient {
}
}

pub fn set_token(&mut self, token: Token) {
pub fn set_token(&mut self, token: api::Token) {
Arc::get_mut(&mut self.0)
.expect("called when multiple clones exist")
.token = Some(token);
Expand All @@ -90,8 +93,8 @@ impl MoocClient {
pub fn course_instances(&self) -> MoocClientResult<Vec<CourseInstance>> {
let res = self
.request(Method::GET, "course-instances")
.send_expect_json()?;
Ok(res)
.send_expect_json::<Vec<api::CourseInstance>>()?;
Ok(res.into_iter().map(Into::into).collect())
}

pub fn course_instance_exercise_slides(
Expand All @@ -101,7 +104,7 @@ impl MoocClient {
let url = format!("course-instances/{course_instance}/exercises");
let res = self
.request(Method::GET, &url)
.send_expect_json::<Vec<ExerciseSlide>>()?
.send_expect_json::<Vec<api::ExerciseSlide>>()?
.into_iter()
.map(TryFrom::try_from)
.collect::<Result<_, JsonError>>()
Expand All @@ -116,7 +119,7 @@ impl MoocClient {
let url = format!("exercises/{exercise}");
let res = self
.request(Method::GET, &url)
.send_expect_json::<ExerciseSlide>()?
.send_expect_json::<api::ExerciseSlide>()?
.try_into()
.map_err(|err: JsonError| MoocClientError::DeserializingResponse {
url,
Expand Down Expand Up @@ -145,7 +148,7 @@ impl MoocClient {
archive: &Path,
) -> MoocClientResult<ExerciseTaskSubmissionResult> {
// upload archive
let metadata = UploadMetadata { slide_id, task_id };
let metadata = api::UploadMetadata { slide_id, task_id };
let metadata = serialize::to_json_vec(&metadata)?;
let form = Form::new()
.part(
Expand All @@ -159,23 +162,23 @@ impl MoocClient {
let res = self
.request(Method::POST, &format!("exercises/{exercise_id}/upload"))
.multipart(form)
.send_expect_json::<UploadResult>()?;
.send_expect_json::<api::UploadResult>()?;

// send submission
let user_answer = UserAnswer::Editor {
download_url: res.download_url,
};
let data_json = serialize::to_json_value(&user_answer)?;
let exercise_slide_submission = ExerciseSlideSubmission {
let exercise_slide_submission = api::ExerciseSlideSubmission {
exercise_slide_id: slide_id,
exercise_task_id: task_id,
data_json,
};
let res = self
.request(Method::POST, &format!("exercises/{exercise_id}/submit"))
.json(&exercise_slide_submission)
.send_expect_json()?;
Ok(res)
.send_expect_json::<api::ExerciseTaskSubmissionResult>()?;
Ok(res.into())
}

pub fn get_submission_grading(
Expand All @@ -184,8 +187,8 @@ impl MoocClient {
) -> MoocClientResult<ExerciseTaskSubmissionStatus> {
let res = self
.request(Method::GET, &format!("submissions/{submission_id}/grading"))
.send_expect_json()?;
Ok(res)
.send_expect_json::<api::ExerciseTaskSubmissionStatus>()?;
Ok(res.into())
}
}

Expand Down Expand Up @@ -272,3 +275,107 @@ impl MoocRequest {
Ok(json)
}
}

#[derive(Debug, Serialize)]
#[cfg_attr(feature = "ts-rs", derive(TS))]
pub struct CourseInstance {
pub id: Uuid,
pub course_id: Uuid,
pub course_slug: String,
pub course_name: String,
pub course_description: Option<String>,
pub instance_name: Option<String>,
pub instance_description: Option<String>,
}

impl From<api::CourseInstance> for CourseInstance {
fn from(value: api::CourseInstance) -> Self {
Self {
id: value.id,
course_id: value.course_id,
course_slug: value.course_slug,
course_name: value.course_name,
course_description: value.course_description,
instance_name: value.instance_name,
instance_description: value.instance_description,
}
}
}

#[derive(Debug, Serialize)]
#[cfg_attr(feature = "ts-rs", derive(TS))]
pub struct ExerciseTaskSubmissionResult {
pub submission_id: Uuid,
}

impl From<api::ExerciseTaskSubmissionResult> for ExerciseTaskSubmissionResult {
fn from(value: api::ExerciseTaskSubmissionResult) -> Self {
Self {
submission_id: value.submission_id,
}
}
}

#[derive(Debug, Serialize)]
#[cfg_attr(feature = "ts-rs", derive(TS))]
pub enum ExerciseTaskSubmissionStatus {
NoGradingYet,
Grading {
grading_progress: GradingProgress,
score_given: Option<f32>,
grading_started_at: Option<DateTime<Utc>>,
grading_completed_at: Option<DateTime<Utc>>,
feedback_json: Option<serde_json::Value>,
feedback_text: Option<String>,
},
}

impl From<api::ExerciseTaskSubmissionStatus> for ExerciseTaskSubmissionStatus {
fn from(value: api::ExerciseTaskSubmissionStatus) -> Self {
match value {
api::ExerciseTaskSubmissionStatus::NoGradingYet => Self::NoGradingYet,
api::ExerciseTaskSubmissionStatus::Grading {
grading_progress,
score_given,
grading_started_at,
grading_completed_at,
feedback_json,
feedback_text,
} => Self::Grading {
grading_progress: grading_progress.into(),
score_given,
grading_started_at,
grading_completed_at,
feedback_json,
feedback_text,
},
}
}
}

#[derive(Debug, Clone, Copy, Serialize)]
#[cfg_attr(feature = "ts-rs", derive(TS))]
pub enum GradingProgress {
/// The grading could not complete.
Failed,
/// There is no grading process occurring; for example, the student has not yet made any submission.
NotReady,
/// 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.
PendingManual,
/// 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.
Pending,
/// The grading process is completed; the score value, if any, represents the current Final Grade;
FullyGraded,
}

impl From<api::GradingProgress> for GradingProgress {
fn from(value: api::GradingProgress) -> Self {
match value {
api::GradingProgress::Failed => Self::Failed,
api::GradingProgress::NotReady => Self::NotReady,
api::GradingProgress::PendingManual => Self::PendingManual,
api::GradingProgress::Pending => Self::Pending,
api::GradingProgress::FullyGraded => Self::FullyGraded,
}
}
}

0 comments on commit 9cc73a1

Please sign in to comment.