kbolt-core 0.1.2

Core engine for kbolt local-first retrieval
Documentation
use kbolt_types::KboltError;

use crate::Result;

pub(crate) fn schedule_id_number(id: &str) -> Option<u32> {
    let number = id.strip_prefix('s')?.parse::<u32>().ok()?;
    if number == 0 {
        return None;
    }
    Some(number)
}

pub(crate) fn schedule_id_sort_key(id: &str) -> u32 {
    schedule_id_number(id).unwrap_or(u32::MAX)
}

pub(crate) fn parse_canonical_schedule_time(time: &str) -> Result<(u32, u32)> {
    let (hour, minute) = time
        .split_once(':')
        .ok_or_else(|| KboltError::InvalidInput(format!("invalid schedule time: {time}")))?;
    let hour = hour
        .parse::<u32>()
        .map_err(|_| KboltError::InvalidInput(format!("invalid schedule time: {time}")))?;
    let minute = minute
        .parse::<u32>()
        .map_err(|_| KboltError::InvalidInput(format!("invalid schedule time: {time}")))?;

    if hour > 23 || minute > 59 {
        return Err(KboltError::InvalidInput(format!("invalid schedule time: {time}")).into());
    }

    Ok((hour, minute))
}

#[cfg(test)]
mod tests {
    use super::{parse_canonical_schedule_time, schedule_id_number, schedule_id_sort_key};

    #[test]
    fn schedule_id_number_rejects_invalid_or_zero_ids() {
        assert_eq!(schedule_id_number("s1"), Some(1));
        assert_eq!(schedule_id_number("s10"), Some(10));
        assert_eq!(schedule_id_number("s0"), None);
        assert_eq!(schedule_id_number("schedule-1"), None);
    }

    #[test]
    fn schedule_id_sort_key_sends_invalid_ids_to_end() {
        assert!(schedule_id_sort_key("invalid") > schedule_id_sort_key("s9"));
    }

    #[test]
    fn parse_canonical_schedule_time_rejects_out_of_range_values() {
        let err = parse_canonical_schedule_time("25:99").expect_err("invalid time should fail");
        assert!(err.to_string().contains("invalid schedule time: 25:99"));
    }
}