zinzen 0.3.0

Algorithm for auto-scheduling time-constrained tasks on a timeline
//! The scheduler's error type.
//!
//! ADR-0002 (see `adr/0002-fallible-api-and-error-migration.md`): the scheduler
//! is a library compiled to WASM, where a `panic!` becomes an unrecoverable
//! exception for the whole UI call. Recoverable / input-driven failures are
//! therefore reported as a typed [`SchedulerError`] instead of panicking.
//!
//! `SchedulerError` implements [`std::error::Error`], so it converts cleanly to
//! `wasm_bindgen::JsError` at the WASM boundary via the `?` operator.

use std::fmt;

use chrono::NaiveDateTime;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SchedulerError {
    /// A user-provided datetime lies more than one day outside the calendar
    /// window `[start, end]`.
    DateTimeOutOfBounds {
        what: &'static str,
        datetime: NaiveDateTime,
        start: NaiveDateTime,
        end: NaiveDateTime,
    },
    /// A budget has no periods.
    BudgetMissingPeriods { budget_id: String },
    /// A goal references a budget id that is not in `Input.budgets`.
    UnknownBudgetId { goal_id: String, budget_id: String },
    /// Duplicate budget ids in `Input.budgets`.
    DuplicateBudgetId { budget_id: String },
    /// `min > max` on a week or period bound.
    InvalidBudgetBounds {
        budget_id: String,
        what: &'static str,
    },
    /// Input `schemaVersion` is not supported.
    UnsupportedSchemaVersion { version: u32 },
    /// A numeric field is not divisible by the slot size (5 minutes).
    NotSlotAligned {
        field: &'static str,
        value: usize,
        slot_minutes: usize,
    },
    /// A datetime is not aligned to the slot grid.
    DateTimeNotAligned {
        what: &'static str,
        datetime: NaiveDateTime,
        slot_minutes: usize,
    },
    /// Calendar span is not a whole number of slots.
    CalendarSpanNotAligned {
        span_minutes: i64,
        slot_minutes: usize,
    },
    /// A numeric value overflowed during unit conversion.
    ValueOutOfRange { field: &'static str, value: usize },
}

impl fmt::Display for SchedulerError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SchedulerError::DateTimeOutOfBounds {
                what,
                datetime,
                start,
                end,
            } => write!(
                f,
                "{what} datetime {datetime:?} is more than one day outside the calendar window [{start:?}, {end:?}]"
            ),
            SchedulerError::BudgetMissingPeriods { budget_id } => {
                write!(f, "budget {budget_id:?} must have at least one period")
            }
            SchedulerError::UnknownBudgetId { goal_id, budget_id } => write!(
                f,
                "goal {goal_id:?} references unknown budgetId {budget_id:?}"
            ),
            SchedulerError::DuplicateBudgetId { budget_id } => {
                write!(f, "duplicate budget id {budget_id:?}")
            }
            SchedulerError::InvalidBudgetBounds { budget_id, what } => {
                write!(f, "budget {budget_id:?} has invalid bounds: {what}")
            }
            SchedulerError::UnsupportedSchemaVersion { version } => {
                write!(f, "unsupported schemaVersion {version}")
            }
            SchedulerError::NotSlotAligned {
                field,
                value,
                slot_minutes,
            } => write!(
                f,
                "{field} value {value} is not divisible by slot size {slot_minutes} minutes"
            ),
            SchedulerError::DateTimeNotAligned {
                what,
                datetime,
                slot_minutes,
            } => write!(
                f,
                "{what} datetime {datetime:?} is not aligned to {slot_minutes}-minute slots"
            ),
            SchedulerError::CalendarSpanNotAligned {
                span_minutes,
                slot_minutes,
            } => write!(
                f,
                "calendar span of {span_minutes} minutes is not divisible by slot size {slot_minutes} minutes"
            ),
            SchedulerError::ValueOutOfRange { field, value } => {
                write!(
                    f,
                    "{field} value {value} is out of range after unit conversion"
                )
            }
        }
    }
}

impl std::error::Error for SchedulerError {}