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 SnapshotVersion {
57 saved: String,
59 current: String,
61 },
62 SnapshotFormat(String),
64}
65
66impl fmt::Display for SimError {
67 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68 match self {
69 Self::InvalidConfig { field, reason } => {
70 write!(f, "invalid config '{field}': {reason}")
71 }
72 Self::EntityNotFound(id) => write!(f, "entity not found: {id:?}"),
73 Self::StopNotFound(id) => write!(f, "stop not found: {id}"),
74 Self::GroupNotFound(id) => write!(f, "group not found: {id}"),
75 Self::InvalidState { entity, reason } => {
76 write!(f, "invalid state for {entity:?}: {reason}")
77 }
78 Self::LineNotFound(id) => write!(f, "line entity {id:?} not found"),
79 Self::NoRoute {
80 origin,
81 destination,
82 origin_groups,
83 destination_groups,
84 } => {
85 write!(
86 f,
87 "no route from {origin:?} to {destination:?} (origin served by {}, destination served by {})",
88 format_group_list(origin_groups),
89 format_group_list(destination_groups),
90 )
91 }
92 Self::AmbiguousRoute {
93 origin,
94 destination,
95 groups,
96 } => {
97 write!(
98 f,
99 "ambiguous route from {origin:?} to {destination:?}: served by groups {}",
100 format_group_list(groups),
101 )
102 }
103 Self::SnapshotVersion { saved, current } => {
104 write!(
105 f,
106 "snapshot was saved on elevator-core {saved}, but current version is {current}",
107 )
108 }
109 Self::SnapshotFormat(reason) => write!(f, "malformed snapshot: {reason}"),
110 }
111 }
112}
113
114impl std::error::Error for SimError {
115 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
116 None
117 }
118}
119
120fn format_group_list(groups: &[GroupId]) -> String {
122 if groups.is_empty() {
123 return "[]".to_string();
124 }
125 let parts: Vec<String> = groups.iter().map(GroupId::to_string).collect();
126 format!("[{}]", parts.join(", "))
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
131#[non_exhaustive]
132pub enum RejectionReason {
133 OverCapacity,
135 PreferenceBased,
137 AccessDenied,
139}
140
141impl fmt::Display for RejectionReason {
142 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
143 match self {
144 Self::OverCapacity => write!(f, "over capacity"),
145 Self::PreferenceBased => write!(f, "rider preference"),
146 Self::AccessDenied => write!(f, "access denied"),
147 }
148 }
149}
150
151#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
156pub struct RejectionContext {
157 pub attempted_weight: OrderedFloat<f64>,
159 pub current_load: OrderedFloat<f64>,
161 pub capacity: OrderedFloat<f64>,
163}
164
165impl fmt::Display for RejectionContext {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 let excess = (*self.current_load + *self.attempted_weight) - *self.capacity;
180 if excess > 0.0 {
181 write!(
182 f,
183 "over capacity by {excess:.1}kg ({:.1}/{:.1} + {:.1})",
184 *self.current_load, *self.capacity, *self.attempted_weight,
185 )
186 } else {
187 write!(
188 f,
189 "load {:.1}kg/{:.1}kg + {:.1}kg",
190 *self.current_load, *self.capacity, *self.attempted_weight,
191 )
192 }
193 }
194}
195
196impl From<EntityId> for SimError {
197 fn from(id: EntityId) -> Self {
198 Self::EntityNotFound(id)
199 }
200}
201
202impl From<StopId> for SimError {
203 fn from(id: StopId) -> Self {
204 Self::StopNotFound(id)
205 }
206}
207
208impl From<GroupId> for SimError {
209 fn from(id: GroupId) -> Self {
210 Self::GroupNotFound(id)
211 }
212}