use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use serde_with::serde_as;
use snafu::OptionExt;
use crate::{
errors,
errors::mycqu::MyCQUResult,
mycqu::{
course::{CQUSession, Course},
utils::{check_website_response, mycqu_request_handler},
},
session::Session,
utils::{
ApiModel,
consts::{MYCQU_API_GPA_RANKING_URL, MYCQU_API_SCORE_URL},
},
};
#[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(session: &Session, is_minor: bool) -> MyCQUResult<Vec<Self>> {
let mut res = mycqu_request_handler(session, |client| {
client
.get(MYCQU_API_SCORE_URL)
.query(&[("isMinorBoo", is_minor)])
})
.await?
.json::<Map<String, Value>>()
.await?;
check_website_response(&res)?;
res.get_mut("data")
.and_then(Value::as_object_mut)
.context(errors::ModelParseSnafu {
msg: "Excepted field \"data\" is missing or not an object".to_string(),
})?
.values_mut()
.map(|obj| {
obj.get_mut("stuScoreHomePgVoS")
.and_then(Value::as_array_mut)
.context(errors::ModelParseSnafu {
msg: "Failed to parse score list".to_string(),
})
.and_then(ApiModel::parse_json_array)
})
.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(session: &Session) -> MyCQUResult<Self> {
let mut res = mycqu_request_handler(session, |client| {
client
.get(MYCQU_API_GPA_RANKING_URL)
.query(&[("isMinorBoo", false)])
})
.await?
.json::<Map<String, Value>>()
.await?;
check_website_response(&res)?;
Self::extract_object(&mut res, "data")
}
}
impl ApiModel for GPARanking {}