use std::fmt::{Display, Formatter};
use serde::Serialize;
use thiserror::Error;
pub type Result<T, E = WrapperError> = std::result::Result<T, E>;
pub type Schedule = Vec<ScheduledSection>;
pub type Courses = Vec<CourseSection>;
pub type SearchResult = Vec<SearchResultItem>;
pub type Events = Vec<Event>;
pub type TimeType = u32;
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct SearchResultItem {
pub subj_code: String,
pub course_code: String,
pub course_title: String,
}
impl Display for SearchResultItem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"{} {} - {}",
self.subj_code, self.course_code, self.course_title
)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct CourseSection {
pub subj_course_id: String,
pub section_id: String,
pub section_code: String,
pub all_instructors: Vec<String>,
pub available_seats: i64,
pub enrolled_ct: i64,
pub total_seats: i64,
pub waitlist_ct: i64,
pub meetings: Vec<Meeting>,
pub is_visible: bool,
}
impl CourseSection {
pub fn has_seats(&self) -> bool {
self.available_seats > 0 && self.waitlist_ct == 0
}
}
impl Display for CourseSection {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"[{} / {}] {}",
self.section_code, self.section_id, self.subj_course_id
)?;
writeln!(f, "\tInstructors: [{}]", self.all_instructors.join(", "))?;
writeln!(f, "\tEnrolled: {}", self.enrolled_ct)?;
writeln!(f, "\tAvailable: {}", self.available_seats)?;
writeln!(f, "\tWaitlist: {}", self.waitlist_ct)?;
writeln!(f, "\tTotal Seats: {}", self.total_seats)?;
writeln!(f, "\tCan Enroll? {}", self.has_seats())?;
writeln!(f, "\tMeeting Information:")?;
for meeting in &self.meetings {
write!(f, "\t\t{meeting}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct Meeting {
pub meeting_type: String,
#[serde(rename = "meeting_days")]
pub meeting_days: MeetingDay,
pub start_hr: TimeType,
pub start_min: TimeType,
pub end_hr: TimeType,
pub end_min: TimeType,
pub building: String,
pub room: String,
pub instructors: Vec<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
#[serde(untagged)]
pub enum MeetingDay {
Repeated(Vec<String>),
OneTime(String),
None,
}
impl Display for Meeting {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] ", self.meeting_type)?;
match &self.meeting_days {
MeetingDay::Repeated(r) => write!(f, "{} ", r.join("")),
MeetingDay::OneTime(r) => write!(f, "{} ", r),
MeetingDay::None => write!(f, "N/A "),
}?;
write!(
f,
"at {}:{:02} - {}:{:02} ",
self.start_hr, self.start_min, self.end_hr, self.end_min
)?;
writeln!(f, "in {} {}", self.building, self.room)?;
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct ScheduledSection {
pub section_id: String,
pub subject_code: String,
pub course_code: String,
pub course_title: String,
pub section_code: String,
pub section_capacity: i64,
pub enrolled_count: i64,
pub available_seats: i64,
pub grade_option: String,
pub all_instructors: Vec<String>,
pub units: i64,
#[serde(rename = "enrolled_status")]
pub enrolled_status: EnrollmentStatus,
pub waitlist_ct: i64,
pub meetings: Vec<Meeting>,
}
impl Display for ScheduledSection {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"[{} / {}] {} {}: {}",
self.section_code,
self.section_id,
self.section_code,
self.course_code,
self.course_title
)?;
writeln!(f, "\tInstructors: [{}]", self.all_instructors.join(", "))?;
writeln!(f, "\tCourse Enrollment Information:")?;
writeln!(f, "\t\tEnrolled: {}", self.enrolled_count)?;
writeln!(f, "\t\tAvailable: {}", self.available_seats)?;
writeln!(f, "\t\tWaitlist: {}", self.waitlist_ct)?;
writeln!(f, "\t\tTotal Seats: {}", self.section_capacity)?;
writeln!(f, "\tEnrollment Information:")?;
write!(f, "\t\tStatus: ")?;
match self.enrolled_status {
EnrollmentStatus::Enrolled => writeln!(f, "Enrolled"),
EnrollmentStatus::Waitlist { waitlist_pos } => {
writeln!(f, "Waitlisted (Position {waitlist_pos})")
}
EnrollmentStatus::Planned => writeln!(f, "Planned"),
EnrollmentStatus::Unknown => writeln!(f, "Unknown"),
}?;
writeln!(f, "\t\tUnits: {}", self.units)?;
writeln!(f, "\t\tGrade Option: {}", self.grade_option)?;
writeln!(f, "\tMeeting Information:")?;
for meeting in &self.meetings {
write!(f, "\t\t{meeting}")?;
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
#[serde(tag = "enroll_status")]
pub enum EnrollmentStatus {
Enrolled,
Waitlist { waitlist_pos: i64 },
Planned,
Unknown,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct PrerequisiteInfo {
pub course_prerequisites: Vec<Vec<CoursePrerequisite>>,
pub exam_prerequisites: Vec<String>,
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize)]
pub struct CoursePrerequisite {
pub subj_course_id: String,
pub course_title: String,
}
impl CoursePrerequisite {
pub fn new(subj_course_id: impl Into<String>, course_title: impl Into<String>) -> Self {
Self {
subj_course_id: subj_course_id.into(),
course_title: course_title.into(),
}
}
}
#[derive(Debug, Clone, Serialize, Eq, PartialEq, Hash)]
pub struct Event {
pub location: String,
pub start_hr: TimeType,
pub start_min: TimeType,
pub end_hr: TimeType,
pub end_min: TimeType,
pub name: String,
pub days: Vec<String>,
pub timestamp: String,
}
impl Display for Event {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
writeln!(f, "[Event] {}", self.name)?;
writeln!(f, "\tLocation: {}", self.location)?;
writeln!(f, "\tDay of Week: {}", self.days.join(""))?;
writeln!(
f,
"\tTime: {}:{:02} - {}:{:02}",
self.start_hr, self.start_min, self.end_hr, self.end_min
)?;
writeln!(f, "\tTimestamp: {}", self.timestamp)?;
Ok(())
}
}
#[derive(Error, Debug)]
pub enum WrapperError {
#[error("Request error occurred: {0}")]
RequestError(#[from] reqwest::Error),
#[error("Malformed url: {0}")]
UrlParseError(#[from] url::ParseError),
#[error("Invalid input for '{0}' provided: {1}")]
InputError(&'static str, &'static str),
#[error("Serde error occurred: {0}")]
SerdeError(#[from] serde_json::Error),
#[error("Unsuccessful status code: {0} (context: {1:?})")]
BadStatusCode(u16, Option<String>),
#[error("A time value, either minute or hour, is not formatted correctly.")]
BadTimeError,
#[error("Error from WebReg: {0}")]
WebRegError(String),
#[error("Section ID not found: {0} (context: {1}")]
SectionIdNotFound(String, SectionIdNotFoundContext),
#[error("An error occurred when parsing the response from WebReg: {0}")]
WrapperParsingError(String),
#[error("The current session is not valid. Are your cookies valid?")]
SessionNotValid,
}
#[derive(Debug)]
pub enum SectionIdNotFoundContext {
Schedule,
Catalog,
}
impl Display for SectionIdNotFoundContext {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SectionIdNotFoundContext::Schedule => write!(f, "Schedule"),
SectionIdNotFoundContext::Catalog => write!(f, "Offered"),
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct Term {
pub seq_id: i64,
pub term_code: String,
}