1use crate::entity::EntityId;
4use crate::ids::GroupId;
5use crate::stop::StopId;
6use ordered_float::OrderedFloat;
7use std::fmt;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
11#[non_exhaustive]
12pub enum SimError {
13 InvalidConfig {
15 field: &'static str,
17 reason: String,
19 },
20 EntityNotFound(EntityId),
22 StopNotFound(StopId),
24 GroupNotFound(GroupId),
26 InvalidState {
28 entity: EntityId,
30 reason: String,
32 },
33 LineNotFound(EntityId),
35 NoRoute {
37 origin: EntityId,
39 destination: EntityId,
41 origin_groups: Vec<GroupId>,
43 destination_groups: Vec<GroupId>,
45 },
46 AmbiguousRoute {
48 origin: EntityId,
50 destination: EntityId,
52 groups: Vec<GroupId>,
54 },
55}
56
57impl fmt::Display for SimError {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Self::InvalidConfig { field, reason } => {
61 write!(f, "invalid config '{field}': {reason}")
62 }
63 Self::EntityNotFound(id) => write!(f, "entity not found: {id:?}"),
64 Self::StopNotFound(id) => write!(f, "stop not found: {id}"),
65 Self::GroupNotFound(id) => write!(f, "group not found: {id}"),
66 Self::InvalidState { entity, reason } => {
67 write!(f, "invalid state for {entity:?}: {reason}")
68 }
69 Self::LineNotFound(id) => write!(f, "line entity {id:?} not found"),
70 Self::NoRoute {
71 origin,
72 destination,
73 origin_groups,
74 destination_groups,
75 } => {
76 write!(
77 f,
78 "no route from {origin:?} to {destination:?} (origin served by {}, destination served by {})",
79 format_group_list(origin_groups),
80 format_group_list(destination_groups),
81 )
82 }
83 Self::AmbiguousRoute {
84 origin,
85 destination,
86 groups,
87 } => {
88 write!(
89 f,
90 "ambiguous route from {origin:?} to {destination:?}: served by groups {}",
91 format_group_list(groups),
92 )
93 }
94 }
95 }
96}
97
98impl std::error::Error for SimError {
99 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
100 None
101 }
102}
103
104fn format_group_list(groups: &[GroupId]) -> String {
106 if groups.is_empty() {
107 return "[]".to_string();
108 }
109 let parts: Vec<String> = groups.iter().map(GroupId::to_string).collect();
110 format!("[{}]", parts.join(", "))
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
115#[non_exhaustive]
116pub enum RejectionReason {
117 OverCapacity,
119 PreferenceBased,
121 AccessDenied,
123}
124
125impl fmt::Display for RejectionReason {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 match self {
128 Self::OverCapacity => write!(f, "over capacity"),
129 Self::PreferenceBased => write!(f, "rider preference"),
130 Self::AccessDenied => write!(f, "access denied"),
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
140pub struct RejectionContext {
141 pub attempted_weight: OrderedFloat<f64>,
143 pub current_load: OrderedFloat<f64>,
145 pub capacity: OrderedFloat<f64>,
147}
148
149impl fmt::Display for RejectionContext {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 let excess = (*self.current_load + *self.attempted_weight) - *self.capacity;
164 if excess > 0.0 {
165 write!(
166 f,
167 "over capacity by {excess:.1}kg ({:.1}/{:.1} + {:.1})",
168 *self.current_load, *self.capacity, *self.attempted_weight,
169 )
170 } else {
171 write!(
172 f,
173 "load {:.1}kg/{:.1}kg + {:.1}kg",
174 *self.current_load, *self.capacity, *self.attempted_weight,
175 )
176 }
177 }
178}
179
180impl From<EntityId> for SimError {
181 fn from(id: EntityId) -> Self {
182 Self::EntityNotFound(id)
183 }
184}
185
186impl From<StopId> for SimError {
187 fn from(id: StopId) -> Self {
188 Self::StopNotFound(id)
189 }
190}
191
192impl From<GroupId> for SimError {
193 fn from(id: GroupId) -> Self {
194 Self::GroupNotFound(id)
195 }
196}