1use serde::{Deserialize, Serialize};
2use std::collections::BTreeMap;
3
4#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
5#[serde(rename_all = "lowercase")]
6pub enum DiagnosticSeverity {
7 Info,
8 Warning,
9 Degraded,
10 Error,
11}
12
13#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
14#[serde(rename_all = "snake_case")]
15pub enum KernelStage {
16 Collect,
17 Classify,
18 Intersect,
19 Refine,
20 Fragment,
21 Select,
22 Sew,
23 Validate,
24 Export,
25}
26
27#[derive(Clone, Debug, Deserialize, Serialize)]
28pub struct DiagnosticEvent {
29 pub severity: DiagnosticSeverity,
30 pub stage: KernelStage,
31 pub code: String,
33 pub message: String,
34}
35
36#[derive(Clone, Debug, Default, Deserialize, Serialize)]
37pub struct KernelDiagnostics {
38 pub events: Vec<DiagnosticEvent>,
39 pub counters: BTreeMap<String, u64>,
40 pub measurements: BTreeMap<String, f64>,
41}
42
43impl KernelDiagnostics {
44 pub fn event(
45 &mut self,
46 severity: DiagnosticSeverity,
47 stage: KernelStage,
48 code: impl Into<String>,
49 message: impl Into<String>,
50 ) {
51 self.events.push(DiagnosticEvent {
52 severity,
53 stage,
54 code: code.into(),
55 message: message.into(),
56 });
57 }
58
59 pub fn count(&mut self, code: impl Into<String>) {
60 *self.counters.entry(code.into()).or_default() += 1;
61 }
62
63 pub fn count_n(&mut self, code: impl Into<String>, amount: u64) {
64 *self.counters.entry(code.into()).or_default() += amount;
65 }
66
67 pub fn measure_max(&mut self, code: impl Into<String>, value: f64) {
68 let entry = self.measurements.entry(code.into()).or_insert(value);
69 *entry = entry.max(value);
70 }
71
72 pub fn worst(&self) -> Option<DiagnosticSeverity> {
73 self.events.iter().map(|event| event.severity).max()
74 }
75
76 pub fn shippable(&self) -> bool {
77 self.events
78 .iter()
79 .all(|event| event.severity < DiagnosticSeverity::Error)
80 }
81}
82
83#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
96pub struct KernelRefusal {
97 #[serde(flatten)]
98 pub class: RefusalClass,
99 pub stage: KernelStage,
100 pub message: String,
102}
103
104#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
113#[serde(tag = "class", rename_all = "snake_case")]
114pub enum RefusalClass {
115 DegenerateArrangement { open_edges: u32, issues: u32 },
118 NonIntegralGenus { shells: u32, euler: i64 },
120 NonPositiveVolume,
122 TangentNodeSingularity,
124 InvalidResultTopology { issues: u32 },
126 ConservativeEmptyOverlap,
129 NonConvergence { what: String },
132 UnsupportedGeometry { what: String },
134 InvalidInput { what: String },
136 Internal { what: String },
139}
140
141impl RefusalClass {
142 pub fn perturbation_eligible(&self) -> bool {
146 matches!(
147 self,
148 Self::DegenerateArrangement { .. }
149 | Self::NonIntegralGenus { .. }
150 | Self::NonPositiveVolume
151 | Self::TangentNodeSingularity
152 | Self::InvalidResultTopology { .. }
153 )
154 }
155
156 pub fn tag(&self) -> &'static str {
158 match self {
159 Self::DegenerateArrangement { .. } => "degenerate_arrangement",
160 Self::NonIntegralGenus { .. } => "non_integral_genus",
161 Self::NonPositiveVolume => "non_positive_volume",
162 Self::TangentNodeSingularity => "tangent_node_singularity",
163 Self::InvalidResultTopology { .. } => "invalid_result_topology",
164 Self::ConservativeEmptyOverlap => "conservative_empty_overlap",
165 Self::NonConvergence { .. } => "non_convergence",
166 Self::UnsupportedGeometry { .. } => "unsupported_geometry",
167 Self::InvalidInput { .. } => "invalid_input",
168 Self::Internal { .. } => "internal",
169 }
170 }
171}
172
173impl KernelRefusal {
174 pub fn new(class: RefusalClass, stage: KernelStage, message: impl Into<String>) -> Self {
175 Self {
176 class,
177 stage,
178 message: message.into(),
179 }
180 }
181
182 pub fn internal(
185 stage: KernelStage,
186 what: impl Into<String>,
187 message: impl Into<String>,
188 ) -> Self {
189 Self::new(RefusalClass::Internal { what: what.into() }, stage, message)
190 }
191
192 pub fn input(stage: KernelStage, what: impl Into<String>, message: impl Into<String>) -> Self {
194 Self::new(
195 RefusalClass::InvalidInput { what: what.into() },
196 stage,
197 message,
198 )
199 }
200
201 pub fn unsupported(
203 stage: KernelStage,
204 what: impl Into<String>,
205 message: impl Into<String>,
206 ) -> Self {
207 Self::new(
208 RefusalClass::UnsupportedGeometry { what: what.into() },
209 stage,
210 message,
211 )
212 }
213
214 pub fn non_convergence(
216 stage: KernelStage,
217 what: impl Into<String>,
218 message: impl Into<String>,
219 ) -> Self {
220 Self::new(
221 RefusalClass::NonConvergence { what: what.into() },
222 stage,
223 message,
224 )
225 }
226
227 pub fn with_message(mut self, f: impl FnOnce(&str) -> String) -> Self {
232 self.message = f(&self.message);
233 self
234 }
235}
236
237impl std::fmt::Display for KernelRefusal {
238 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
239 f.write_str(&self.message)
240 }
241}
242
243impl std::error::Error for KernelRefusal {}
244
245impl From<KernelRefusal> for String {
250 fn from(refusal: KernelRefusal) -> Self {
251 refusal.message
252 }
253}
254
255pub trait OrRefuse<T> {
260 fn or_refuse(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal>;
261 fn or_input(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal>;
262}
263
264impl<T> OrRefuse<T> for Result<T, String> {
265 fn or_refuse(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal> {
266 self.map_err(|message| KernelRefusal::internal(stage, what, message))
267 }
268
269 fn or_input(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal> {
270 self.map_err(|message| KernelRefusal::input(stage, what, message))
271 }
272}
273
274#[derive(Clone, Debug, Deserialize, Serialize)]
275pub struct KernelOutcome<T> {
276 pub value: T,
277 pub diagnostics: KernelDiagnostics,
278}
279
280