use std::io; use log::Level; use serde::Serialize; use thiserror::Error; #[derive(Error, Debug, Serialize)] #[serde(tag = "kind", content = "details")] pub enum SearchError { #[error("another thread found a solution")] SolutionFound, #[error("time limit exceeded")] Timeout, #[error("schedule is already manually filled")] ScheduleFull, #[error("user configuration is invalid")] Config(String), #[error("no solution found")] NoSolutionFound, } impl SearchError { pub fn log_level(&self) -> Level { match self { SearchError::SolutionFound | SearchError::ScheduleFull => Level::Info, SearchError::NoSolutionFound => Level::Warn, SearchError::Timeout | SearchError::Config(_) => Level::Error, } } } impl From for SearchError { fn from(err: anyhow::Error) -> Self { SearchError::Config(err.to_string()) } } #[derive(Error, Debug)] pub enum ExportError { #[error("no schedule has been generated yet")] NotGenerated, #[error("path not found: {0}")] InvalidPath(#[from] io::Error), #[error("docx packaging error: {0}")] Packaging(String), #[error("failed to open doc: {0}")] OpenFailed(String), } #[derive(Error, Debug, Serialize)] #[serde(tag = "kind", content = "details")] pub enum SwapError { #[error("no schedule has been generated yet")] NotGenerated, #[error("invalid shift position: {0}")] InvalidPosition(String), #[error("slot has no assigned resident")] SlotEmpty, #[error("both slots belong to the same resident")] SameResident, #[error("resident not found in config")] ResidentNotFound, #[error("resident cannot take the target slot: negative shift or disallowed type")] ResidentCannotTakeSlot, #[error("swap violates same-day or toxic pair constraint")] SameDayOrToxicPair, #[error("swap creates a consecutive-day constraint violation")] ConsecutiveDays, #[error("cannot swap the same slot or slots on the same day")] SameSlotOrDay, } impl Serialize for ExportError { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { use serde::ser::SerializeStruct; let mut s = serializer.serialize_struct("ExportError", 2)?; s.serialize_field( "kind", match self { ExportError::NotGenerated => "NotGenerated", ExportError::InvalidPath(_) => "InvalidPath", ExportError::Packaging(_) => "Packaging", ExportError::OpenFailed(_) => "OpenFailed", }, )?; s.serialize_field("details", &self.to_string())?; s.end() } }