1use std::fmt;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum PanicKind {
10 Overflow,
12 DivideByZero,
14 DecodeError,
16 TypeError,
19 DerivationError,
22 CapiMisuse,
25 Internal,
27}
28
29impl PanicKind {
30 pub fn name(self) -> &'static str {
31 match self {
32 PanicKind::Overflow => "overflow",
33 PanicKind::DivideByZero => "divide-by-zero",
34 PanicKind::DecodeError => "decode-error",
35 PanicKind::TypeError => "type-error",
36 PanicKind::DerivationError => "derivation-error",
37 PanicKind::CapiMisuse => "capi-misuse",
38 PanicKind::Internal => "internal",
39 }
40 }
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct TraceFrame {
49 pub definition: String,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
57pub struct SoilError {
58 pub kind: PanicKind,
59 pub message: String,
60 pub trace: Vec<TraceFrame>,
61 pub inputs: Option<String>,
64}
65
66impl SoilError {
67 pub fn new(kind: PanicKind, message: impl Into<String>) -> Self {
68 SoilError {
69 kind,
70 message: message.into(),
71 trace: Vec::new(),
72 inputs: None,
73 }
74 }
75
76 pub fn with_inputs(mut self, inputs: impl Into<String>) -> Self {
77 self.inputs = Some(inputs.into());
78 self
79 }
80}
81
82impl fmt::Display for SoilError {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "{}: {}", self.kind.name(), self.message)?;
85 if let Some(inputs) = &self.inputs {
86 write!(f, " (inputs: {inputs})")?;
87 }
88 for frame in &self.trace {
89 write!(f, "\n in {}", frame.definition)?;
90 }
91 Ok(())
92 }
93}
94
95impl std::error::Error for SoilError {}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn display_includes_kind_message_and_trace() {
103 let mut err =
104 SoilError::new(PanicKind::DivideByZero, "divisor is zero").with_inputs("[10,0]");
105 err.trace.push(TraceFrame {
106 definition: "csvstats::mean".to_string(),
107 });
108 let rendered = err.to_string();
109 assert_eq!(
110 rendered,
111 "divide-by-zero: divisor is zero (inputs: [10,0])\n in csvstats::mean"
112 );
113 }
114
115 #[test]
116 fn kind_names_are_stable() {
117 assert_eq!(PanicKind::Overflow.name(), "overflow");
118 assert_eq!(PanicKind::Internal.name(), "internal");
119 }
120}