1use std::borrow::Cow;
138use std::error::Error as StdError;
139use std::fmt;
140use std::time::Duration;
141
142use thiserror::Error;
143
144#[cfg(feature = "backtrace")]
145use std::backtrace::Backtrace;
146
147pub type Result<T> = std::result::Result<T, Error>;
149
150const MAX_ERROR_MESSAGE_LEN: usize = 1024;
152
153fn truncate_message(mut msg: String) -> String {
155 if msg.len() > MAX_ERROR_MESSAGE_LEN {
156 msg.truncate(MAX_ERROR_MESSAGE_LEN);
157 msg.push_str("... (truncated)");
158 }
159 msg
160}
161
162#[derive(Debug)]
177#[non_exhaustive]
178pub struct ExchangeErrorDetails {
179 pub code: String,
181 pub message: String,
183 pub data: Option<serde_json::Value>,
185 #[cfg(feature = "backtrace")]
187 pub backtrace: Backtrace,
188}
189
190impl ExchangeErrorDetails {
191 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
193 Self {
194 code: code.into(),
195 message: message.into(),
196 data: None,
197 #[cfg(feature = "backtrace")]
198 backtrace: Backtrace::capture(),
199 }
200 }
201
202 pub fn with_data(
204 code: impl Into<String>,
205 message: impl Into<String>,
206 data: serde_json::Value,
207 ) -> Self {
208 Self {
209 code: code.into(),
210 message: message.into(),
211 data: Some(data),
212 #[cfg(feature = "backtrace")]
213 backtrace: Backtrace::capture(),
214 }
215 }
216}
217
218impl fmt::Display for ExchangeErrorDetails {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 write!(f, "{} (code: {})", self.message, self.code)
221 }
222}
223
224#[derive(Error, Debug)]
257#[non_exhaustive]
258pub enum NetworkError {
259 #[error("Request failed with status {status}: {message}")]
261 RequestFailed {
262 status: u16,
264 message: String,
266 },
267
268 #[error("Request timeout")]
270 Timeout,
271
272 #[error("Connection failed: {0}")]
274 ConnectionFailed(String),
275
276 #[error("DNS resolution failed: {0}")]
278 DnsResolution(String),
279
280 #[error("SSL/TLS error: {0}")]
282 Ssl(String),
283
284 #[error("Transport error")]
287 Transport(#[source] Box<dyn StdError + Send + Sync + 'static>),
288}
289
290#[derive(Error, Debug)]
326#[non_exhaustive]
327pub enum ParseError {
328 #[error("Failed to parse decimal: {0}")]
330 Decimal(#[from] rust_decimal::Error),
331
332 #[error("Failed to deserialize JSON: {0}")]
334 Json(#[from] serde_json::Error),
335
336 #[error("Failed to parse timestamp: {0}")]
338 Timestamp(Cow<'static, str>),
339
340 #[error("Missing required field: {0}")]
342 MissingField(Cow<'static, str>),
343
344 #[error("Invalid value for '{field}': {message}")]
346 InvalidValue {
347 field: Cow<'static, str>,
349 message: Cow<'static, str>,
351 },
352
353 #[error("Invalid format for '{field}': {message}")]
355 InvalidFormat {
356 field: Cow<'static, str>,
358 message: Cow<'static, str>,
360 },
361}
362
363impl ParseError {
364 #[must_use]
366 pub fn missing_field(field: &'static str) -> Self {
367 Self::MissingField(Cow::Borrowed(field))
368 }
369
370 #[must_use]
372 pub fn missing_field_owned(field: String) -> Self {
373 Self::MissingField(Cow::Owned(field))
374 }
375
376 pub fn invalid_value(
378 field: impl Into<Cow<'static, str>>,
379 message: impl Into<Cow<'static, str>>,
380 ) -> Self {
381 Self::InvalidValue {
382 field: field.into(),
383 message: message.into(),
384 }
385 }
386
387 #[must_use]
389 pub fn timestamp(message: &'static str) -> Self {
390 Self::Timestamp(Cow::Borrowed(message))
391 }
392
393 #[must_use]
395 pub fn timestamp_owned(message: String) -> Self {
396 Self::Timestamp(Cow::Owned(message))
397 }
398
399 pub fn invalid_format(
401 field: impl Into<Cow<'static, str>>,
402 message: impl Into<Cow<'static, str>>,
403 ) -> Self {
404 Self::InvalidFormat {
405 field: field.into(),
406 message: message.into(),
407 }
408 }
409}
410
411#[derive(Error, Debug)]
432#[non_exhaustive]
433pub enum OrderError {
434 #[error("Order creation failed: {0}")]
436 CreationFailed(String),
437
438 #[error("Order cancellation failed: {0}")]
440 CancellationFailed(String),
441
442 #[error("Order modification failed: {0}")]
444 ModificationFailed(String),
445
446 #[error("Invalid order parameters: {0}")]
448 InvalidParameters(String),
449}
450
451#[derive(Error, Debug)]
467#[non_exhaustive]
468pub enum Error {
469 #[error("Exchange error: {0}")]
472 Exchange(Box<ExchangeErrorDetails>),
473
474 #[error("Network error: {0}")]
477 Network(Box<NetworkError>),
478
479 #[error("Authentication error: {0}")]
481 Authentication(Cow<'static, str>),
482
483 #[error("Rate limit exceeded: {message}")]
485 RateLimit {
486 message: Cow<'static, str>,
488 retry_after: Option<Duration>,
490 },
491
492 #[error("Invalid request: {0}")]
494 InvalidRequest(Cow<'static, str>),
495
496 #[error("Order error: {0}")]
498 Order(Box<OrderError>),
499
500 #[error("Insufficient balance: {0}")]
502 InsufficientBalance(Cow<'static, str>),
503
504 #[error("Invalid order: {0}")]
506 InvalidOrder(Cow<'static, str>),
507
508 #[error("Order not found: {0}")]
510 OrderNotFound(Cow<'static, str>),
511
512 #[error("Market not found: {0}")]
514 MarketNotFound(Cow<'static, str>),
515
516 #[error("Parse error: {0}")]
518 Parse(Box<ParseError>),
519
520 #[error("WebSocket error: {0}")]
523 WebSocket(#[source] Box<dyn StdError + Send + Sync + 'static>),
524
525 #[error("Timeout: {0}")]
527 Timeout(Cow<'static, str>),
528
529 #[error("Not implemented: {0}")]
531 NotImplemented(Cow<'static, str>),
532
533 #[error("Cancelled: {0}")]
548 Cancelled(Cow<'static, str>),
549
550 #[error("Resource exhausted: {0}")]
565 ResourceExhausted(Cow<'static, str>),
566
567 #[error("{context}")]
569 Context {
570 context: String,
572 #[source]
574 source: Box<Error>,
575 },
576}
577
578impl Error {
579 pub fn exchange(code: impl Into<String>, message: impl Into<String>) -> Self {
591 Self::Exchange(Box::new(ExchangeErrorDetails::new(code, message)))
592 }
593
594 pub fn exchange_with_data(
596 code: impl Into<String>,
597 message: impl Into<String>,
598 data: serde_json::Value,
599 ) -> Self {
600 Self::Exchange(Box::new(ExchangeErrorDetails::with_data(
601 code, message, data,
602 )))
603 }
604
605 pub fn rate_limit(
621 message: impl Into<Cow<'static, str>>,
622 retry_after: Option<Duration>,
623 ) -> Self {
624 Self::RateLimit {
625 message: message.into(),
626 retry_after,
627 }
628 }
629
630 pub fn authentication(msg: impl Into<Cow<'static, str>>) -> Self {
633 Self::Authentication(msg.into())
634 }
635
636 pub fn generic(msg: impl Into<Cow<'static, str>>) -> Self {
638 Self::InvalidRequest(msg.into())
639 }
640
641 pub fn network(msg: impl Into<String>) -> Self {
643 Self::Network(Box::new(NetworkError::ConnectionFailed(msg.into())))
644 }
645
646 pub fn market_not_found(symbol: impl Into<Cow<'static, str>>) -> Self {
649 Self::MarketNotFound(symbol.into())
650 }
651
652 pub fn not_implemented(feature: impl Into<Cow<'static, str>>) -> Self {
655 Self::NotImplemented(feature.into())
656 }
657
658 pub fn cancelled(msg: impl Into<Cow<'static, str>>) -> Self {
677 Self::Cancelled(msg.into())
678 }
679
680 pub fn resource_exhausted(msg: impl Into<Cow<'static, str>>) -> Self {
700 Self::ResourceExhausted(msg.into())
701 }
702
703 pub fn invalid_request(msg: impl Into<Cow<'static, str>>) -> Self {
705 Self::InvalidRequest(msg.into())
706 }
707
708 pub fn invalid_argument(msg: impl Into<Cow<'static, str>>) -> Self {
710 Self::InvalidRequest(msg.into())
711 }
712
713 pub fn bad_symbol(symbol: impl Into<String>) -> Self {
715 let s = symbol.into();
716 Self::InvalidRequest(Cow::Owned(format!("Bad symbol: {s}")))
717 }
718
719 pub fn insufficient_balance(msg: impl Into<Cow<'static, str>>) -> Self {
721 Self::InsufficientBalance(msg.into())
722 }
723
724 pub fn timeout(msg: impl Into<Cow<'static, str>>) -> Self {
726 Self::Timeout(msg.into())
727 }
728
729 pub fn websocket(msg: impl Into<String>) -> Self {
731 Self::WebSocket(Box::new(SimpleError(msg.into())))
732 }
733
734 pub fn websocket_error<E: StdError + Send + Sync + 'static>(err: E) -> Self {
736 Self::WebSocket(Box::new(err))
737 }
738
739 #[must_use]
752 pub fn context(self, context: impl Into<String>) -> Self {
753 Self::Context {
754 context: context.into(),
755 source: Box::new(self),
756 }
757 }
758
759 fn iter_chain(&self) -> impl Iterator<Item = &Error> {
764 std::iter::successors(Some(self), |err| match err {
765 Error::Context { source, .. } => Some(source.as_ref()),
766 _ => None,
767 })
768 }
769
770 #[must_use]
772 pub fn root_cause(&self) -> &Error {
773 self.iter_chain().last().unwrap_or(self)
774 }
775
776 pub fn find_variant<F>(&self, matcher: F) -> Option<&Error>
779 where
780 F: Fn(&Error) -> bool,
781 {
782 self.iter_chain().find(|e| matcher(e))
783 }
784
785 #[must_use]
800 pub fn report(&self) -> String {
801 use std::fmt::Write;
802 let mut report = String::new();
803 report.push_str(&self.to_string());
804
805 let mut current: Option<&(dyn StdError + 'static)> = self.source();
806 while let Some(err) = current {
807 let _ = write!(report, "\nCaused by: {err}");
808 current = err.source();
809 }
810 report
811 }
812
813 #[must_use]
823 pub fn is_retryable(&self) -> bool {
824 match self {
825 Error::Network(ne) => matches!(
826 ne.as_ref(),
827 NetworkError::Timeout | NetworkError::ConnectionFailed(_)
828 ),
829 Error::RateLimit { .. } | Error::Timeout(_) => true,
830 Error::Context { source, .. } => source.is_retryable(),
831 _ => false,
832 }
833 }
834
835 #[must_use]
837 pub fn retry_after(&self) -> Option<Duration> {
838 match self {
839 Error::RateLimit { retry_after, .. } => *retry_after,
840 Error::Context { source, .. } => source.retry_after(),
841 _ => None,
842 }
843 }
844
845 #[must_use]
848 pub fn as_rate_limit(&self) -> Option<(&str, Option<Duration>)> {
849 match self {
850 Error::RateLimit {
851 message,
852 retry_after,
853 } => Some((message.as_ref(), *retry_after)),
854 Error::Context { source, .. } => source.as_rate_limit(),
855 _ => None,
856 }
857 }
858
859 #[must_use]
862 pub fn as_authentication(&self) -> Option<&str> {
863 match self {
864 Error::Authentication(msg) => Some(msg.as_ref()),
865 Error::Context { source, .. } => source.as_authentication(),
866 _ => None,
867 }
868 }
869
870 #[must_use]
886 pub fn as_cancelled(&self) -> Option<&str> {
887 match self {
888 Error::Cancelled(msg) => Some(msg.as_ref()),
889 Error::Context { source, .. } => source.as_cancelled(),
890 _ => None,
891 }
892 }
893
894 #[must_use]
910 pub fn as_resource_exhausted(&self) -> Option<&str> {
911 match self {
912 Error::ResourceExhausted(msg) => Some(msg.as_ref()),
913 Error::Context { source, .. } => source.as_resource_exhausted(),
914 _ => None,
915 }
916 }
917
918 #[must_use]
920 pub fn downcast_websocket<T: StdError + 'static>(&self) -> Option<&T> {
921 match self {
922 Error::WebSocket(e) => e.downcast_ref::<T>(),
923 Error::Context { source, .. } => source.downcast_websocket(),
924 _ => None,
925 }
926 }
927}
928
929#[derive(Debug)]
932struct SimpleError(String);
933
934impl fmt::Display for SimpleError {
935 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
936 write!(f, "{}", self.0)
937 }
938}
939
940impl StdError for SimpleError {}
941
942impl From<NetworkError> for Error {
945 fn from(e: NetworkError) -> Self {
946 Error::Network(Box::new(e))
947 }
948}
949
950impl From<Box<NetworkError>> for Error {
951 fn from(e: Box<NetworkError>) -> Self {
952 Error::Network(e)
953 }
954}
955
956impl From<ParseError> for Error {
957 fn from(e: ParseError) -> Self {
958 Error::Parse(Box::new(e))
959 }
960}
961
962impl From<Box<ParseError>> for Error {
963 fn from(e: Box<ParseError>) -> Self {
964 Error::Parse(e)
965 }
966}
967
968impl From<OrderError> for Error {
969 fn from(e: OrderError) -> Self {
970 Error::Order(Box::new(e))
971 }
972}
973
974impl From<Box<OrderError>> for Error {
975 fn from(e: Box<OrderError>) -> Self {
976 Error::Order(e)
977 }
978}
979
980impl From<serde_json::Error> for Error {
981 fn from(e: serde_json::Error) -> Self {
982 Error::Parse(Box::new(ParseError::Json(e)))
983 }
984}
985
986impl From<rust_decimal::Error> for Error {
987 fn from(e: rust_decimal::Error) -> Self {
988 Error::Parse(Box::new(ParseError::Decimal(e)))
989 }
990}
991
992impl From<reqwest::Error> for NetworkError {
994 fn from(e: reqwest::Error) -> Self {
995 if e.is_timeout() {
996 NetworkError::Timeout
997 } else if e.is_connect() {
998 NetworkError::ConnectionFailed(truncate_message(e.to_string()))
999 } else if let Some(status) = e.status() {
1000 NetworkError::RequestFailed {
1001 status: status.as_u16(),
1002 message: truncate_message(e.to_string()),
1003 }
1004 } else {
1005 NetworkError::Transport(Box::new(e))
1006 }
1007 }
1008}
1009
1010impl From<reqwest::Error> for Error {
1011 fn from(e: reqwest::Error) -> Self {
1012 Error::Network(Box::new(NetworkError::from(e)))
1013 }
1014}
1015
1016pub trait ContextExt<T, E> {
1067 fn context<C>(self, context: C) -> Result<T>
1069 where
1070 C: fmt::Display + Send + Sync + 'static;
1071
1072 fn with_context<C, F>(self, f: F) -> Result<T>
1074 where
1075 C: fmt::Display + Send + Sync + 'static,
1076 F: FnOnce() -> C;
1077}
1078
1079impl<T, E> ContextExt<T, E> for std::result::Result<T, E>
1080where
1081 E: Into<Error>,
1082{
1083 fn context<C>(self, context: C) -> Result<T>
1084 where
1085 C: fmt::Display + Send + Sync + 'static,
1086 {
1087 self.map_err(|e| e.into().context(context.to_string()))
1088 }
1089
1090 fn with_context<C, F>(self, f: F) -> Result<T>
1091 where
1092 C: fmt::Display + Send + Sync + 'static,
1093 F: FnOnce() -> C,
1094 {
1095 self.map_err(|e| e.into().context(f().to_string()))
1096 }
1097}
1098
1099impl<T> ContextExt<T, Error> for Option<T> {
1100 fn context<C>(self, context: C) -> Result<T>
1101 where
1102 C: fmt::Display + Send + Sync + 'static,
1103 {
1104 self.ok_or_else(|| Error::generic(context.to_string()))
1105 }
1106
1107 fn with_context<C, F>(self, f: F) -> Result<T>
1108 where
1109 C: fmt::Display + Send + Sync + 'static,
1110 F: FnOnce() -> C,
1111 {
1112 self.ok_or_else(|| Error::generic(f().to_string()))
1113 }
1114}
1115
1116#[deprecated(since = "0.2.0", note = "Use ContextExt instead")]
1120pub trait ErrorContext<T>: Sized {
1121 fn context(self, context: impl fmt::Display) -> Result<T>;
1123}
1124
1125#[allow(deprecated)]
1126impl<T, E: Into<Error>> ErrorContext<T> for std::result::Result<T, E> {
1127 fn context(self, context: impl fmt::Display) -> Result<T> {
1128 self.map_err(|e| e.into().context(context.to_string()))
1129 }
1130}
1131
1132#[cfg(test)]
1133mod tests {
1134 use super::*;
1135
1136 #[test]
1137 fn test_exchange_error_details_display() {
1138 let details = ExchangeErrorDetails::new("400", "Bad Request");
1139 let display = format!("{}", details);
1140 assert!(display.contains("400"));
1141 assert!(display.contains("Bad Request"));
1142 }
1143
1144 #[test]
1145 fn test_exchange_error_details_with_data() {
1146 let data = serde_json::json!({"error": "test"});
1147 let details = ExchangeErrorDetails::with_data("500", "Internal Error", data.clone());
1148 assert_eq!(details.code, "500");
1149 assert_eq!(details.message, "Internal Error");
1150 assert_eq!(details.data, Some(data));
1151 }
1152
1153 #[test]
1154 fn test_error_exchange_creation() {
1155 let err = Error::exchange("400", "Bad Request");
1156 if let Error::Exchange(details) = &err {
1157 assert_eq!(details.code, "400");
1158 assert_eq!(details.message, "Bad Request");
1159 } else {
1160 panic!("Expected Exchange variant");
1161 }
1162 }
1163
1164 #[test]
1165 fn test_error_exchange_string_code() {
1166 let err = Error::exchange("INVALID_SYMBOL", "Symbol not found");
1168 if let Error::Exchange(details) = &err {
1169 assert_eq!(details.code, "INVALID_SYMBOL");
1170 } else {
1171 panic!("Expected Exchange variant");
1172 }
1173 }
1174
1175 #[test]
1176 fn test_error_authentication() {
1177 let err = Error::authentication("Invalid API key");
1178 assert!(matches!(err, Error::Authentication(_)));
1179 assert!(err.to_string().contains("Invalid API key"));
1180 }
1181
1182 #[test]
1183 fn test_error_rate_limit() {
1184 let err = Error::rate_limit("Too many requests", Some(Duration::from_secs(60)));
1185 if let Error::RateLimit {
1186 message,
1187 retry_after,
1188 } = &err
1189 {
1190 assert_eq!(message.as_ref(), "Too many requests");
1191 assert_eq!(*retry_after, Some(Duration::from_secs(60)));
1192 } else {
1193 panic!("Expected RateLimit variant");
1194 }
1195 }
1196
1197 #[test]
1198 fn test_error_market_not_found() {
1199 let err = Error::market_not_found("BTC/USDT");
1200 assert!(matches!(err, Error::MarketNotFound(_)));
1201 assert!(err.to_string().contains("BTC/USDT"));
1202 }
1203
1204 #[test]
1205 fn test_error_context() {
1206 let base = Error::network("Connection refused");
1207 let with_context = base.context("Failed to fetch ticker");
1208
1209 assert!(matches!(with_context, Error::Context { .. }));
1210 assert!(with_context.to_string().contains("Failed to fetch ticker"));
1211 }
1212
1213 #[test]
1214 fn test_error_context_chain() {
1215 let base = Error::network("Connection refused");
1216 let ctx1 = base.context("Layer 1");
1217 let ctx2 = ctx1.context("Layer 2");
1218
1219 let report = ctx2.report();
1221 assert!(report.contains("Layer 2"));
1222 assert!(report.contains("Layer 1"));
1223 assert!(report.contains("Connection refused"));
1224 }
1225
1226 #[test]
1227 fn test_error_root_cause() {
1228 let base = Error::network("Connection refused");
1229 let ctx1 = base.context("Layer 1");
1230 let ctx2 = ctx1.context("Layer 2");
1231
1232 let root = ctx2.root_cause();
1233 assert!(matches!(root, Error::Network(_)));
1234 }
1235
1236 #[test]
1237 fn test_error_is_retryable() {
1238 assert!(Error::rate_limit("test", None).is_retryable());
1240 assert!(Error::timeout("test").is_retryable());
1241 assert!(Error::from(NetworkError::Timeout).is_retryable());
1242 assert!(Error::from(NetworkError::ConnectionFailed("test".to_string())).is_retryable());
1243
1244 assert!(!Error::authentication("test").is_retryable());
1246 assert!(!Error::invalid_request("test").is_retryable());
1247 assert!(!Error::market_not_found("test").is_retryable());
1248 }
1249
1250 #[test]
1251 fn test_error_is_retryable_through_context() {
1252 let err = Error::rate_limit("test", Some(Duration::from_secs(30)))
1253 .context("Layer 1")
1254 .context("Layer 2");
1255
1256 assert!(err.is_retryable());
1257 assert_eq!(err.retry_after(), Some(Duration::from_secs(30)));
1258 }
1259
1260 #[test]
1261 fn test_error_as_rate_limit() {
1262 let err = Error::rate_limit("Too many requests", Some(Duration::from_secs(60)));
1263 let (msg, retry) = err.as_rate_limit().unwrap();
1264 assert_eq!(msg, "Too many requests");
1265 assert_eq!(retry, Some(Duration::from_secs(60)));
1266 }
1267
1268 #[test]
1269 fn test_error_as_rate_limit_through_context() {
1270 let err = Error::rate_limit("Too many requests", Some(Duration::from_secs(60)))
1271 .context("Wrapped");
1272 let (msg, retry) = err.as_rate_limit().unwrap();
1273 assert_eq!(msg, "Too many requests");
1274 assert_eq!(retry, Some(Duration::from_secs(60)));
1275 }
1276
1277 #[test]
1278 fn test_error_as_authentication() {
1279 let err = Error::authentication("Invalid key");
1280 assert_eq!(err.as_authentication(), Some("Invalid key"));
1281 }
1282
1283 #[test]
1284 fn test_error_as_authentication_through_context() {
1285 let err = Error::authentication("Invalid key").context("Wrapped");
1286 assert_eq!(err.as_authentication(), Some("Invalid key"));
1287 }
1288
1289 #[test]
1290 fn test_network_error_request_failed() {
1291 let err = NetworkError::RequestFailed {
1292 status: 404,
1293 message: "Not Found".to_string(),
1294 };
1295 assert!(err.to_string().contains("404"));
1296 assert!(err.to_string().contains("Not Found"));
1297 }
1298
1299 #[test]
1300 fn test_parse_error_missing_field() {
1301 let err = ParseError::missing_field("price");
1302 assert!(err.to_string().contains("price"));
1303 }
1304
1305 #[test]
1306 fn test_parse_error_invalid_value() {
1307 let err = ParseError::invalid_value("amount", "must be positive");
1308 let display = err.to_string();
1309 assert!(display.contains("amount"));
1310 assert!(display.contains("must be positive"));
1311 }
1312
1313 #[test]
1314 fn test_error_display() {
1315 let err = Error::exchange("400", "Bad Request");
1316 let display = format!("{}", err);
1317 assert!(display.contains("400"));
1318 assert!(display.contains("Bad Request"));
1319 }
1320
1321 #[test]
1322 fn test_context_ext_result() {
1323 let result: std::result::Result<(), Error> = Err(Error::network("test"));
1324 let with_context = ContextExt::context(result, "Operation failed");
1325 assert!(with_context.is_err());
1326 let err = with_context.unwrap_err();
1327 assert!(err.to_string().contains("Operation failed"));
1328 }
1329
1330 #[test]
1331 fn test_context_ext_option() {
1332 let opt: Option<i32> = None;
1333 let result = opt.context("Value not found");
1334 assert!(result.is_err());
1335 let err = result.unwrap_err();
1336 assert!(err.to_string().contains("Value not found"));
1337 }
1338
1339 #[test]
1340 fn test_from_serde_json_error() {
1341 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
1342 let err: Error = json_err.into();
1343 assert!(matches!(err, Error::Parse(_)));
1344 }
1345
1346 #[test]
1347 fn test_from_network_error() {
1348 let network_err = NetworkError::Timeout;
1349 let err: Error = network_err.into();
1350 assert!(matches!(err, Error::Network(_)));
1351 }
1352
1353 #[test]
1354 fn test_from_order_error() {
1355 let order_err = OrderError::CreationFailed("test".to_string());
1356 let err: Error = order_err.into();
1357 assert!(matches!(err, Error::Order(_)));
1358 }
1359
1360 #[test]
1361 fn test_truncate_message() {
1362 let short = "short message".to_string();
1363 assert_eq!(truncate_message(short.clone()), short);
1364
1365 let long = "x".repeat(2000);
1366 let truncated = truncate_message(long);
1367 assert!(truncated.len() < 2000);
1368 assert!(truncated.ends_with("... (truncated)"));
1369 }
1370
1371 #[test]
1373 fn error_is_send_sync_static() {
1374 fn assert_traits<T: Send + Sync + 'static + StdError>() {}
1375 assert_traits::<Error>();
1376 assert_traits::<NetworkError>();
1377 assert_traits::<ParseError>();
1378 assert_traits::<OrderError>();
1379 }
1380
1381 #[test]
1382 fn error_size_is_reasonable() {
1383 let size = std::mem::size_of::<Error>();
1384 assert!(
1386 size <= 56,
1387 "Error enum size {} exceeds 56 bytes, consider boxing large variants",
1388 size
1389 );
1390 }
1391}
1392
1393#[cfg(test)]
1396mod property_tests {
1397 use super::*;
1398 use proptest::prelude::*;
1399 use std::thread;
1400
1401 fn arb_error_code() -> impl Strategy<Value = String> {
1409 prop_oneof![
1410 (100u32..600).prop_map(|n| n.to_string()),
1412 "[A-Z_]{3,20}",
1414 "[A-Za-z0-9_-]{1,30}",
1416 ]
1417 }
1418
1419 fn arb_error_message() -> impl Strategy<Value = String> {
1421 prop_oneof![
1422 Just("".to_string()),
1423 "[a-zA-Z0-9 .,!?-]{1,100}",
1424 "\\PC{1,50}",
1426 ]
1427 }
1428
1429 fn arb_duration() -> impl Strategy<Value = Duration> {
1431 (0u64..=u64::MAX / 2).prop_map(Duration::from_nanos)
1432 }
1433
1434 fn arb_optional_duration() -> impl Strategy<Value = Option<Duration>> {
1436 prop_oneof![Just(None), arb_duration().prop_map(Some),]
1437 }
1438
1439 fn arb_error() -> impl Strategy<Value = Error> {
1441 prop_oneof![
1442 (arb_error_code(), arb_error_message())
1444 .prop_map(|(code, msg)| Error::exchange(code, msg)),
1445 arb_error_message().prop_map(|msg| Error::authentication(msg)),
1447 (arb_error_message(), arb_optional_duration())
1449 .prop_map(|(msg, retry)| Error::rate_limit(msg, retry)),
1450 arb_error_message().prop_map(|msg| Error::invalid_request(msg)),
1452 arb_error_message().prop_map(|msg| Error::market_not_found(msg)),
1454 arb_error_message().prop_map(|msg| Error::timeout(msg)),
1456 arb_error_message().prop_map(|msg| Error::not_implemented(msg)),
1458 arb_error_message().prop_map(|msg| Error::network(msg)),
1460 arb_error_message().prop_map(|msg| Error::websocket(msg)),
1462 arb_error_message().prop_map(|msg| Error::insufficient_balance(msg)),
1464 ]
1465 }
1466
1467 fn arb_network_error() -> impl Strategy<Value = NetworkError> {
1469 prop_oneof![
1470 Just(()).prop_map(|_| NetworkError::Timeout),
1472 arb_error_message().prop_map(NetworkError::ConnectionFailed),
1473 arb_error_message().prop_map(NetworkError::DnsResolution),
1474 arb_error_message().prop_map(NetworkError::Ssl),
1475 (100u16..600, arb_error_message()).prop_map(|(status, msg)| {
1476 NetworkError::RequestFailed {
1477 status,
1478 message: msg,
1479 }
1480 }),
1481 ]
1482 }
1483
1484 fn arb_parse_error() -> impl Strategy<Value = ParseError> {
1486 prop_oneof![
1487 arb_error_message().prop_map(|msg| ParseError::MissingField(Cow::Owned(msg))),
1488 arb_error_message().prop_map(|msg| ParseError::Timestamp(Cow::Owned(msg))),
1489 (arb_error_message(), arb_error_message()).prop_map(|(field, msg)| {
1490 ParseError::InvalidValue {
1491 field: Cow::Owned(field),
1492 message: Cow::Owned(msg),
1493 }
1494 }),
1495 (arb_error_message(), arb_error_message()).prop_map(|(field, msg)| {
1496 ParseError::InvalidFormat {
1497 field: Cow::Owned(field),
1498 message: Cow::Owned(msg),
1499 }
1500 }),
1501 ]
1502 }
1503
1504 fn arb_order_error() -> impl Strategy<Value = OrderError> {
1506 prop_oneof![
1507 arb_error_message().prop_map(OrderError::CreationFailed),
1508 arb_error_message().prop_map(OrderError::CancellationFailed),
1509 arb_error_message().prop_map(OrderError::ModificationFailed),
1510 arb_error_message().prop_map(OrderError::InvalidParameters),
1511 ]
1512 }
1513
1514 proptest! {
1517 #![proptest_config(ProptestConfig::with_cases(100))]
1518
1519 #[test]
1526 fn prop_error_is_send_sync(error in arb_error()) {
1527 fn assert_send_sync_static<T: Send + Sync + 'static>(_: &T) {}
1529 assert_send_sync_static(&error);
1530
1531 let error_string = error.to_string();
1533 let handle = thread::spawn(move || {
1534 error.to_string()
1536 });
1537 let result = handle.join().expect("Thread should not panic");
1538 prop_assert_eq!(result, error_string);
1539 }
1540
1541 #[test]
1548 fn prop_error_thread_safety_with_static_bound(error in arb_error()) {
1549 fn assert_std_error<T: StdError + Send + Sync + 'static>(_: &T) {}
1551 assert_std_error(&error);
1552
1553 let boxed: Box<dyn StdError + Send + Sync + 'static> = Box::new(error);
1555
1556 let handle = thread::spawn(move || {
1558 boxed.to_string()
1559 });
1560 let _ = handle.join().expect("Thread should not panic");
1561 }
1562
1563 #[test]
1565 fn prop_network_error_is_send_sync(error in arb_network_error()) {
1566 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1567 assert_send_sync_static(&error);
1568
1569 let error_string = error.to_string();
1570 let handle = thread::spawn(move || error.to_string());
1571 let result = handle.join().expect("Thread should not panic");
1572 prop_assert_eq!(result, error_string);
1573 }
1574
1575 #[test]
1577 fn prop_parse_error_is_send_sync(error in arb_parse_error()) {
1578 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1579 assert_send_sync_static(&error);
1580
1581 let error_string = error.to_string();
1582 let handle = thread::spawn(move || error.to_string());
1583 let result = handle.join().expect("Thread should not panic");
1584 prop_assert_eq!(result, error_string);
1585 }
1586
1587 #[test]
1589 fn prop_order_error_is_send_sync(error in arb_order_error()) {
1590 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1591 assert_send_sync_static(&error);
1592
1593 let error_string = error.to_string();
1594 let handle = thread::spawn(move || error.to_string());
1595 let result = handle.join().expect("Thread should not panic");
1596 prop_assert_eq!(result, error_string);
1597 }
1598
1599 #[test]
1601 fn prop_error_with_context_is_send_sync(
1602 base_error in arb_error(),
1603 context1 in arb_error_message(),
1604 context2 in arb_error_message()
1605 ) {
1606 let error_with_context = base_error
1607 .context(context1)
1608 .context(context2);
1609
1610 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1611 assert_send_sync_static(&error_with_context);
1612
1613 let error_string = error_with_context.to_string();
1614 let handle = thread::spawn(move || error_with_context.to_string());
1615 let result = handle.join().expect("Thread should not panic");
1616 prop_assert_eq!(result, error_string);
1617 }
1618
1619 #[test]
1621 fn prop_error_from_network_error_is_send_sync(network_error in arb_network_error()) {
1622 let error: Error = network_error.into();
1623
1624 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1625 assert_send_sync_static(&error);
1626
1627 let error_string = error.to_string();
1628 let handle = thread::spawn(move || error.to_string());
1629 let result = handle.join().expect("Thread should not panic");
1630 prop_assert_eq!(result, error_string);
1631 }
1632
1633 #[test]
1635 fn prop_error_from_parse_error_is_send_sync(parse_error in arb_parse_error()) {
1636 let error: Error = parse_error.into();
1637
1638 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1639 assert_send_sync_static(&error);
1640
1641 let error_string = error.to_string();
1642 let handle = thread::spawn(move || error.to_string());
1643 let result = handle.join().expect("Thread should not panic");
1644 prop_assert_eq!(result, error_string);
1645 }
1646
1647 #[test]
1649 fn prop_error_from_order_error_is_send_sync(order_error in arb_order_error()) {
1650 let error: Error = order_error.into();
1651
1652 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1653 assert_send_sync_static(&error);
1654
1655 let error_string = error.to_string();
1656 let handle = thread::spawn(move || error.to_string());
1657 let result = handle.join().expect("Thread should not panic");
1658 prop_assert_eq!(result, error_string);
1659 }
1660 }
1661
1662 #[test]
1667 fn static_assert_error_traits() {
1668 const fn assert_send<T: Send>() {}
1670 const fn assert_sync<T: Sync>() {}
1671 const fn assert_static<T: 'static>() {}
1672 const fn assert_std_error<T: StdError>() {}
1673
1674 assert_send::<Error>();
1676 assert_sync::<Error>();
1677 assert_static::<Error>();
1678 assert_std_error::<Error>();
1679
1680 assert_send::<NetworkError>();
1682 assert_sync::<NetworkError>();
1683 assert_static::<NetworkError>();
1684 assert_std_error::<NetworkError>();
1685
1686 assert_send::<ParseError>();
1688 assert_sync::<ParseError>();
1689 assert_static::<ParseError>();
1690 assert_std_error::<ParseError>();
1691
1692 assert_send::<OrderError>();
1694 assert_sync::<OrderError>();
1695 assert_static::<OrderError>();
1696 assert_std_error::<OrderError>();
1697
1698 assert_send::<ExchangeErrorDetails>();
1700 assert_sync::<ExchangeErrorDetails>();
1701 assert_static::<ExchangeErrorDetails>();
1702 }
1703
1704 #[test]
1706 fn static_assert_anyhow_compatibility() {
1707 fn can_convert_to_anyhow<E: StdError + Send + Sync + 'static>(_: E) -> anyhow::Error {
1708 anyhow::Error::msg("test")
1709 }
1710
1711 let _ = can_convert_to_anyhow(Error::authentication("test"));
1713 let _ = can_convert_to_anyhow(NetworkError::Timeout);
1714 let _ = can_convert_to_anyhow(ParseError::missing_field("test"));
1715 let _ = can_convert_to_anyhow(OrderError::CreationFailed("test".to_string()));
1716 }
1717
1718 #[test]
1720 fn static_assert_async_compatibility() {
1721 fn can_be_spawned<F: std::future::Future + Send + 'static>(_: F) {}
1722
1723 async fn returns_error() -> std::result::Result<(), Error> {
1725 Err(Error::authentication("test"))
1726 }
1727
1728 can_be_spawned(returns_error());
1729 }
1730
1731 proptest! {
1734 #![proptest_config(ProptestConfig::with_cases(100))]
1735
1736 #[test]
1743 fn prop_http_status_code_preservation(status in 100u16..600u16, message in arb_error_message()) {
1744 let network_error = NetworkError::RequestFailed {
1746 status,
1747 message: message.clone(),
1748 };
1749
1750 if let NetworkError::RequestFailed { status: preserved_status, message: preserved_message } = &network_error {
1752 prop_assert_eq!(
1753 *preserved_status, status,
1754 "HTTP status code {} was not preserved, got {}",
1755 status, preserved_status
1756 );
1757 prop_assert_eq!(
1758 preserved_message, &message,
1759 "Error message was not preserved"
1760 );
1761 } else {
1762 prop_assert!(false, "Expected RequestFailed variant");
1763 }
1764
1765 let display = network_error.to_string();
1767 prop_assert!(
1768 display.contains(&status.to_string()),
1769 "Status code {} not found in display output: {}",
1770 status, display
1771 );
1772 }
1773
1774 #[test]
1781 fn prop_http_status_code_preservation_through_error(status in 100u16..600u16, message in arb_error_message()) {
1782 let network_error = NetworkError::RequestFailed {
1784 status,
1785 message: message.clone(),
1786 };
1787 let error: Error = network_error.into();
1788
1789 if let Error::Network(boxed_network_error) = &error {
1791 if let NetworkError::RequestFailed { status: preserved_status, .. } = boxed_network_error.as_ref() {
1792 prop_assert_eq!(
1793 *preserved_status, status,
1794 "HTTP status code {} was not preserved through Error wrapper, got {}",
1795 status, preserved_status
1796 );
1797 } else {
1798 prop_assert!(false, "Expected RequestFailed variant inside Network");
1799 }
1800 } else {
1801 prop_assert!(false, "Expected Network variant");
1802 }
1803
1804 let display = error.to_string();
1806 prop_assert!(
1807 display.contains(&status.to_string()),
1808 "Status code {} not found in Error display output: {}",
1809 status, display
1810 );
1811 }
1812
1813 #[test]
1821 fn prop_http_status_code_preservation_through_context(
1822 status in 100u16..600u16,
1823 message in arb_error_message(),
1824 context in "[a-zA-Z0-9 ]{1,50}"
1825 ) {
1826 let network_error = NetworkError::RequestFailed {
1828 status,
1829 message: message.clone(),
1830 };
1831 let error: Error = network_error.into();
1832 let error_with_context = error.context(context);
1833
1834 let root = error_with_context.root_cause();
1836 if let Error::Network(boxed_network_error) = root {
1837 if let NetworkError::RequestFailed { status: preserved_status, .. } = boxed_network_error.as_ref() {
1838 prop_assert_eq!(
1839 *preserved_status, status,
1840 "HTTP status code {} was not preserved through context, got {}",
1841 status, preserved_status
1842 );
1843 } else {
1844 prop_assert!(false, "Expected RequestFailed variant in root_cause");
1845 }
1846 } else {
1847 prop_assert!(false, "Expected Network variant in root_cause");
1848 }
1849
1850 let report = error_with_context.report();
1852 prop_assert!(
1853 report.contains(&status.to_string()),
1854 "Status code {} not found in error report: {}",
1855 status, report
1856 );
1857 }
1858 }
1859
1860 proptest! {
1863 #![proptest_config(ProptestConfig::with_cases(100))]
1864
1865 #[test]
1875 fn prop_error_context_chain_preservation_single_context(
1876 base_error in arb_error(),
1877 context_str in "[a-zA-Z0-9 .,!?-]{1,100}"
1878 ) {
1879 let base_error_string = base_error.to_string();
1881
1882 let wrapped = base_error.context(context_str.clone());
1884
1885 let source = wrapped.source();
1887 prop_assert!(
1888 source.is_some(),
1889 "source() should return Some for Context variant"
1890 );
1891 let source_string = source.unwrap().to_string();
1892 prop_assert!(
1893 source_string.contains(&base_error_string) || base_error_string.contains(&source_string) || source_string == base_error_string,
1894 "source() should return the base error. Expected to contain '{}', got '{}'",
1895 base_error_string, source_string
1896 );
1897
1898 let root = wrapped.root_cause();
1900 let root_string = root.to_string();
1901 prop_assert!(
1902 root_string == base_error_string || root_string.contains(&base_error_string) || base_error_string.contains(&root_string),
1903 "root_cause() should return the original base error. Expected '{}', got '{}'",
1904 base_error_string, root_string
1905 );
1906
1907 let report = wrapped.report();
1909 prop_assert!(
1910 report.contains(&context_str),
1911 "report() should contain context string '{}'. Got: {}",
1912 context_str, report
1913 );
1914
1915 prop_assert!(
1917 report.contains(&base_error_string),
1918 "report() should contain base error message '{}'. Got: {}",
1919 base_error_string, report
1920 );
1921
1922 let wrapped_display = wrapped.to_string();
1924 prop_assert!(
1925 wrapped_display.contains(&context_str),
1926 "Wrapped error Display should contain context '{}'. Got: {}",
1927 context_str, wrapped_display
1928 );
1929 }
1930
1931 #[test]
1938 fn prop_error_context_chain_preservation_multiple_contexts(
1939 base_error in arb_error(),
1940 context1 in "[a-zA-Z0-9]{5,20}",
1941 context2 in "[a-zA-Z0-9]{5,20}",
1942 context3 in "[a-zA-Z0-9]{5,20}"
1943 ) {
1944 let base_error_string = base_error.to_string();
1946
1947 let wrapped = base_error
1949 .context(context1.clone())
1950 .context(context2.clone())
1951 .context(context3.clone());
1952
1953 let source1 = wrapped.source();
1956 prop_assert!(source1.is_some(), "First source() should return Some");
1957
1958 let source2 = source1.unwrap().source();
1959 prop_assert!(source2.is_some(), "Second source() should return Some");
1960
1961 let source3 = source2.unwrap().source();
1962 prop_assert!(source3.is_some(), "Third source() should return Some");
1963
1964 let root = wrapped.root_cause();
1966 let root_string = root.to_string();
1967 prop_assert!(
1968 root_string == base_error_string || root_string.contains(&base_error_string) || base_error_string.contains(&root_string),
1969 "root_cause() should return the original base error. Expected '{}', got '{}'",
1970 base_error_string, root_string
1971 );
1972
1973 let report = wrapped.report();
1975 prop_assert!(
1976 report.contains(&context1),
1977 "report() should contain context1 '{}'. Got: {}",
1978 context1, report
1979 );
1980 prop_assert!(
1981 report.contains(&context2),
1982 "report() should contain context2 '{}'. Got: {}",
1983 context2, report
1984 );
1985 prop_assert!(
1986 report.contains(&context3),
1987 "report() should contain context3 '{}'. Got: {}",
1988 context3, report
1989 );
1990
1991 prop_assert!(
1993 report.contains(&base_error_string),
1994 "report() should contain base error message '{}'. Got: {}",
1995 base_error_string, report
1996 );
1997
1998 let wrapped_display = wrapped.to_string();
2000 prop_assert!(
2001 wrapped_display.contains(&context3),
2002 "Wrapped error Display should contain outermost context '{}'. Got: {}",
2003 context3, wrapped_display
2004 );
2005 }
2006
2007 #[test]
2014 fn prop_error_context_chain_depth(
2015 base_error in arb_error(),
2016 contexts in proptest::collection::vec("[a-zA-Z0-9]{3,15}", 1..=10)
2017 ) {
2018 let base_error_string = base_error.to_string();
2020 let num_contexts = contexts.len();
2021
2022 let mut wrapped = base_error;
2024 for ctx in &contexts {
2025 wrapped = wrapped.context(ctx.clone());
2026 }
2027
2028 let mut depth = 0;
2030 let mut current: Option<&(dyn StdError + 'static)> = wrapped.source();
2031 while let Some(err) = current {
2032 depth += 1;
2033 current = err.source();
2034 }
2035 prop_assert!(
2037 depth >= num_contexts,
2038 "Chain depth {} should be at least {} (number of contexts)",
2039 depth, num_contexts
2040 );
2041
2042 let root = wrapped.root_cause();
2044 let root_string = root.to_string();
2045 prop_assert!(
2046 root_string == base_error_string || root_string.contains(&base_error_string) || base_error_string.contains(&root_string),
2047 "root_cause() should return the original base error. Expected '{}', got '{}'",
2048 base_error_string, root_string
2049 );
2050
2051 let report = wrapped.report();
2053 for ctx in &contexts {
2054 prop_assert!(
2055 report.contains(ctx),
2056 "report() should contain context '{}'. Got: {}",
2057 ctx, report
2058 );
2059 }
2060
2061 prop_assert!(
2063 report.contains(&base_error_string),
2064 "report() should contain base error message '{}'. Got: {}",
2065 base_error_string, report
2066 );
2067 }
2068
2069 #[test]
2076 fn prop_error_context_preserves_source_error_variant(
2077 context_str in "[a-zA-Z0-9 ]{1,50}"
2078 ) {
2079 let rate_limit_err = Error::rate_limit("test rate limit", Some(Duration::from_secs(30)));
2083 let wrapped_rate_limit = rate_limit_err.context(context_str.clone());
2084
2085 prop_assert!(
2087 wrapped_rate_limit.as_rate_limit().is_some(),
2088 "as_rate_limit() should work through context"
2089 );
2090 prop_assert!(
2091 wrapped_rate_limit.is_retryable(),
2092 "is_retryable() should work through context for RateLimit"
2093 );
2094 prop_assert_eq!(
2095 wrapped_rate_limit.retry_after(),
2096 Some(Duration::from_secs(30)),
2097 "retry_after() should work through context"
2098 );
2099
2100 let auth_err = Error::authentication("test auth error");
2102 let wrapped_auth = auth_err.context(context_str.clone());
2103
2104 prop_assert!(
2105 wrapped_auth.as_authentication().is_some(),
2106 "as_authentication() should work through context"
2107 );
2108
2109 let timeout_err = Error::timeout("test timeout");
2111 let wrapped_timeout = timeout_err.context(context_str.clone());
2112
2113 prop_assert!(
2114 wrapped_timeout.is_retryable(),
2115 "is_retryable() should work through context for Timeout"
2116 );
2117
2118 let network_err = Error::from(NetworkError::Timeout);
2120 let wrapped_network = network_err.context(context_str);
2121
2122 prop_assert!(
2123 wrapped_network.is_retryable(),
2124 "is_retryable() should work through context for NetworkError::Timeout"
2125 );
2126 }
2127 }
2128
2129 #[test]
2143 fn static_assert_error_size_constraint() {
2144 const ERROR_SIZE: usize = std::mem::size_of::<Error>();
2146 const MAX_ALLOWED_SIZE: usize = 56;
2147
2148 const _: () = assert!(
2150 ERROR_SIZE <= MAX_ALLOWED_SIZE,
2151 );
2153
2154 assert!(
2156 ERROR_SIZE <= MAX_ALLOWED_SIZE,
2157 "Error enum size {} bytes exceeds maximum allowed {} bytes. \
2158 Consider boxing large variants to reduce enum size.",
2159 ERROR_SIZE,
2160 MAX_ALLOWED_SIZE
2161 );
2162
2163 let network_error_size = std::mem::size_of::<NetworkError>();
2165 let parse_error_size = std::mem::size_of::<ParseError>();
2166 let order_error_size = std::mem::size_of::<OrderError>();
2167
2168 assert!(
2171 network_error_size <= 80,
2172 "NetworkError size {} bytes is unexpectedly large",
2173 network_error_size
2174 );
2175 assert!(
2176 parse_error_size <= 80,
2177 "ParseError size {} bytes is unexpectedly large",
2178 parse_error_size
2179 );
2180 assert!(
2181 order_error_size <= 48,
2182 "OrderError size {} bytes is unexpectedly large",
2183 order_error_size
2184 );
2185 }
2186
2187 proptest! {
2188 #![proptest_config(ProptestConfig::with_cases(100))]
2189
2190 #[test]
2198 fn prop_error_size_constraint_with_arbitrary_data(error in arb_error()) {
2199 const MAX_ALLOWED_SIZE: usize = 56;
2203 let error_size = std::mem::size_of_val(&error);
2204
2205 prop_assert!(
2208 error_size <= MAX_ALLOWED_SIZE,
2209 "Error size {} bytes exceeds {} bytes for variant: {:?}",
2210 error_size,
2211 MAX_ALLOWED_SIZE,
2212 std::mem::discriminant(&error)
2213 );
2214
2215 prop_assert_eq!(
2217 error_size,
2218 std::mem::size_of::<Error>(),
2219 "size_of_val should equal size_of::<Error>()"
2220 );
2221 }
2222
2223 #[test]
2225 fn prop_error_size_with_context_layers(
2226 base_error in arb_error(),
2227 context1 in "[a-zA-Z0-9 ]{1,50}",
2228 context2 in "[a-zA-Z0-9 ]{1,50}",
2229 context3 in "[a-zA-Z0-9 ]{1,50}"
2230 ) {
2231 const MAX_ALLOWED_SIZE: usize = 56;
2232
2233 let wrapped = base_error
2235 .context(context1)
2236 .context(context2)
2237 .context(context3);
2238
2239 let wrapped_size = std::mem::size_of_val(&wrapped);
2240
2241 prop_assert!(
2244 wrapped_size <= MAX_ALLOWED_SIZE,
2245 "Error with context size {} bytes exceeds {} bytes",
2246 wrapped_size,
2247 MAX_ALLOWED_SIZE
2248 );
2249
2250 prop_assert_eq!(
2251 wrapped_size,
2252 std::mem::size_of::<Error>(),
2253 "Wrapped error size should equal base Error size"
2254 );
2255 }
2256
2257 #[test]
2259 fn prop_network_error_size_constraint(error in arb_network_error()) {
2260 const MAX_ALLOWED_SIZE: usize = 80;
2261 let error_size = std::mem::size_of_val(&error);
2262
2263 prop_assert!(
2264 error_size <= MAX_ALLOWED_SIZE,
2265 "NetworkError size {} bytes exceeds {} bytes",
2266 error_size,
2267 MAX_ALLOWED_SIZE
2268 );
2269 }
2270
2271 #[test]
2273 fn prop_parse_error_size_constraint(error in arb_parse_error()) {
2274 const MAX_ALLOWED_SIZE: usize = 80;
2275 let error_size = std::mem::size_of_val(&error);
2276
2277 prop_assert!(
2278 error_size <= MAX_ALLOWED_SIZE,
2279 "ParseError size {} bytes exceeds {} bytes",
2280 error_size,
2281 MAX_ALLOWED_SIZE
2282 );
2283 }
2284
2285 #[test]
2287 fn prop_order_error_size_constraint(error in arb_order_error()) {
2288 const MAX_ALLOWED_SIZE: usize = 48;
2289 let error_size = std::mem::size_of_val(&error);
2290
2291 prop_assert!(
2292 error_size <= MAX_ALLOWED_SIZE,
2293 "OrderError size {} bytes exceeds {} bytes",
2294 error_size,
2295 MAX_ALLOWED_SIZE
2296 );
2297 }
2298 }
2299
2300 proptest! {
2303 #![proptest_config(ProptestConfig::with_cases(100))]
2304
2305 #[test]
2312 fn prop_error_display_non_empty(error in arb_error()) {
2313 let display = error.to_string();
2314 prop_assert!(
2315 !display.is_empty(),
2316 "Error::to_string() returned empty string for error: {:?}",
2317 error
2318 );
2319 prop_assert!(
2320 display.trim().len() > 0,
2321 "Error::to_string() returned whitespace-only string for error: {:?}",
2322 error
2323 );
2324 }
2325
2326 #[test]
2332 fn prop_network_error_display_non_empty(error in arb_network_error()) {
2333 let display = error.to_string();
2334 prop_assert!(
2335 !display.is_empty(),
2336 "NetworkError::to_string() returned empty string for error: {:?}",
2337 error
2338 );
2339 }
2340
2341 #[test]
2347 fn prop_parse_error_display_non_empty(error in arb_parse_error()) {
2348 let display = error.to_string();
2349 prop_assert!(
2350 !display.is_empty(),
2351 "ParseError::to_string() returned empty string for error: {:?}",
2352 error
2353 );
2354 }
2355
2356 #[test]
2362 fn prop_order_error_display_non_empty(error in arb_order_error()) {
2363 let display = error.to_string();
2364 prop_assert!(
2365 !display.is_empty(),
2366 "OrderError::to_string() returned empty string for error: {:?}",
2367 error
2368 );
2369 }
2370
2371 #[test]
2377 fn prop_error_with_context_display_non_empty(
2378 base_error in arb_error(),
2379 context in "[a-zA-Z0-9 ]{1,50}"
2380 ) {
2381 let wrapped = base_error.context(context);
2382 let display = wrapped.to_string();
2383 prop_assert!(
2384 !display.is_empty(),
2385 "Error with context::to_string() returned empty string for error: {:?}",
2386 wrapped
2387 );
2388 }
2389 }
2390
2391 proptest! {
2394 #![proptest_config(ProptestConfig::with_cases(100))]
2395
2396 #[test]
2404 fn prop_retryable_error_classification_rate_limit(
2405 message in arb_error_message(),
2406 retry_after in arb_optional_duration()
2407 ) {
2408 let error = Error::rate_limit(message, retry_after);
2409 prop_assert!(
2410 error.is_retryable(),
2411 "RateLimit error should be retryable"
2412 );
2413 }
2414
2415 #[test]
2421 fn prop_retryable_error_classification_timeout(message in arb_error_message()) {
2422 let error = Error::timeout(message);
2423 prop_assert!(
2424 error.is_retryable(),
2425 "Timeout error should be retryable"
2426 );
2427 }
2428
2429 #[test]
2435 fn prop_retryable_error_classification_network_timeout(_dummy in Just(())) {
2436 let error: Error = NetworkError::Timeout.into();
2437 prop_assert!(
2438 error.is_retryable(),
2439 "NetworkError::Timeout should be retryable"
2440 );
2441 }
2442
2443 #[test]
2449 fn prop_retryable_error_classification_connection_failed(message in arb_error_message()) {
2450 let error: Error = NetworkError::ConnectionFailed(message).into();
2451 prop_assert!(
2452 error.is_retryable(),
2453 "NetworkError::ConnectionFailed should be retryable"
2454 );
2455 }
2456
2457 #[test]
2463 fn prop_non_retryable_error_classification_authentication(message in arb_error_message()) {
2464 let error = Error::authentication(message);
2465 prop_assert!(
2466 !error.is_retryable(),
2467 "Authentication error should NOT be retryable"
2468 );
2469 }
2470
2471 #[test]
2477 fn prop_non_retryable_error_classification_invalid_request(message in arb_error_message()) {
2478 let error = Error::invalid_request(message);
2479 prop_assert!(
2480 !error.is_retryable(),
2481 "InvalidRequest error should NOT be retryable"
2482 );
2483 }
2484
2485 #[test]
2491 fn prop_non_retryable_error_classification_market_not_found(message in arb_error_message()) {
2492 let error = Error::market_not_found(message);
2493 prop_assert!(
2494 !error.is_retryable(),
2495 "MarketNotFound error should NOT be retryable"
2496 );
2497 }
2498
2499 #[test]
2505 fn prop_non_retryable_error_classification_exchange(
2506 code in arb_error_code(),
2507 message in arb_error_message()
2508 ) {
2509 let error = Error::exchange(code, message);
2510 prop_assert!(
2511 !error.is_retryable(),
2512 "Exchange error should NOT be retryable"
2513 );
2514 }
2515
2516 #[test]
2522 fn prop_retryable_error_through_context(
2523 message in arb_error_message(),
2524 retry_after in arb_optional_duration(),
2525 context1 in "[a-zA-Z0-9 ]{1,30}",
2526 context2 in "[a-zA-Z0-9 ]{1,30}"
2527 ) {
2528 let error = Error::rate_limit(message, retry_after)
2529 .context(context1)
2530 .context(context2);
2531 prop_assert!(
2532 error.is_retryable(),
2533 "RateLimit error wrapped in context should still be retryable"
2534 );
2535 }
2536
2537 #[test]
2543 fn prop_non_retryable_error_through_context(
2544 message in arb_error_message(),
2545 context1 in "[a-zA-Z0-9 ]{1,30}",
2546 context2 in "[a-zA-Z0-9 ]{1,30}"
2547 ) {
2548 let error = Error::authentication(message)
2549 .context(context1)
2550 .context(context2);
2551 prop_assert!(
2552 !error.is_retryable(),
2553 "Authentication error wrapped in context should still NOT be retryable"
2554 );
2555 }
2556
2557 #[test]
2563 fn prop_non_retryable_error_classification_request_failed(
2564 status in 100u16..600u16,
2565 message in arb_error_message()
2566 ) {
2567 let error: Error = NetworkError::RequestFailed { status, message }.into();
2568 prop_assert!(
2569 !error.is_retryable(),
2570 "NetworkError::RequestFailed should NOT be retryable"
2571 );
2572 }
2573
2574 #[test]
2580 fn prop_non_retryable_error_classification_dns_resolution(message in arb_error_message()) {
2581 let error: Error = NetworkError::DnsResolution(message).into();
2582 prop_assert!(
2583 !error.is_retryable(),
2584 "NetworkError::DnsResolution should NOT be retryable"
2585 );
2586 }
2587
2588 #[test]
2594 fn prop_non_retryable_error_classification_ssl(message in arb_error_message()) {
2595 let error: Error = NetworkError::Ssl(message).into();
2596 prop_assert!(
2597 !error.is_retryable(),
2598 "NetworkError::Ssl should NOT be retryable"
2599 );
2600 }
2601 }
2602
2603 #[test]
2608 fn test_from_network_error_preserves_info() {
2609 let network_err = NetworkError::Timeout;
2611 let error: Error = network_err.into();
2612 assert!(matches!(error, Error::Network(_)));
2613 assert!(error.to_string().contains("timeout") || error.to_string().contains("Timeout"));
2614
2615 let network_err = NetworkError::ConnectionFailed("Connection refused".to_string());
2617 let error: Error = network_err.into();
2618 assert!(matches!(error, Error::Network(_)));
2619 assert!(error.to_string().contains("Connection refused"));
2620
2621 let network_err = NetworkError::RequestFailed {
2623 status: 404,
2624 message: "Not Found".to_string(),
2625 };
2626 let error: Error = network_err.into();
2627 assert!(matches!(error, Error::Network(_)));
2628 assert!(error.to_string().contains("404"));
2629 assert!(error.to_string().contains("Not Found"));
2630
2631 let network_err = NetworkError::DnsResolution("DNS lookup failed".to_string());
2633 let error: Error = network_err.into();
2634 assert!(matches!(error, Error::Network(_)));
2635 assert!(error.to_string().contains("DNS"));
2636
2637 let network_err = NetworkError::Ssl("Certificate expired".to_string());
2639 let error: Error = network_err.into();
2640 assert!(matches!(error, Error::Network(_)));
2641 assert!(error.to_string().contains("Certificate expired"));
2642 }
2643
2644 #[test]
2645 fn test_from_parse_error_preserves_info() {
2646 let parse_err = ParseError::missing_field("price");
2648 let error: Error = parse_err.into();
2649 assert!(matches!(error, Error::Parse(_)));
2650 assert!(error.to_string().contains("price"));
2651
2652 let parse_err = ParseError::invalid_value("amount", "must be positive");
2654 let error: Error = parse_err.into();
2655 assert!(matches!(error, Error::Parse(_)));
2656 assert!(error.to_string().contains("amount"));
2657 assert!(error.to_string().contains("must be positive"));
2658
2659 let parse_err = ParseError::timestamp("invalid timestamp format");
2661 let error: Error = parse_err.into();
2662 assert!(matches!(error, Error::Parse(_)));
2663 assert!(error.to_string().contains("timestamp"));
2664
2665 let parse_err = ParseError::invalid_format("date", "expected ISO 8601");
2667 let error: Error = parse_err.into();
2668 assert!(matches!(error, Error::Parse(_)));
2669 assert!(error.to_string().contains("date"));
2670 assert!(error.to_string().contains("ISO 8601"));
2671 }
2672
2673 #[test]
2674 fn test_from_order_error_preserves_info() {
2675 let order_err = OrderError::CreationFailed("Insufficient margin".to_string());
2677 let error: Error = order_err.into();
2678 assert!(matches!(error, Error::Order(_)));
2679 assert!(error.to_string().contains("Insufficient margin"));
2680
2681 let order_err = OrderError::CancellationFailed("Order already filled".to_string());
2683 let error: Error = order_err.into();
2684 assert!(matches!(error, Error::Order(_)));
2685 assert!(error.to_string().contains("Order already filled"));
2686
2687 let order_err = OrderError::ModificationFailed("Cannot modify filled order".to_string());
2689 let error: Error = order_err.into();
2690 assert!(matches!(error, Error::Order(_)));
2691 assert!(error.to_string().contains("Cannot modify"));
2692
2693 let order_err = OrderError::InvalidParameters("Invalid quantity".to_string());
2695 let error: Error = order_err.into();
2696 assert!(matches!(error, Error::Order(_)));
2697 assert!(error.to_string().contains("Invalid quantity"));
2698 }
2699
2700 #[test]
2701 fn test_from_serde_json_error_preserves_info() {
2702 let json_err = serde_json::from_str::<serde_json::Value>("{ invalid json }").unwrap_err();
2703 let error: Error = json_err.into();
2704 assert!(matches!(error, Error::Parse(_)));
2705 let display = error.to_string();
2707 assert!(
2708 display.contains("JSON") || display.contains("json") || display.contains("parse"),
2709 "Expected JSON-related error message, got: {}",
2710 display
2711 );
2712 }
2713
2714 #[test]
2715 fn test_from_rust_decimal_error_preserves_info() {
2716 use rust_decimal::Decimal;
2717 use std::str::FromStr;
2718
2719 let decimal_err = Decimal::from_str("not_a_number").unwrap_err();
2720 let error: Error = decimal_err.into();
2721 assert!(matches!(error, Error::Parse(_)));
2722 let display = error.to_string();
2724 assert!(
2725 display.contains("decimal") || display.contains("Decimal") || display.contains("parse"),
2726 "Expected decimal-related error message, got: {}",
2727 display
2728 );
2729 }
2730
2731 #[test]
2732 fn test_from_boxed_network_error_preserves_info() {
2733 let network_err = Box::new(NetworkError::Timeout);
2734 let error: Error = network_err.into();
2735 assert!(matches!(error, Error::Network(_)));
2736 assert!(error.to_string().contains("timeout") || error.to_string().contains("Timeout"));
2737 }
2738
2739 #[test]
2740 fn test_from_boxed_parse_error_preserves_info() {
2741 let parse_err = Box::new(ParseError::missing_field("symbol"));
2742 let error: Error = parse_err.into();
2743 assert!(matches!(error, Error::Parse(_)));
2744 assert!(error.to_string().contains("symbol"));
2745 }
2746
2747 #[test]
2748 fn test_from_boxed_order_error_preserves_info() {
2749 let order_err = Box::new(OrderError::CreationFailed("Test failure".to_string()));
2750 let error: Error = order_err.into();
2751 assert!(matches!(error, Error::Order(_)));
2752 assert!(error.to_string().contains("Test failure"));
2753 }
2754
2755 #[tokio::test]
2764 async fn test_from_reqwest_timeout_error() {
2765 let client = reqwest::Client::builder()
2767 .timeout(std::time::Duration::from_nanos(1))
2768 .build()
2769 .expect("Failed to build client");
2770
2771 let result = client.get("https://httpbin.org/delay/10").send().await;
2773
2774 if let Err(reqwest_err) = result {
2775 let network_err: NetworkError = reqwest_err.into();
2777
2778 let is_timeout_or_connection = matches!(
2782 network_err,
2783 NetworkError::Timeout
2784 | NetworkError::ConnectionFailed(_)
2785 | NetworkError::Transport(_)
2786 );
2787 assert!(
2788 is_timeout_or_connection,
2789 "Expected Timeout, ConnectionFailed, or Transport variant, got: {:?}",
2790 network_err
2791 );
2792 }
2793 }
2795
2796 #[tokio::test]
2798 async fn test_from_reqwest_connection_error() {
2799 let client = reqwest::Client::new();
2800
2801 let result = client.get("http://127.0.0.1:1").send().await;
2803
2804 if let Err(reqwest_err) = result {
2805 assert!(
2807 reqwest_err.is_connect(),
2808 "Expected connection error, got: {:?}",
2809 reqwest_err
2810 );
2811
2812 let network_err: NetworkError = reqwest_err.into();
2814
2815 assert!(
2817 matches!(network_err, NetworkError::ConnectionFailed(_)),
2818 "Expected ConnectionFailed variant, got: {:?}",
2819 network_err
2820 );
2821
2822 let error: Error = network_err.into();
2824 assert!(matches!(error, Error::Network(_)));
2825 }
2826 }
2827
2828 #[tokio::test]
2830 async fn test_from_reqwest_error_to_error() {
2831 let client = reqwest::Client::new();
2832
2833 let result = client.get("http://127.0.0.1:1").send().await;
2835
2836 if let Err(reqwest_err) = result {
2837 let error: Error = reqwest_err.into();
2839
2840 assert!(
2842 matches!(error, Error::Network(_)),
2843 "Expected Network variant, got: {:?}",
2844 error
2845 );
2846
2847 let display = error.to_string();
2849 assert!(!display.is_empty(), "Error display should not be empty");
2850 }
2851 }
2852
2853 #[tokio::test]
2855 async fn test_reqwest_error_preserves_info_through_chain() {
2856 let client = reqwest::Client::new();
2857
2858 let result = client.get("http://127.0.0.1:1").send().await;
2860
2861 if let Err(reqwest_err) = result {
2862 let _original_message = reqwest_err.to_string();
2863
2864 let error: Error = reqwest_err.into();
2866
2867 assert!(
2869 error.is_retryable(),
2870 "Connection failed errors should be retryable"
2871 );
2872
2873 let error_with_context = error.context("Failed to fetch data");
2875 assert!(
2876 error_with_context.is_retryable(),
2877 "Retryable status should be preserved through context"
2878 );
2879
2880 let report = error_with_context.report();
2882 assert!(
2883 report.contains("Failed to fetch data"),
2884 "Report should contain context"
2885 );
2886 assert!(
2888 report.contains("Network")
2889 || report.contains("Connection")
2890 || report.contains("connect"),
2891 "Report should contain network error info, got: {}",
2892 report
2893 );
2894 }
2895 }
2896
2897 #[test]
2899 fn test_truncate_message_preserves_short_messages() {
2900 let short = "Short message".to_string();
2901 let result = truncate_message(short.clone());
2902 assert_eq!(result, short);
2903 }
2904
2905 #[test]
2906 fn test_truncate_message_truncates_long_messages() {
2907 let long = "x".repeat(2000);
2908 let result = truncate_message(long);
2909 assert!(result.len() < 2000);
2910 assert!(result.len() <= MAX_ERROR_MESSAGE_LEN + 20); assert!(result.ends_with("... (truncated)"));
2912 }
2913
2914 #[test]
2915 fn test_truncate_message_boundary() {
2916 let exact = "x".repeat(MAX_ERROR_MESSAGE_LEN);
2918 let result = truncate_message(exact.clone());
2919 assert_eq!(result, exact); let over = "x".repeat(MAX_ERROR_MESSAGE_LEN + 1);
2923 let result = truncate_message(over);
2924 assert!(result.ends_with("... (truncated)"));
2925 }
2926
2927 #[test]
2932 fn test_all_network_error_variants_convert_to_error() {
2933 let err: Error = NetworkError::Timeout.into();
2935 assert!(matches!(err, Error::Network(_)));
2936 assert!(err.to_string().to_lowercase().contains("timeout"));
2937
2938 let err: Error = NetworkError::ConnectionFailed("refused".to_string()).into();
2940 assert!(matches!(err, Error::Network(_)));
2941 assert!(err.to_string().contains("refused"));
2942
2943 let err: Error = NetworkError::DnsResolution("lookup failed".to_string()).into();
2945 assert!(matches!(err, Error::Network(_)));
2946 assert!(err.to_string().contains("lookup failed"));
2947
2948 let err: Error = NetworkError::Ssl("cert error".to_string()).into();
2950 assert!(matches!(err, Error::Network(_)));
2951 assert!(err.to_string().contains("cert error"));
2952
2953 let err: Error = NetworkError::RequestFailed {
2955 status: 500,
2956 message: "Internal Server Error".to_string(),
2957 }
2958 .into();
2959 assert!(matches!(err, Error::Network(_)));
2960 assert!(err.to_string().contains("500"));
2961 assert!(err.to_string().contains("Internal Server Error"));
2962
2963 let simple_err = std::io::Error::new(std::io::ErrorKind::Other, "transport error");
2965 let err: Error = NetworkError::Transport(Box::new(simple_err)).into();
2966 assert!(matches!(err, Error::Network(_)));
2967 }
2968
2969 #[test]
2971 fn test_all_parse_error_variants_convert_to_error() {
2972 let err: Error = ParseError::missing_field("price").into();
2974 assert!(matches!(err, Error::Parse(_)));
2975 assert!(err.to_string().contains("price"));
2976
2977 let err: Error = ParseError::missing_field_owned("dynamic_field".to_string()).into();
2979 assert!(matches!(err, Error::Parse(_)));
2980 assert!(err.to_string().contains("dynamic_field"));
2981
2982 let err: Error = ParseError::invalid_value("amount", "negative value").into();
2984 assert!(matches!(err, Error::Parse(_)));
2985 assert!(err.to_string().contains("amount"));
2986 assert!(err.to_string().contains("negative value"));
2987
2988 let err: Error = ParseError::timestamp("invalid format").into();
2990 assert!(matches!(err, Error::Parse(_)));
2991 assert!(err.to_string().contains("timestamp"));
2992
2993 let err: Error = ParseError::timestamp_owned("dynamic timestamp error".to_string()).into();
2995 assert!(matches!(err, Error::Parse(_)));
2996 assert!(err.to_string().contains("dynamic timestamp error"));
2997
2998 let err: Error = ParseError::invalid_format("date", "expected YYYY-MM-DD").into();
3000 assert!(matches!(err, Error::Parse(_)));
3001 assert!(err.to_string().contains("date"));
3002 assert!(err.to_string().contains("YYYY-MM-DD"));
3003
3004 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
3006 let err: Error = ParseError::Json(json_err).into();
3007 assert!(matches!(err, Error::Parse(_)));
3008
3009 use rust_decimal::Decimal;
3011 use std::str::FromStr;
3012 let decimal_err = Decimal::from_str("not_a_number").unwrap_err();
3013 let err: Error = ParseError::Decimal(decimal_err).into();
3014 assert!(matches!(err, Error::Parse(_)));
3015 }
3016
3017 #[test]
3019 fn test_all_order_error_variants_convert_to_error() {
3020 let err: Error = OrderError::CreationFailed("insufficient funds".to_string()).into();
3022 assert!(matches!(err, Error::Order(_)));
3023 assert!(err.to_string().contains("insufficient funds"));
3024
3025 let err: Error = OrderError::CancellationFailed("order not found".to_string()).into();
3027 assert!(matches!(err, Error::Order(_)));
3028 assert!(err.to_string().contains("order not found"));
3029
3030 let err: Error = OrderError::ModificationFailed("order already filled".to_string()).into();
3032 assert!(matches!(err, Error::Order(_)));
3033 assert!(err.to_string().contains("order already filled"));
3034
3035 let err: Error = OrderError::InvalidParameters("invalid quantity".to_string()).into();
3037 assert!(matches!(err, Error::Order(_)));
3038 assert!(err.to_string().contains("invalid quantity"));
3039 }
3040
3041 #[test]
3043 fn test_from_implementations_with_question_mark() {
3044 fn parse_json() -> Result<serde_json::Value> {
3045 let value: serde_json::Value = serde_json::from_str("invalid")?;
3046 Ok(value)
3047 }
3048
3049 let result = parse_json();
3050 assert!(result.is_err());
3051 let err = result.unwrap_err();
3052 assert!(matches!(err, Error::Parse(_)));
3053 }
3054
3055 #[test]
3057 fn test_from_implementations_preserve_source() {
3058 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
3060 let parse_err = ParseError::Json(json_err);
3061 let error: Error = parse_err.into();
3062
3063 if let Error::Parse(boxed_parse) = &error {
3065 if let ParseError::Json(inner) = boxed_parse.as_ref() {
3066 assert!(!inner.to_string().is_empty());
3068 } else {
3069 panic!("Expected ParseError::Json variant");
3070 }
3071 } else {
3072 panic!("Expected Error::Parse variant");
3073 }
3074
3075 use rust_decimal::Decimal;
3077 use std::str::FromStr;
3078 let decimal_err = Decimal::from_str("not_a_number").unwrap_err();
3079 let parse_err = ParseError::Decimal(decimal_err);
3080 let error: Error = parse_err.into();
3081
3082 if let Error::Parse(boxed_parse) = &error {
3083 if let ParseError::Decimal(inner) = boxed_parse.as_ref() {
3084 assert!(!inner.to_string().is_empty());
3085 } else {
3086 panic!("Expected ParseError::Decimal variant");
3087 }
3088 } else {
3089 panic!("Expected Error::Parse variant");
3090 }
3091 }
3092}