scirs2_special/
error_context.rs1use crate::error::{SpecialError, SpecialResult};
7use std::fmt;
8
9#[derive(Debug, Clone)]
11pub struct ErrorContext {
12 pub function_name: String,
14 pub operation: String,
16 pub parameters: Vec<(String, String)>,
18 pub additional_info: Option<String>,
20}
21
22impl ErrorContext {
23 pub fn new(function_name: impl Into<String>, operation: impl Into<String>) -> Self {
25 Self {
26 function_name: function_name.into(),
27 operation: operation.into(),
28 parameters: Vec::new(),
29 additional_info: None,
30 }
31 }
32
33 pub fn with_param(mut self, name: impl Into<String>, value: impl fmt::Display) -> Self {
35 self.parameters.push((name.into(), value.to_string()));
36 self
37 }
38
39 pub fn with_info(mut self, info: impl Into<String>) -> Self {
41 self.additional_info = Some(info.into());
42 self
43 }
44
45 pub fn to_error_message(&self) -> String {
47 let mut msg = format!("Error in {} during {}", self.function_name, self.operation);
48
49 if !self.parameters.is_empty() {
50 msg.push_str(" with parameters: ");
51 let params: Vec<String> = self
52 .parameters
53 .iter()
54 .map(|(name, value)| format!("{name}={value}"))
55 .collect();
56 msg.push_str(¶ms.join(", "));
57 }
58
59 if let Some(ref info) = self.additional_info {
60 msg.push_str(&format!(". {info}"));
61 }
62
63 msg
64 }
65}
66
67#[derive(Debug)]
69pub struct ContextualError {
70 pub error: SpecialError,
71 pub context: ErrorContext,
72}
73
74impl fmt::Display for ContextualError {
75 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
76 write!(f, "{}: {}", self.context.to_error_message(), self.error)
77 }
78}
79
80impl std::error::Error for ContextualError {}
81
82pub trait ErrorContextExt<T> {
84 fn with_context<F>(self, f: F) -> SpecialResult<T>
86 where
87 F: FnOnce() -> ErrorContext;
88}
89
90impl<T> ErrorContextExt<T> for SpecialResult<T> {
91 fn with_context<F>(self, f: F) -> SpecialResult<T>
92 where
93 F: FnOnce() -> ErrorContext,
94 {
95 self.map_err(|e| {
96 let ctx = f();
97 SpecialError::ComputationError(format!("{}: {e}", ctx.to_error_message()))
98 })
99 }
100}
101
102pub trait ValidatedFunction<Input, Output> {
104 fn validateinputs(&self, input: &Input) -> SpecialResult<()>;
106
107 fn compute_validated(&self, input: Input) -> SpecialResult<Output>;
109
110 fn evaluate(&self, input: Input) -> SpecialResult<Output> {
112 self.validateinputs(&input)?;
113 self.compute_validated(input)
114 }
115}
116
117#[derive(Debug, Clone, Copy)]
119pub enum RecoveryStrategy {
120 ReturnDefault,
122 ClampToRange,
124 UseApproximation,
126 PropagateError,
128}
129
130pub trait ErrorRecovery<T> {
132 fn recover(&self, error: &SpecialError, strategy: RecoveryStrategy) -> Option<T>;
134}
135
136#[macro_export]
138macro_rules! special_error {
139 (domain: $func:expr, $op:expr, $($param:expr => $value:expr),* $(,)?) => {{
140 let mut ctx = $crate::error_context::ErrorContext::new($func, $op);
141 $(ctx = ctx.with_param($param, $value);)*
142 $crate::error::SpecialError::DomainError(ctx.to_error_message())
143 }};
144
145 (convergence: $func:expr, $op:expr, $($param:expr => $value:expr),* $(,)?) => {{
146 let mut ctx = $crate::error_context::ErrorContext::new($func, $op);
147 $(ctx = ctx.with_param($param, $value);)*
148 $crate::error::SpecialError::ConvergenceError(ctx.to_error_message())
149 }};
150
151 (computation: $func:expr, $op:expr, $($param:expr => $value:expr),* $(,)?) => {{
152 let mut ctx = $crate::error_context::ErrorContext::new($func, $op);
153 $(ctx = ctx.with_param($param, $value);)*
154 $crate::error::SpecialError::ComputationError(ctx.to_error_message())
155 }};
156}
157
158#[macro_export]
160macro_rules! validate_with_context {
161 ($condition:expr, $error_type:ident, $func:expr, $msg:expr $(, $param:expr => $value:expr)*) => {
162 if !($condition) {
163 let mut ctx = $crate::error_context::ErrorContext::new($func, "validation");
164 $(ctx = ctx.with_param($param, $value);)*
165 ctx = ctx.with_info($msg);
166 return Err($crate::error::SpecialError::$error_type(ctx.to_error_message()));
167 }
168 };
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn test_error_context() {
177 let ctx = ErrorContext::new("gamma", "computation")
178 .with_param("x", -1.0)
179 .with_info("Gamma function is undefined at negative integers");
180
181 let msg = ctx.to_error_message();
182 assert!(msg.contains("gamma"));
183 assert!(msg.contains("x=-1"));
184 assert!(msg.contains("undefined"));
185 }
186
187 #[test]
188 fn test_error_context_macro() {
189 let err = special_error!(
190 domain: "bessel_j", "evaluation",
191 "n" => 5,
192 "x" => -10.0
193 );
194
195 match err {
196 SpecialError::DomainError(msg) => {
197 assert!(msg.contains("bessel_j"));
198 assert!(msg.contains("n=5"));
199 assert!(msg.contains("x=-10"));
200 }
201 _ => panic!("Wrong error type"),
202 }
203 }
204}