use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum DiagnosticSeverity {
Info,
Warning,
Degraded,
Error,
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum KernelStage {
Collect,
Classify,
Intersect,
Refine,
Fragment,
Select,
Sew,
Validate,
Export,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct DiagnosticEvent {
pub severity: DiagnosticSeverity,
pub stage: KernelStage,
pub code: String,
pub message: String,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct KernelDiagnostics {
pub events: Vec<DiagnosticEvent>,
pub counters: BTreeMap<String, u64>,
pub measurements: BTreeMap<String, f64>,
}
impl KernelDiagnostics {
pub fn event(
&mut self,
severity: DiagnosticSeverity,
stage: KernelStage,
code: impl Into<String>,
message: impl Into<String>,
) {
self.events.push(DiagnosticEvent {
severity,
stage,
code: code.into(),
message: message.into(),
});
}
pub fn count(&mut self, code: impl Into<String>) {
*self.counters.entry(code.into()).or_default() += 1;
}
pub fn count_n(&mut self, code: impl Into<String>, amount: u64) {
*self.counters.entry(code.into()).or_default() += amount;
}
pub fn measure_max(&mut self, code: impl Into<String>, value: f64) {
let entry = self.measurements.entry(code.into()).or_insert(value);
*entry = entry.max(value);
}
pub fn worst(&self) -> Option<DiagnosticSeverity> {
self.events.iter().map(|event| event.severity).max()
}
pub fn shippable(&self) -> bool {
self.events
.iter()
.all(|event| event.severity < DiagnosticSeverity::Error)
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct KernelRefusal {
#[serde(flatten)]
pub class: RefusalClass,
pub stage: KernelStage,
pub message: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "class", rename_all = "snake_case")]
pub enum RefusalClass {
DegenerateArrangement { open_edges: u32, issues: u32 },
NonIntegralGenus { shells: u32, euler: i64 },
NonPositiveVolume,
TangentNodeSingularity,
InvalidResultTopology { issues: u32 },
ConservativeEmptyOverlap,
NonConvergence { what: String },
UnsupportedGeometry { what: String },
InvalidInput { what: String },
Internal { what: String },
}
impl RefusalClass {
pub fn perturbation_eligible(&self) -> bool {
matches!(
self,
Self::DegenerateArrangement { .. }
| Self::NonIntegralGenus { .. }
| Self::NonPositiveVolume
| Self::TangentNodeSingularity
| Self::InvalidResultTopology { .. }
)
}
pub fn tag(&self) -> &'static str {
match self {
Self::DegenerateArrangement { .. } => "degenerate_arrangement",
Self::NonIntegralGenus { .. } => "non_integral_genus",
Self::NonPositiveVolume => "non_positive_volume",
Self::TangentNodeSingularity => "tangent_node_singularity",
Self::InvalidResultTopology { .. } => "invalid_result_topology",
Self::ConservativeEmptyOverlap => "conservative_empty_overlap",
Self::NonConvergence { .. } => "non_convergence",
Self::UnsupportedGeometry { .. } => "unsupported_geometry",
Self::InvalidInput { .. } => "invalid_input",
Self::Internal { .. } => "internal",
}
}
}
impl KernelRefusal {
pub fn new(class: RefusalClass, stage: KernelStage, message: impl Into<String>) -> Self {
Self {
class,
stage,
message: message.into(),
}
}
pub fn internal(
stage: KernelStage,
what: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::new(RefusalClass::Internal { what: what.into() }, stage, message)
}
pub fn input(stage: KernelStage, what: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(
RefusalClass::InvalidInput { what: what.into() },
stage,
message,
)
}
pub fn unsupported(
stage: KernelStage,
what: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::new(
RefusalClass::UnsupportedGeometry { what: what.into() },
stage,
message,
)
}
pub fn non_convergence(
stage: KernelStage,
what: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self::new(
RefusalClass::NonConvergence { what: what.into() },
stage,
message,
)
}
pub fn with_message(mut self, f: impl FnOnce(&str) -> String) -> Self {
self.message = f(&self.message);
self
}
}
impl std::fmt::Display for KernelRefusal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl std::error::Error for KernelRefusal {}
impl From<KernelRefusal> for String {
fn from(refusal: KernelRefusal) -> Self {
refusal.message
}
}
pub trait OrRefuse<T> {
fn or_refuse(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal>;
fn or_input(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal>;
}
impl<T> OrRefuse<T> for Result<T, String> {
fn or_refuse(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal> {
self.map_err(|message| KernelRefusal::internal(stage, what, message))
}
fn or_input(self, stage: KernelStage, what: &'static str) -> Result<T, KernelRefusal> {
self.map_err(|message| KernelRefusal::input(stage, what, message))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct KernelOutcome<T> {
pub value: T,
pub diagnostics: KernelDiagnostics,
}