twine_thermo/model/coolprop/
error.rs1use std::sync::PoisonError;
2
3use thiserror::Error;
4
5use crate::PropertyError;
6
7#[derive(Debug, Error)]
9#[non_exhaustive]
10pub enum CoolPropError {
11 #[error(transparent)]
12 Rfluids(#[from] rfluids::native::CoolPropError),
13 #[error("CoolProp abstract state mutex poisoned")]
14 Poisoned,
15}
16
17impl<T> From<PoisonError<T>> for CoolPropError {
18 fn from(_: PoisonError<T>) -> Self {
19 CoolPropError::Poisoned
20 }
21}
22
23impl From<CoolPropError> for PropertyError {
24 fn from(error: CoolPropError) -> Self {
25 match error {
26 CoolPropError::Rfluids(message) => map_error_message(&message.to_string()),
27 CoolPropError::Poisoned => PropertyError::Calculation {
28 context: "CoolProp abstract state mutex poisoned".to_string(),
29 },
30 }
31 }
32}
33
34fn map_error_message(message: &str) -> PropertyError {
39 const UNDEFINED_MARKERS: &[&str] = &["not defined"];
40 const OUT_OF_DOMAIN_MARKERS: &[&str] = &[
41 "not in range",
42 "out of range",
43 "outside the range of validity",
44 "must be in range",
45 "must be between",
46 "quality must be",
47 ];
48 const INVALID_STATE_MARKERS: &[&str] =
49 &["not a valid number", "invalid state", "invalid number"];
50
51 let lowered = message.to_lowercase();
52 let context = message.to_string();
53
54 if contains_any(&lowered, UNDEFINED_MARKERS) {
55 PropertyError::Undefined { context }
56 } else if contains_any(&lowered, OUT_OF_DOMAIN_MARKERS) {
57 PropertyError::OutOfDomain { context }
58 } else if contains_any(&lowered, INVALID_STATE_MARKERS) {
59 PropertyError::InvalidState { context }
60 } else {
61 PropertyError::Calculation { context }
62 }
63}
64
65fn contains_any(haystack: &str, needles: &[&str]) -> bool {
67 needles.iter().any(|needle| haystack.contains(needle))
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn map_error_message_returns_undefined() {
76 let error = map_error_message("inputs are not defined for this region");
77 assert!(matches!(error, PropertyError::Undefined { .. }));
78 }
79
80 #[test]
81 fn map_error_message_returns_out_of_domain() {
82 let error = map_error_message("input 3.14 is out of range");
83 assert!(matches!(error, PropertyError::OutOfDomain { .. }));
84 }
85
86 #[test]
87 fn map_error_message_returns_invalid_state() {
88 let error = map_error_message("p is not a valid number");
89 assert!(matches!(error, PropertyError::InvalidState { .. }));
90 }
91
92 #[test]
93 fn map_error_message_returns_calculation_by_default() {
94 let error = map_error_message("some other failure");
95 assert!(matches!(error, PropertyError::Calculation { .. }));
96 }
97}