use core::fmt;
use crate::EntityId;
#[derive(Debug, Clone)]
pub enum ValidationError {
InvalidReference {
source_entity_type: &'static str,
source_id: EntityId,
field_name: &'static str,
referenced_id: EntityId,
expected_type: &'static str,
},
DuplicateId {
entity_type: &'static str,
id: EntityId,
},
CascadeCycle {
cycle_ids: Vec<EntityId>,
},
InvalidFillingConfig {
hydro_id: EntityId,
reason: String,
},
DisconnectedBus {
bus_id: EntityId,
},
InvalidPenalty {
entity_type: &'static str,
entity_id: EntityId,
field_name: &'static str,
reason: String,
},
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidReference {
source_entity_type,
source_id,
field_name,
referenced_id,
expected_type,
} => write!(
f,
"{source_entity_type} with id {source_id} has invalid cross-reference \
in field '{field_name}': referenced {expected_type} id {referenced_id} does not exist"
),
Self::DuplicateId { entity_type, id } => {
write!(f, "duplicate {entity_type} id: {id}")
}
Self::CascadeCycle { cycle_ids } => {
let ids = cycle_ids
.iter()
.map(EntityId::to_string)
.collect::<Vec<_>>()
.join(", ");
write!(f, "hydro cascade contains a cycle: [{ids}]")
}
Self::InvalidFillingConfig { hydro_id, reason } => {
write!(
f,
"hydro {hydro_id} has invalid filling configuration: {reason}"
)
}
Self::DisconnectedBus { bus_id } => {
write!(
f,
"bus {bus_id} is disconnected (no lines, generators, or loads)"
)
}
Self::InvalidPenalty {
entity_type,
entity_id,
field_name,
reason,
} => write!(
f,
"{entity_type} with id {entity_id} has invalid penalty in field '{field_name}': {reason}"
),
}
}
}
impl std::error::Error for ValidationError {}
#[cfg(test)]
mod tests {
use super::ValidationError;
use crate::EntityId;
#[test]
fn test_display_invalid_reference() {
let err = ValidationError::InvalidReference {
source_entity_type: "Hydro",
source_id: EntityId(3),
field_name: "bus_id",
referenced_id: EntityId(99),
expected_type: "Bus",
};
let msg = err.to_string();
assert!(msg.contains("Hydro"), "missing source entity type: {msg}");
assert!(msg.contains("bus_id"), "missing field name: {msg}");
assert!(msg.contains("99"), "missing referenced id: {msg}");
}
#[test]
fn test_display_duplicate_id() {
let err = ValidationError::DuplicateId {
entity_type: "Thermal",
id: EntityId(5),
};
let msg = err.to_string();
assert!(msg.contains("Thermal"), "missing entity type: {msg}");
assert!(msg.contains('5'), "missing id: {msg}");
}
#[test]
fn test_display_cascade_cycle() {
let err = ValidationError::CascadeCycle {
cycle_ids: vec![EntityId(1), EntityId(2), EntityId(3)],
};
let msg = err.to_string();
assert!(msg.contains('1'), "missing id 1: {msg}");
assert!(msg.contains('2'), "missing id 2: {msg}");
assert!(msg.contains('3'), "missing id 3: {msg}");
}
#[test]
fn test_error_trait() {
let err = ValidationError::DisconnectedBus {
bus_id: EntityId(7),
};
let _: &dyn std::error::Error = &err;
}
}