ubs 0.1.1

a CLI for fetching real-time University at Buffalo class schedules
use chrono::NaiveDate;
use serde::Serialize;
use ubs_lib::parser;

#[derive(Debug, Serialize)]
pub struct ClassSchedule {
    pub groups: Vec<ClassGroup>,
}

#[derive(Debug, Serialize)]
pub struct ClassGroup {
    pub session: Option<u32>,
    pub start_date: Option<NaiveDate>,
    pub end_date: Option<NaiveDate>,
    pub classes: Vec<Class>,
}

#[derive(Debug, Serialize)]
pub struct Class {
    // TODO: use parser::ClassType?
    pub is_open: Option<bool>,
    pub class_type: Option<String>,
    pub class_id: Option<u32>,
    pub section: Option<String>,
    pub days_of_week: Option<Vec<Option<String>>>,
    pub room: Option<String>,
    pub instructor: Option<String>,
    pub open_seats: Option<u32>,
    pub total_seats: Option<u32>,
}

impl TryFrom<parser::ClassSchedule> for ClassSchedule {
    type Error = parser::ParseError;

    fn try_from(schedule: parser::ClassSchedule) -> Result<Self, Self::Error> {
        let mut groups = Vec::new();
        for group in schedule.group_iter() {
            groups.push(ClassGroup::try_from(group)?);
        }

        Ok(ClassSchedule { groups })
    }
}

impl TryFrom<parser::ClassGroup<'_>> for ClassGroup {
    type Error = parser::ParseError;

    fn try_from(group: parser::ClassGroup<'_>) -> Result<Self, Self::Error> {
        let mut classes = Vec::new();
        for class in group.class_iter() {
            classes.push(Class::try_from(class)?);
        }

        Ok(ClassGroup {
            session: group.session().ok(),
            start_date: group.start_date().ok(),
            end_date: group.end_date().ok(),
            classes,
        })
    }
}

impl TryFrom<parser::Class<'_>> for Class {
    type Error = parser::ParseError;

    fn try_from(class: parser::Class<'_>) -> Result<Self, Self::Error> {
        Ok(Class {
            is_open: class.is_open().ok(),
            class_type: class
                .class_type()
                .ok()
                .map(|class_type| class_type.to_string()),
            class_id: class.class_id().ok(),
            section: class.section().ok().map(ToOwned::to_owned),
            days_of_week: class.days_of_week().ok().flatten().map(|dow| {
                dow.iter()
                    .map(|dow| match dow {
                        Ok(dow) => Some(ToString::to_string(dow)),
                        Err(_) => None,
                    })
                    .collect()
            }),
            room: class.room().ok().map(ToOwned::to_owned),
            instructor: class.instructor().ok().map(ToOwned::to_owned),
            open_seats: class.open_seats().ok().flatten(),
            total_seats: class.total_seats().ok().flatten(),
        })
    }
}