use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_with::serde_as;
use crate::{
errors,
errors::mycqu::MyCQUResult,
mycqu::{
course::{CQUSession, Course},
utils::{check_website_response, mycqu_request_handler},
},
session::{Client, Session},
utils::{
ApiModel,
consts::{MYCQU_API_GPA_RANKING_URL, MYCQU_API_SCORE_URL},
response_json_map,
},
};
#[serde_as]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct Score {
#[serde(alias = "sessionName")]
#[serde_as(deserialize_as = "serde_with::PickFirst<(_, serde_with::DisplayFromStr)>")]
pub session: CQUSession,
#[serde(flatten)]
pub course: Course,
#[serde_as(deserialize_as = "serde_with::FromInto<ScoreField>")]
#[serde(flatten)]
pub score: Option<String>,
#[serde(alias = "studyNature")]
pub study_nature: String,
#[serde(alias = "courseNature")]
pub course_nature: String,
}
serde_fallback!(
ScoreField,
String,
score,
fallback = [effectiveScoreShow],
apply = [
#[serde_with::apply(
_ => #[serde_as(deserialize_as = "serde_with::DefaultOnError")]
)]
]
);
impl Score {
pub async fn fetch_self(
client: &Client,
session: &Session,
is_minor: bool,
) -> MyCQUResult<Vec<Self>> {
let response = mycqu_request_handler(client, session, |client| {
client
.get(MYCQU_API_SCORE_URL)
.query(&[("isMinorBoo", is_minor)])
})
.await?;
let (mut res, raw_response) = response_json_map(response).await?;
check_website_response(&res)?;
res.get_mut("data")
.and_then(Value::as_object_mut)
.ok_or_else(|| errors::ApiError::ModelParse {
msg: "Excepted field \"data\" is missing or not an object".to_string(),
raw_response: raw_response.clone(),
})?
.values_mut()
.map(|obj| {
obj.get_mut("stuScoreHomePgVoS")
.and_then(Value::as_array_mut)
.ok_or_else(|| errors::ApiError::ModelParse {
msg: "Failed to parse score list".to_string(),
raw_response: raw_response.clone(),
})
.and_then(|array| ApiModel::parse_json_array(array, &raw_response))
})
.try_fold(Vec::<Self>::new(), |mut acc, item| {
acc.extend(item?);
Ok(acc)
})
}
}
impl ApiModel for Score {}
#[serde_as]
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub struct GPARanking {
#[serde_as(deserialize_as = "serde_with::PickFirst<(_, serde_with::DisplayFromStr)>")]
pub gpa: f32,
#[serde_as(deserialize_as = "Option<serde_with::PickFirst<(_, serde_with::DisplayFromStr)>>")]
#[serde(alias = "majorRanking")]
pub major_ranking: Option<u16>,
#[serde_as(deserialize_as = "Option<serde_with::PickFirst<(_, serde_with::DisplayFromStr)>>")]
#[serde(alias = "gradeRanking")]
pub grade_ranking: Option<u16>,
#[serde_as(deserialize_as = "Option<serde_with::PickFirst<(_, serde_with::DisplayFromStr)>>")]
#[serde(alias = "classRanking")]
pub class_ranking: Option<u16>,
#[serde_as(deserialize_as = "serde_with::PickFirst<(_, serde_with::DisplayFromStr)>")]
#[serde(alias = "weightedAvg")]
pub weighted_avg: f32,
#[serde_as(deserialize_as = "Option<serde_with::PickFirst<(_, serde_with::DisplayFromStr)>>")]
#[serde(alias = "minorWeightedAvg")]
pub minor_weighted_avg: Option<f32>,
#[serde_as(deserialize_as = "Option<serde_with::PickFirst<(_, serde_with::DisplayFromStr)>>")]
#[serde(alias = "minorGpa")]
pub minor_gpa: Option<f32>,
}
impl GPARanking {
pub async fn fetch_self(client: &Client, session: &Session) -> MyCQUResult<Self> {
let response = mycqu_request_handler(client, session, |client| {
client
.get(MYCQU_API_GPA_RANKING_URL)
.query(&[("isMinorBoo", false)])
})
.await?;
let (mut res, raw_response) = response_json_map(response).await?;
check_website_response(&res)?;
Self::extract_object(&mut res, "data", &raw_response)
}
}
impl ApiModel for GPARanking {}