1use crate::core::address::Address;
6use crate::core::distribution::*;
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum ErrorCode {
12 InvalidMean = 100,
14 InvalidVariance = 101,
15 InvalidProbability = 102,
16 InvalidRange = 103,
17 InvalidShape = 104,
18 InvalidRate = 105,
19 InvalidCount = 106,
20
21 NumericalOverflow = 200,
23 NumericalUnderflow = 201,
24 NumericalInstability = 202,
25 InvalidLogDensity = 203,
26
27 ModelExecutionFailed = 300,
29 AddressConflict = 301,
30 UnexpectedModelStructure = 302,
31
32 InferenceConvergenceFailed = 400,
34 InsufficientSamples = 401,
35 InvalidInferenceConfig = 402,
36
37 TraceAddressNotFound = 500,
39 TraceCorrupted = 501,
40 TraceReplayFailed = 502,
41
42 TypeMismatch = 600,
44 UnsupportedType = 601,
45}
46
47impl ErrorCode {
48 pub fn description(&self) -> &'static str {
50 match self {
51 ErrorCode::InvalidMean => "Distribution mean parameter is invalid",
52 ErrorCode::InvalidVariance => "Distribution variance/scale parameter is invalid",
53 ErrorCode::InvalidProbability => "Probability parameter is invalid",
54 ErrorCode::InvalidRange => "Parameter range is invalid",
55 ErrorCode::InvalidShape => "Shape parameter is invalid",
56 ErrorCode::InvalidRate => "Rate parameter is invalid",
57 ErrorCode::InvalidCount => "Count parameter is invalid",
58
59 ErrorCode::NumericalOverflow => "Numerical computation resulted in overflow",
60 ErrorCode::NumericalUnderflow => "Numerical computation resulted in underflow",
61 ErrorCode::NumericalInstability => "Numerical computation is unstable",
62 ErrorCode::InvalidLogDensity => "Log density computation is invalid",
63
64 ErrorCode::ModelExecutionFailed => "Model execution failed",
65 ErrorCode::AddressConflict => "Address already exists in trace",
66 ErrorCode::UnexpectedModelStructure => "Model structure is unexpected",
67
68 ErrorCode::InferenceConvergenceFailed => "Inference algorithm failed to converge",
69 ErrorCode::InsufficientSamples => "Insufficient samples for reliable inference",
70 ErrorCode::InvalidInferenceConfig => "Inference configuration is invalid",
71
72 ErrorCode::TraceAddressNotFound => "Address not found in trace",
73 ErrorCode::TraceCorrupted => "Trace data is corrupted",
74 ErrorCode::TraceReplayFailed => "Trace replay failed",
75
76 ErrorCode::TypeMismatch => "Type mismatch in trace value",
77 ErrorCode::UnsupportedType => "Unsupported type for operation",
78 }
79 }
80
81 pub fn category(&self) -> ErrorCategory {
83 match (*self as u32) / 100 {
84 1 => ErrorCategory::DistributionValidation,
85 2 => ErrorCategory::NumericalComputation,
86 3 => ErrorCategory::ModelExecution,
87 4 => ErrorCategory::InferenceAlgorithm,
88 5 => ErrorCategory::TraceManipulation,
89 6 => ErrorCategory::TypeSystem,
90 _ => ErrorCategory::Unknown,
91 }
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub enum ErrorCategory {
98 DistributionValidation,
99 NumericalComputation,
100 ModelExecution,
101 InferenceAlgorithm,
102 TraceManipulation,
103 TypeSystem,
104 Unknown,
105}
106
107#[derive(Debug, Clone)]
109pub struct ErrorContext {
110 pub source_location: Option<(String, u32)>,
112 pub context: Vec<(String, String)>,
114 pub cause: Option<Box<FugueError>>,
116}
117
118impl ErrorContext {
119 pub fn new() -> Self {
121 Self {
122 source_location: None,
123 context: Vec::new(),
124 cause: None,
125 }
126 }
127
128 pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
130 self.context.push((key.into(), value.into()));
131 self
132 }
133
134 pub fn with_source_location(mut self, file: impl Into<String>, line: u32) -> Self {
136 self.source_location = Some((file.into(), line));
137 self
138 }
139
140 pub fn with_cause(mut self, cause: FugueError) -> Self {
142 self.cause = Some(Box::new(cause));
143 self
144 }
145}
146
147impl Default for ErrorContext {
148 fn default() -> Self {
149 Self::new()
150 }
151}
152
153#[derive(Debug, Clone)]
155#[allow(clippy::result_large_err)]
156pub enum FugueError {
157 InvalidParameters {
159 distribution: String,
160 reason: String,
161 code: ErrorCode,
162 context: ErrorContext,
163 },
164 NumericalError {
166 operation: String,
167 details: String,
168 code: ErrorCode,
169 context: ErrorContext,
170 },
171 ModelError {
173 address: Option<Address>,
174 reason: String,
175 code: ErrorCode,
176 context: ErrorContext,
177 },
178 InferenceError {
180 algorithm: String,
181 reason: String,
182 code: ErrorCode,
183 context: ErrorContext,
184 },
185 TraceError {
187 operation: String,
188 address: Option<Address>,
189 reason: String,
190 code: ErrorCode,
191 context: ErrorContext,
192 },
193 TypeMismatch {
195 address: Address,
196 expected: String,
197 found: String,
198 code: ErrorCode,
199 context: ErrorContext,
200 },
201}
202
203impl fmt::Display for FugueError {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 FugueError::InvalidParameters {
207 distribution,
208 reason,
209 code,
210 context,
211 } => {
212 write!(
213 f,
214 "[{}] Invalid parameters for {}: {}",
215 *code as u32, distribution, reason
216 )?;
217 self.write_context(f, context)?;
218 Ok(())
219 }
220 FugueError::NumericalError {
221 operation,
222 details,
223 code,
224 context,
225 } => {
226 write!(
227 f,
228 "[{}] Numerical error in {}: {}",
229 *code as u32, operation, details
230 )?;
231 self.write_context(f, context)?;
232 Ok(())
233 }
234 FugueError::ModelError {
235 address,
236 reason,
237 code,
238 context,
239 } => {
240 if let Some(addr) = address {
241 write!(f, "[{}] Model error at {}: {}", *code as u32, addr, reason)?;
242 } else {
243 write!(f, "[{}] Model error: {}", *code as u32, reason)?;
244 }
245 self.write_context(f, context)?;
246 Ok(())
247 }
248 FugueError::InferenceError {
249 algorithm,
250 reason,
251 code,
252 context,
253 } => {
254 write!(
255 f,
256 "[{}] Inference error in {}: {}",
257 *code as u32, algorithm, reason
258 )?;
259 self.write_context(f, context)?;
260 Ok(())
261 }
262 FugueError::TraceError {
263 operation,
264 address,
265 reason,
266 code,
267 context,
268 } => {
269 if let Some(addr) = address {
270 write!(
271 f,
272 "[{}] Trace error in {} at {}: {}",
273 *code as u32, operation, addr, reason
274 )?;
275 } else {
276 write!(
277 f,
278 "[{}] Trace error in {}: {}",
279 *code as u32, operation, reason
280 )?;
281 }
282 self.write_context(f, context)?;
283 Ok(())
284 }
285 FugueError::TypeMismatch {
286 address,
287 expected,
288 found,
289 code,
290 context,
291 } => {
292 write!(
293 f,
294 "[{}] Type mismatch at {}: expected {}, found {}",
295 *code as u32, address, expected, found
296 )?;
297 self.write_context(f, context)?;
298 Ok(())
299 }
300 }
301 }
302}
303
304impl FugueError {
305 fn write_context(&self, f: &mut fmt::Formatter<'_>, context: &ErrorContext) -> fmt::Result {
307 if let Some((file, line)) = &context.source_location {
309 write!(f, " (at {}:{})", file, line)?;
310 }
311
312 if !context.context.is_empty() {
314 write!(f, " [")?;
315 for (i, (key, value)) in context.context.iter().enumerate() {
316 if i > 0 {
317 write!(f, ", ")?;
318 }
319 write!(f, "{}={}", key, value)?;
320 }
321 write!(f, "]")?;
322 }
323
324 if let Some(cause) = &context.cause {
326 write!(f, "\n Caused by: {}", cause)?;
327 }
328
329 Ok(())
330 }
331
332 pub fn code(&self) -> ErrorCode {
334 match self {
335 FugueError::InvalidParameters { code, .. } => *code,
336 FugueError::NumericalError { code, .. } => *code,
337 FugueError::ModelError { code, .. } => *code,
338 FugueError::InferenceError { code, .. } => *code,
339 FugueError::TraceError { code, .. } => *code,
340 FugueError::TypeMismatch { code, .. } => *code,
341 }
342 }
343
344 pub fn category(&self) -> ErrorCategory {
346 self.code().category()
347 }
348
349 pub fn context(&self) -> &ErrorContext {
351 match self {
352 FugueError::InvalidParameters { context, .. } => context,
353 FugueError::NumericalError { context, .. } => context,
354 FugueError::ModelError { context, .. } => context,
355 FugueError::InferenceError { context, .. } => context,
356 FugueError::TraceError { context, .. } => context,
357 FugueError::TypeMismatch { context, .. } => context,
358 }
359 }
360
361 pub fn is_validation_error(&self) -> bool {
363 matches!(self.category(), ErrorCategory::DistributionValidation)
364 }
365
366 pub fn is_numerical_error(&self) -> bool {
368 matches!(self.category(), ErrorCategory::NumericalComputation)
369 }
370
371 pub fn is_recoverable(&self) -> bool {
373 matches!(
374 self.code(),
375 ErrorCode::InsufficientSamples
376 | ErrorCode::NumericalInstability
377 | ErrorCode::InferenceConvergenceFailed
378 )
379 }
380}
381
382impl std::error::Error for FugueError {}
383
384#[allow(clippy::result_large_err)]
386pub type FugueResult<T> = Result<T, FugueError>;
387
388impl FugueError {
393 pub fn invalid_parameters(
395 distribution: impl Into<String>,
396 reason: impl Into<String>,
397 code: ErrorCode,
398 ) -> Self {
399 Self::InvalidParameters {
400 distribution: distribution.into(),
401 reason: reason.into(),
402 code,
403 context: ErrorContext::new(),
404 }
405 }
406
407 pub fn invalid_parameters_with_context(
409 distribution: impl Into<String>,
410 reason: impl Into<String>,
411 code: ErrorCode,
412 context: ErrorContext,
413 ) -> Self {
414 Self::InvalidParameters {
415 distribution: distribution.into(),
416 reason: reason.into(),
417 code,
418 context,
419 }
420 }
421
422 pub fn numerical_error(
424 operation: impl Into<String>,
425 details: impl Into<String>,
426 code: ErrorCode,
427 ) -> Self {
428 Self::NumericalError {
429 operation: operation.into(),
430 details: details.into(),
431 code,
432 context: ErrorContext::new(),
433 }
434 }
435
436 pub fn trace_error(
438 operation: impl Into<String>,
439 address: Option<Address>,
440 reason: impl Into<String>,
441 code: ErrorCode,
442 ) -> Self {
443 Self::TraceError {
444 operation: operation.into(),
445 address,
446 reason: reason.into(),
447 code,
448 context: ErrorContext::new(),
449 }
450 }
451
452 pub fn type_mismatch(
454 address: Address,
455 expected: impl Into<String>,
456 found: impl Into<String>,
457 ) -> Self {
458 Self::TypeMismatch {
459 address,
460 expected: expected.into(),
461 found: found.into(),
462 code: ErrorCode::TypeMismatch,
463 context: ErrorContext::new(),
464 }
465 }
466
467 pub fn with_context(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
469 match &mut self {
470 FugueError::InvalidParameters { context, .. } => {
471 context.context.push((key.into(), value.into()));
472 }
473 FugueError::NumericalError { context, .. } => {
474 context.context.push((key.into(), value.into()));
475 }
476 FugueError::ModelError { context, .. } => {
477 context.context.push((key.into(), value.into()));
478 }
479 FugueError::InferenceError { context, .. } => {
480 context.context.push((key.into(), value.into()));
481 }
482 FugueError::TraceError { context, .. } => {
483 context.context.push((key.into(), value.into()));
484 }
485 FugueError::TypeMismatch { context, .. } => {
486 context.context.push((key.into(), value.into()));
487 }
488 }
489 self
490 }
491
492 pub fn with_source_location(mut self, file: impl Into<String>, line: u32) -> Self {
494 match &mut self {
495 FugueError::InvalidParameters { context, .. } => {
496 context.source_location = Some((file.into(), line));
497 }
498 FugueError::NumericalError { context, .. } => {
499 context.source_location = Some((file.into(), line));
500 }
501 FugueError::ModelError { context, .. } => {
502 context.source_location = Some((file.into(), line));
503 }
504 FugueError::InferenceError { context, .. } => {
505 context.source_location = Some((file.into(), line));
506 }
507 FugueError::TraceError { context, .. } => {
508 context.source_location = Some((file.into(), line));
509 }
510 FugueError::TypeMismatch { context, .. } => {
511 context.source_location = Some((file.into(), line));
512 }
513 }
514 self
515 }
516}
517
518impl From<std::num::ParseFloatError> for FugueError {
524 fn from(err: std::num::ParseFloatError) -> Self {
525 FugueError::numerical_error(
526 "parse_float",
527 format!("Failed to parse float: {}", err),
528 ErrorCode::NumericalInstability,
529 )
530 }
531}
532
533impl From<std::num::ParseIntError> for FugueError {
534 fn from(err: std::num::ParseIntError) -> Self {
535 FugueError::numerical_error(
536 "parse_int",
537 format!("Failed to parse integer: {}", err),
538 ErrorCode::NumericalInstability,
539 )
540 }
541}
542
543impl From<&str> for FugueError {
545 fn from(msg: &str) -> Self {
546 FugueError::ModelError {
547 address: None,
548 reason: msg.to_string(),
549 code: ErrorCode::ModelExecutionFailed,
550 context: ErrorContext::new(),
551 }
552 }
553}
554
555impl From<String> for FugueError {
556 fn from(msg: String) -> Self {
557 FugueError::ModelError {
558 address: None,
559 reason: msg,
560 code: ErrorCode::ModelExecutionFailed,
561 context: ErrorContext::new(),
562 }
563 }
564}
565
566#[macro_export]
580macro_rules! invalid_params {
581 ($dist:expr, $reason:expr, $code:ident) => {
582 $crate::error::FugueError::invalid_parameters($dist, $reason, $crate::error::ErrorCode::$code)
583 };
584 ($dist:expr, $reason:expr, $code:ident, $($key:expr => $value:expr),+ $(,)?) => {
585 $crate::error::FugueError::invalid_parameters($dist, $reason, $crate::error::ErrorCode::$code)
586 $(.with_context($key, $value))*
587 };
588}
589
590#[macro_export]
600macro_rules! numerical_error {
601 ($op:expr, $details:expr, $code:ident) => {
602 $crate::error::FugueError::numerical_error($op, $details, $crate::error::ErrorCode::$code)
603 };
604 ($op:expr, $details:expr, $code:ident, $($key:expr => $value:expr),+ $(,)?) => {
605 $crate::error::FugueError::numerical_error($op, $details, $crate::error::ErrorCode::$code)
606 $(.with_context($key, $value))*
607 };
608}
609
610#[macro_export]
618macro_rules! trace_error {
619 ($op:expr, $addr:expr, $reason:expr, $code:ident) => {
620 $crate::error::FugueError::trace_error($op, $addr, $reason, $crate::error::ErrorCode::$code)
621 };
622 ($op:expr, $addr:expr, $reason:expr, $code:ident, $($key:expr => $value:expr),+ $(,)?) => {
623 $crate::error::FugueError::trace_error($op, $addr, $reason, $crate::error::ErrorCode::$code)
624 $(.with_context($key, $value))*
625 };
626}
627
628pub trait Validate {
630 fn validate(&self) -> FugueResult<()>;
631}
632
633impl Validate for Normal {
634 fn validate(&self) -> FugueResult<()> {
635 if !self.mu().is_finite() {
636 return Err(invalid_params!(
637 "Normal",
638 "Mean (mu) must be finite",
639 InvalidMean,
640 "mu" => format!("{}", self.mu())
641 ));
642 }
643 if self.sigma() <= 0.0 || !self.sigma().is_finite() {
644 return Err(invalid_params!(
645 "Normal",
646 "Standard deviation (sigma) must be positive and finite",
647 InvalidVariance,
648 "sigma" => format!("{}", self.sigma()),
649 "expected" => "> 0.0 and finite"
650 ));
651 }
652 Ok(())
653 }
654}
655
656impl Validate for Exponential {
657 fn validate(&self) -> FugueResult<()> {
658 if self.rate() <= 0.0 || !self.rate().is_finite() {
659 return Err(invalid_params!(
660 "Exponential",
661 "Rate parameter must be positive and finite",
662 InvalidRate,
663 "rate" => format!("{}", self.rate()),
664 "expected" => "> 0.0 and finite"
665 ));
666 }
667 Ok(())
668 }
669}
670
671impl Validate for Beta {
672 fn validate(&self) -> FugueResult<()> {
673 if self.alpha() <= 0.0 || !self.alpha().is_finite() {
674 return Err(invalid_params!(
675 "Beta",
676 "Alpha parameter must be positive and finite",
677 InvalidShape,
678 "alpha" => format!("{}", self.alpha()),
679 "expected" => "> 0.0 and finite"
680 ));
681 }
682 if self.beta() <= 0.0 || !self.beta().is_finite() {
683 return Err(invalid_params!(
684 "Beta",
685 "Beta parameter must be positive and finite",
686 InvalidShape,
687 "beta" => format!("{}", self.beta()),
688 "expected" => "> 0.0 and finite"
689 ));
690 }
691 Ok(())
692 }
693}
694
695impl Validate for Gamma {
696 fn validate(&self) -> FugueResult<()> {
697 if self.shape() <= 0.0 || !self.shape().is_finite() {
698 return Err(invalid_params!(
699 "Gamma",
700 "Shape parameter must be positive and finite",
701 InvalidShape,
702 "shape" => format!("{}", self.shape()),
703 "expected" => "> 0.0 and finite"
704 ));
705 }
706 if self.rate() <= 0.0 || !self.rate().is_finite() {
707 return Err(invalid_params!(
708 "Gamma",
709 "Rate parameter must be positive and finite",
710 InvalidRate,
711 "rate" => format!("{}", self.rate()),
712 "expected" => "> 0.0 and finite"
713 ));
714 }
715 Ok(())
716 }
717}
718
719impl Validate for Uniform {
720 fn validate(&self) -> FugueResult<()> {
721 if !self.low().is_finite() || !self.high().is_finite() {
722 return Err(invalid_params!(
723 "Uniform",
724 "Bounds must be finite",
725 InvalidRange,
726 "low" => format!("{}", self.low()),
727 "high" => format!("{}", self.high())
728 ));
729 }
730 if self.low() >= self.high() {
731 return Err(invalid_params!(
732 "Uniform",
733 "Lower bound must be less than upper bound",
734 InvalidRange,
735 "low" => format!("{}", self.low()),
736 "high" => format!("{}", self.high())
737 ));
738 }
739 Ok(())
740 }
741}
742
743impl Validate for Bernoulli {
744 fn validate(&self) -> FugueResult<()> {
745 if !self.p().is_finite() || self.p() < 0.0 || self.p() > 1.0 {
746 return Err(invalid_params!(
747 "Bernoulli",
748 "Probability must be in [0, 1]",
749 InvalidProbability,
750 "p" => format!("{}", self.p()),
751 "expected" => "[0.0, 1.0]"
752 ));
753 }
754 Ok(())
755 }
756}
757
758impl Validate for Categorical {
759 fn validate(&self) -> FugueResult<()> {
760 if self.probs().is_empty() {
761 return Err(invalid_params!(
762 "Categorical",
763 "Probability vector cannot be empty",
764 InvalidProbability,
765 "length" => "0"
766 ));
767 }
768
769 let sum: f64 = self.probs().iter().sum();
770 if (sum - 1.0).abs() > 1e-6 {
771 return Err(invalid_params!(
772 "Categorical",
773 "Probabilities must sum to 1.0",
774 InvalidProbability,
775 "sum" => format!("{:.6}", sum),
776 "expected" => "1.0",
777 "tolerance" => "1e-6"
778 ));
779 }
780
781 for (i, &p) in self.probs().iter().enumerate() {
782 if !p.is_finite() || p < 0.0 {
783 return Err(invalid_params!(
784 "Categorical",
785 "All probabilities must be non-negative and finite",
786 InvalidProbability,
787 "index" => format!("{}", i),
788 "value" => format!("{}", p),
789 "expected" => ">= 0.0 and finite"
790 ));
791 }
792 }
793
794 Ok(())
795 }
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::addr;
802
803 #[test]
804 fn error_code_category_and_description() {
805 let code = ErrorCode::InvalidMean;
806 assert!(ErrorCode::InvalidMean.description().contains("mean"));
807 assert_eq!(code.category(), ErrorCategory::DistributionValidation);
808
809 let code = ErrorCode::NumericalOverflow;
810 assert_eq!(code.category(), ErrorCategory::NumericalComputation);
811 }
812
813 #[test]
814 fn invalid_parameters_constructor_and_context() {
815 let err = FugueError::invalid_parameters("Normal", "bad params", ErrorCode::InvalidMean)
816 .with_context("mu", "nan")
817 .with_source_location("file.rs", 10);
818
819 let msg = format!("{}", err);
820 assert!(msg.contains("Invalid parameters for Normal"));
821 assert!(msg.contains("mu=nan"));
822 assert_eq!(err.code(), ErrorCode::InvalidMean);
823 assert_eq!(err.category(), ErrorCategory::DistributionValidation);
824 }
825
826 #[test]
827 fn error_macros_create_expected_variants() {
828 let e1 = invalid_params!("Uniform", "bad range", InvalidRange, "low" => "1", "high" => "0");
829 match e1 {
830 FugueError::InvalidParameters { code, .. } => assert_eq!(code, ErrorCode::InvalidRange),
831 _ => panic!("expected InvalidParameters"),
832 }
833
834 let e2 = numerical_error!("compute", "overflow", NumericalOverflow, "x" => "1e309");
835 match e2 {
836 FugueError::NumericalError { code, .. } => {
837 assert_eq!(code, ErrorCode::NumericalOverflow)
838 }
839 _ => panic!("expected NumericalError"),
840 }
841
842 let e3 = trace_error!("lookup", Some(addr!("x")), "missing", TraceAddressNotFound);
843 match e3 {
844 FugueError::TraceError { code, .. } => {
845 assert_eq!(code, ErrorCode::TraceAddressNotFound)
846 }
847 _ => panic!("expected TraceError"),
848 }
849 }
850
851 #[test]
852 fn type_mismatch_constructor() {
853 let e = FugueError::type_mismatch(addr!("a"), "f64", "bool");
854 assert_eq!(e.code(), ErrorCode::TypeMismatch);
855 assert_eq!(e.category(), ErrorCategory::TypeSystem);
856 let msg = format!("{}", e);
857 assert!(msg.contains("Type mismatch"));
858 }
859
860 #[test]
861 fn validate_trait_on_valid_distributions() {
862 assert!(Normal::new(0.0, 1.0).unwrap().validate().is_ok());
863 assert!(Uniform::new(0.0, 1.0).unwrap().validate().is_ok());
864 assert!(Bernoulli::new(0.5).unwrap().validate().is_ok());
865 assert!(Categorical::new(vec![0.2, 0.8]).unwrap().validate().is_ok());
866 }
867
868 #[test]
869 fn error_cause_chaining_and_display_variants() {
870 let base = FugueError::invalid_parameters("Normal", "bad", ErrorCode::InvalidMean);
872 let ctx = ErrorContext::new().with_cause(base.clone());
873 let inf = FugueError::InferenceError {
874 algorithm: "MH".into(),
875 reason: "did not converge".into(),
876 code: ErrorCode::InferenceConvergenceFailed,
877 context: ctx.clone(),
878 };
879 let msg = format!("{}", inf);
880 assert!(msg.contains("Inference error"));
881
882 let model_err = FugueError::ModelError {
883 address: Some(crate::addr!("x")),
884 reason: "failed".into(),
885 code: ErrorCode::ModelExecutionFailed,
886 context: ctx,
887 };
888 let msg2 = format!("{}", model_err);
889 assert!(msg2.contains("Model error"));
890 }
891
892 #[test]
893 fn from_conversions_cover_paths() {
894 let e_float: FugueError = "abc".parse::<f64>().unwrap_err().into();
896 assert!(matches!(e_float, FugueError::NumericalError { .. }));
897
898 let e_int: FugueError = "abc".parse::<i32>().unwrap_err().into();
900 assert!(matches!(e_int, FugueError::NumericalError { .. }));
901
902 let e_str: FugueError = "oops".into();
904 assert!(matches!(e_str, FugueError::ModelError { .. }));
905
906 let e_string: FugueError = String::from("oops").into();
908 assert!(matches!(e_string, FugueError::ModelError { .. }));
909 }
910}