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("{context}")]
535 Context {
536 context: String,
538 #[source]
540 source: Box<Error>,
541 },
542}
543
544impl Error {
545 pub fn exchange(code: impl Into<String>, message: impl Into<String>) -> Self {
557 Self::Exchange(Box::new(ExchangeErrorDetails::new(code, message)))
558 }
559
560 pub fn exchange_with_data(
562 code: impl Into<String>,
563 message: impl Into<String>,
564 data: serde_json::Value,
565 ) -> Self {
566 Self::Exchange(Box::new(ExchangeErrorDetails::with_data(
567 code, message, data,
568 )))
569 }
570
571 pub fn rate_limit(
587 message: impl Into<Cow<'static, str>>,
588 retry_after: Option<Duration>,
589 ) -> Self {
590 Self::RateLimit {
591 message: message.into(),
592 retry_after,
593 }
594 }
595
596 pub fn authentication(msg: impl Into<Cow<'static, str>>) -> Self {
599 Self::Authentication(msg.into())
600 }
601
602 pub fn generic(msg: impl Into<Cow<'static, str>>) -> Self {
604 Self::InvalidRequest(msg.into())
605 }
606
607 pub fn network(msg: impl Into<String>) -> Self {
609 Self::Network(Box::new(NetworkError::ConnectionFailed(msg.into())))
610 }
611
612 pub fn market_not_found(symbol: impl Into<Cow<'static, str>>) -> Self {
615 Self::MarketNotFound(symbol.into())
616 }
617
618 pub fn not_implemented(feature: impl Into<Cow<'static, str>>) -> Self {
621 Self::NotImplemented(feature.into())
622 }
623
624 pub fn invalid_request(msg: impl Into<Cow<'static, str>>) -> Self {
626 Self::InvalidRequest(msg.into())
627 }
628
629 pub fn invalid_argument(msg: impl Into<Cow<'static, str>>) -> Self {
631 Self::InvalidRequest(msg.into())
632 }
633
634 pub fn bad_symbol(symbol: impl Into<String>) -> Self {
636 let s = symbol.into();
637 Self::InvalidRequest(Cow::Owned(format!("Bad symbol: {s}")))
638 }
639
640 pub fn insufficient_balance(msg: impl Into<Cow<'static, str>>) -> Self {
642 Self::InsufficientBalance(msg.into())
643 }
644
645 pub fn timeout(msg: impl Into<Cow<'static, str>>) -> Self {
647 Self::Timeout(msg.into())
648 }
649
650 pub fn websocket(msg: impl Into<String>) -> Self {
652 Self::WebSocket(Box::new(SimpleError(msg.into())))
653 }
654
655 pub fn websocket_error<E: StdError + Send + Sync + 'static>(err: E) -> Self {
657 Self::WebSocket(Box::new(err))
658 }
659
660 #[must_use]
673 pub fn context(self, context: impl Into<String>) -> Self {
674 Self::Context {
675 context: context.into(),
676 source: Box::new(self),
677 }
678 }
679
680 fn iter_chain(&self) -> impl Iterator<Item = &Error> {
685 std::iter::successors(Some(self), |err| match err {
686 Error::Context { source, .. } => Some(source.as_ref()),
687 _ => None,
688 })
689 }
690
691 #[must_use]
693 pub fn root_cause(&self) -> &Error {
694 self.iter_chain().last().unwrap_or(self)
695 }
696
697 pub fn find_variant<F>(&self, matcher: F) -> Option<&Error>
700 where
701 F: Fn(&Error) -> bool,
702 {
703 self.iter_chain().find(|e| matcher(e))
704 }
705
706 #[must_use]
721 pub fn report(&self) -> String {
722 use std::fmt::Write;
723 let mut report = String::new();
724 report.push_str(&self.to_string());
725
726 let mut current: Option<&(dyn StdError + 'static)> = self.source();
727 while let Some(err) = current {
728 let _ = write!(report, "\nCaused by: {err}");
729 current = err.source();
730 }
731 report
732 }
733
734 #[must_use]
744 pub fn is_retryable(&self) -> bool {
745 match self {
746 Error::Network(ne) => matches!(
747 ne.as_ref(),
748 NetworkError::Timeout | NetworkError::ConnectionFailed(_)
749 ),
750 Error::RateLimit { .. } | Error::Timeout(_) => true,
751 Error::Context { source, .. } => source.is_retryable(),
752 _ => false,
753 }
754 }
755
756 #[must_use]
758 pub fn retry_after(&self) -> Option<Duration> {
759 match self {
760 Error::RateLimit { retry_after, .. } => *retry_after,
761 Error::Context { source, .. } => source.retry_after(),
762 _ => None,
763 }
764 }
765
766 #[must_use]
769 pub fn as_rate_limit(&self) -> Option<(&str, Option<Duration>)> {
770 match self {
771 Error::RateLimit {
772 message,
773 retry_after,
774 } => Some((message.as_ref(), *retry_after)),
775 Error::Context { source, .. } => source.as_rate_limit(),
776 _ => None,
777 }
778 }
779
780 #[must_use]
783 pub fn as_authentication(&self) -> Option<&str> {
784 match self {
785 Error::Authentication(msg) => Some(msg.as_ref()),
786 Error::Context { source, .. } => source.as_authentication(),
787 _ => None,
788 }
789 }
790
791 #[must_use]
793 pub fn downcast_websocket<T: StdError + 'static>(&self) -> Option<&T> {
794 match self {
795 Error::WebSocket(e) => e.downcast_ref::<T>(),
796 Error::Context { source, .. } => source.downcast_websocket(),
797 _ => None,
798 }
799 }
800}
801
802#[derive(Debug)]
805struct SimpleError(String);
806
807impl fmt::Display for SimpleError {
808 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
809 write!(f, "{}", self.0)
810 }
811}
812
813impl StdError for SimpleError {}
814
815impl From<NetworkError> for Error {
818 fn from(e: NetworkError) -> Self {
819 Error::Network(Box::new(e))
820 }
821}
822
823impl From<Box<NetworkError>> for Error {
824 fn from(e: Box<NetworkError>) -> Self {
825 Error::Network(e)
826 }
827}
828
829impl From<ParseError> for Error {
830 fn from(e: ParseError) -> Self {
831 Error::Parse(Box::new(e))
832 }
833}
834
835impl From<Box<ParseError>> for Error {
836 fn from(e: Box<ParseError>) -> Self {
837 Error::Parse(e)
838 }
839}
840
841impl From<OrderError> for Error {
842 fn from(e: OrderError) -> Self {
843 Error::Order(Box::new(e))
844 }
845}
846
847impl From<Box<OrderError>> for Error {
848 fn from(e: Box<OrderError>) -> Self {
849 Error::Order(e)
850 }
851}
852
853impl From<serde_json::Error> for Error {
854 fn from(e: serde_json::Error) -> Self {
855 Error::Parse(Box::new(ParseError::Json(e)))
856 }
857}
858
859impl From<rust_decimal::Error> for Error {
860 fn from(e: rust_decimal::Error) -> Self {
861 Error::Parse(Box::new(ParseError::Decimal(e)))
862 }
863}
864
865impl From<reqwest::Error> for NetworkError {
867 fn from(e: reqwest::Error) -> Self {
868 if e.is_timeout() {
869 NetworkError::Timeout
870 } else if e.is_connect() {
871 NetworkError::ConnectionFailed(truncate_message(e.to_string()))
872 } else if let Some(status) = e.status() {
873 NetworkError::RequestFailed {
874 status: status.as_u16(),
875 message: truncate_message(e.to_string()),
876 }
877 } else {
878 NetworkError::Transport(Box::new(e))
879 }
880 }
881}
882
883impl From<reqwest::Error> for Error {
884 fn from(e: reqwest::Error) -> Self {
885 Error::Network(Box::new(NetworkError::from(e)))
886 }
887}
888
889pub trait ContextExt<T, E> {
940 fn context<C>(self, context: C) -> Result<T>
942 where
943 C: fmt::Display + Send + Sync + 'static;
944
945 fn with_context<C, F>(self, f: F) -> Result<T>
947 where
948 C: fmt::Display + Send + Sync + 'static,
949 F: FnOnce() -> C;
950}
951
952impl<T, E> ContextExt<T, E> for std::result::Result<T, E>
953where
954 E: Into<Error>,
955{
956 fn context<C>(self, context: C) -> Result<T>
957 where
958 C: fmt::Display + Send + Sync + 'static,
959 {
960 self.map_err(|e| e.into().context(context.to_string()))
961 }
962
963 fn with_context<C, F>(self, f: F) -> Result<T>
964 where
965 C: fmt::Display + Send + Sync + 'static,
966 F: FnOnce() -> C,
967 {
968 self.map_err(|e| e.into().context(f().to_string()))
969 }
970}
971
972impl<T> ContextExt<T, Error> for Option<T> {
973 fn context<C>(self, context: C) -> Result<T>
974 where
975 C: fmt::Display + Send + Sync + 'static,
976 {
977 self.ok_or_else(|| Error::generic(context.to_string()))
978 }
979
980 fn with_context<C, F>(self, f: F) -> Result<T>
981 where
982 C: fmt::Display + Send + Sync + 'static,
983 F: FnOnce() -> C,
984 {
985 self.ok_or_else(|| Error::generic(f().to_string()))
986 }
987}
988
989#[deprecated(since = "0.2.0", note = "Use ContextExt instead")]
993pub trait ErrorContext<T>: Sized {
994 fn context(self, context: impl fmt::Display) -> Result<T>;
996}
997
998#[allow(deprecated)]
999impl<T, E: Into<Error>> ErrorContext<T> for std::result::Result<T, E> {
1000 fn context(self, context: impl fmt::Display) -> Result<T> {
1001 self.map_err(|e| e.into().context(context.to_string()))
1002 }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use super::*;
1008
1009 #[test]
1010 fn test_exchange_error_details_display() {
1011 let details = ExchangeErrorDetails::new("400", "Bad Request");
1012 let display = format!("{}", details);
1013 assert!(display.contains("400"));
1014 assert!(display.contains("Bad Request"));
1015 }
1016
1017 #[test]
1018 fn test_exchange_error_details_with_data() {
1019 let data = serde_json::json!({"error": "test"});
1020 let details = ExchangeErrorDetails::with_data("500", "Internal Error", data.clone());
1021 assert_eq!(details.code, "500");
1022 assert_eq!(details.message, "Internal Error");
1023 assert_eq!(details.data, Some(data));
1024 }
1025
1026 #[test]
1027 fn test_error_exchange_creation() {
1028 let err = Error::exchange("400", "Bad Request");
1029 if let Error::Exchange(details) = &err {
1030 assert_eq!(details.code, "400");
1031 assert_eq!(details.message, "Bad Request");
1032 } else {
1033 panic!("Expected Exchange variant");
1034 }
1035 }
1036
1037 #[test]
1038 fn test_error_exchange_string_code() {
1039 let err = Error::exchange("INVALID_SYMBOL", "Symbol not found");
1041 if let Error::Exchange(details) = &err {
1042 assert_eq!(details.code, "INVALID_SYMBOL");
1043 } else {
1044 panic!("Expected Exchange variant");
1045 }
1046 }
1047
1048 #[test]
1049 fn test_error_authentication() {
1050 let err = Error::authentication("Invalid API key");
1051 assert!(matches!(err, Error::Authentication(_)));
1052 assert!(err.to_string().contains("Invalid API key"));
1053 }
1054
1055 #[test]
1056 fn test_error_rate_limit() {
1057 let err = Error::rate_limit("Too many requests", Some(Duration::from_secs(60)));
1058 if let Error::RateLimit {
1059 message,
1060 retry_after,
1061 } = &err
1062 {
1063 assert_eq!(message.as_ref(), "Too many requests");
1064 assert_eq!(*retry_after, Some(Duration::from_secs(60)));
1065 } else {
1066 panic!("Expected RateLimit variant");
1067 }
1068 }
1069
1070 #[test]
1071 fn test_error_market_not_found() {
1072 let err = Error::market_not_found("BTC/USDT");
1073 assert!(matches!(err, Error::MarketNotFound(_)));
1074 assert!(err.to_string().contains("BTC/USDT"));
1075 }
1076
1077 #[test]
1078 fn test_error_context() {
1079 let base = Error::network("Connection refused");
1080 let with_context = base.context("Failed to fetch ticker");
1081
1082 assert!(matches!(with_context, Error::Context { .. }));
1083 assert!(with_context.to_string().contains("Failed to fetch ticker"));
1084 }
1085
1086 #[test]
1087 fn test_error_context_chain() {
1088 let base = Error::network("Connection refused");
1089 let ctx1 = base.context("Layer 1");
1090 let ctx2 = ctx1.context("Layer 2");
1091
1092 let report = ctx2.report();
1094 assert!(report.contains("Layer 2"));
1095 assert!(report.contains("Layer 1"));
1096 assert!(report.contains("Connection refused"));
1097 }
1098
1099 #[test]
1100 fn test_error_root_cause() {
1101 let base = Error::network("Connection refused");
1102 let ctx1 = base.context("Layer 1");
1103 let ctx2 = ctx1.context("Layer 2");
1104
1105 let root = ctx2.root_cause();
1106 assert!(matches!(root, Error::Network(_)));
1107 }
1108
1109 #[test]
1110 fn test_error_is_retryable() {
1111 assert!(Error::rate_limit("test", None).is_retryable());
1113 assert!(Error::timeout("test").is_retryable());
1114 assert!(Error::from(NetworkError::Timeout).is_retryable());
1115 assert!(Error::from(NetworkError::ConnectionFailed("test".to_string())).is_retryable());
1116
1117 assert!(!Error::authentication("test").is_retryable());
1119 assert!(!Error::invalid_request("test").is_retryable());
1120 assert!(!Error::market_not_found("test").is_retryable());
1121 }
1122
1123 #[test]
1124 fn test_error_is_retryable_through_context() {
1125 let err = Error::rate_limit("test", Some(Duration::from_secs(30)))
1126 .context("Layer 1")
1127 .context("Layer 2");
1128
1129 assert!(err.is_retryable());
1130 assert_eq!(err.retry_after(), Some(Duration::from_secs(30)));
1131 }
1132
1133 #[test]
1134 fn test_error_as_rate_limit() {
1135 let err = Error::rate_limit("Too many requests", Some(Duration::from_secs(60)));
1136 let (msg, retry) = err.as_rate_limit().unwrap();
1137 assert_eq!(msg, "Too many requests");
1138 assert_eq!(retry, Some(Duration::from_secs(60)));
1139 }
1140
1141 #[test]
1142 fn test_error_as_rate_limit_through_context() {
1143 let err = Error::rate_limit("Too many requests", Some(Duration::from_secs(60)))
1144 .context("Wrapped");
1145 let (msg, retry) = err.as_rate_limit().unwrap();
1146 assert_eq!(msg, "Too many requests");
1147 assert_eq!(retry, Some(Duration::from_secs(60)));
1148 }
1149
1150 #[test]
1151 fn test_error_as_authentication() {
1152 let err = Error::authentication("Invalid key");
1153 assert_eq!(err.as_authentication(), Some("Invalid key"));
1154 }
1155
1156 #[test]
1157 fn test_error_as_authentication_through_context() {
1158 let err = Error::authentication("Invalid key").context("Wrapped");
1159 assert_eq!(err.as_authentication(), Some("Invalid key"));
1160 }
1161
1162 #[test]
1163 fn test_network_error_request_failed() {
1164 let err = NetworkError::RequestFailed {
1165 status: 404,
1166 message: "Not Found".to_string(),
1167 };
1168 assert!(err.to_string().contains("404"));
1169 assert!(err.to_string().contains("Not Found"));
1170 }
1171
1172 #[test]
1173 fn test_parse_error_missing_field() {
1174 let err = ParseError::missing_field("price");
1175 assert!(err.to_string().contains("price"));
1176 }
1177
1178 #[test]
1179 fn test_parse_error_invalid_value() {
1180 let err = ParseError::invalid_value("amount", "must be positive");
1181 let display = err.to_string();
1182 assert!(display.contains("amount"));
1183 assert!(display.contains("must be positive"));
1184 }
1185
1186 #[test]
1187 fn test_error_display() {
1188 let err = Error::exchange("400", "Bad Request");
1189 let display = format!("{}", err);
1190 assert!(display.contains("400"));
1191 assert!(display.contains("Bad Request"));
1192 }
1193
1194 #[test]
1195 fn test_context_ext_result() {
1196 let result: std::result::Result<(), Error> = Err(Error::network("test"));
1197 let with_context = ContextExt::context(result, "Operation failed");
1198 assert!(with_context.is_err());
1199 let err = with_context.unwrap_err();
1200 assert!(err.to_string().contains("Operation failed"));
1201 }
1202
1203 #[test]
1204 fn test_context_ext_option() {
1205 let opt: Option<i32> = None;
1206 let result = opt.context("Value not found");
1207 assert!(result.is_err());
1208 let err = result.unwrap_err();
1209 assert!(err.to_string().contains("Value not found"));
1210 }
1211
1212 #[test]
1213 fn test_from_serde_json_error() {
1214 let json_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
1215 let err: Error = json_err.into();
1216 assert!(matches!(err, Error::Parse(_)));
1217 }
1218
1219 #[test]
1220 fn test_from_network_error() {
1221 let network_err = NetworkError::Timeout;
1222 let err: Error = network_err.into();
1223 assert!(matches!(err, Error::Network(_)));
1224 }
1225
1226 #[test]
1227 fn test_from_order_error() {
1228 let order_err = OrderError::CreationFailed("test".to_string());
1229 let err: Error = order_err.into();
1230 assert!(matches!(err, Error::Order(_)));
1231 }
1232
1233 #[test]
1234 fn test_truncate_message() {
1235 let short = "short message".to_string();
1236 assert_eq!(truncate_message(short.clone()), short);
1237
1238 let long = "x".repeat(2000);
1239 let truncated = truncate_message(long);
1240 assert!(truncated.len() < 2000);
1241 assert!(truncated.ends_with("... (truncated)"));
1242 }
1243
1244 #[test]
1246 fn error_is_send_sync_static() {
1247 fn assert_traits<T: Send + Sync + 'static + StdError>() {}
1248 assert_traits::<Error>();
1249 assert_traits::<NetworkError>();
1250 assert_traits::<ParseError>();
1251 assert_traits::<OrderError>();
1252 }
1253
1254 #[test]
1255 fn error_size_is_reasonable() {
1256 let size = std::mem::size_of::<Error>();
1257 assert!(
1259 size <= 56,
1260 "Error enum size {} exceeds 56 bytes, consider boxing large variants",
1261 size
1262 );
1263 }
1264}
1265
1266#[cfg(test)]
1269mod property_tests {
1270 use super::*;
1271 use proptest::prelude::*;
1272 use std::thread;
1273
1274 fn arb_error_code() -> impl Strategy<Value = String> {
1282 prop_oneof![
1283 (100u32..600).prop_map(|n| n.to_string()),
1285 "[A-Z_]{3,20}",
1287 "[A-Za-z0-9_-]{1,30}",
1289 ]
1290 }
1291
1292 fn arb_error_message() -> impl Strategy<Value = String> {
1294 prop_oneof![
1295 Just("".to_string()),
1296 "[a-zA-Z0-9 .,!?-]{1,100}",
1297 "\\PC{1,50}",
1299 ]
1300 }
1301
1302 fn arb_duration() -> impl Strategy<Value = Duration> {
1304 (0u64..=u64::MAX / 2).prop_map(Duration::from_nanos)
1305 }
1306
1307 fn arb_optional_duration() -> impl Strategy<Value = Option<Duration>> {
1309 prop_oneof![Just(None), arb_duration().prop_map(Some),]
1310 }
1311
1312 fn arb_error() -> impl Strategy<Value = Error> {
1314 prop_oneof![
1315 (arb_error_code(), arb_error_message())
1317 .prop_map(|(code, msg)| Error::exchange(code, msg)),
1318 arb_error_message().prop_map(|msg| Error::authentication(msg)),
1320 (arb_error_message(), arb_optional_duration())
1322 .prop_map(|(msg, retry)| Error::rate_limit(msg, retry)),
1323 arb_error_message().prop_map(|msg| Error::invalid_request(msg)),
1325 arb_error_message().prop_map(|msg| Error::market_not_found(msg)),
1327 arb_error_message().prop_map(|msg| Error::timeout(msg)),
1329 arb_error_message().prop_map(|msg| Error::not_implemented(msg)),
1331 arb_error_message().prop_map(|msg| Error::network(msg)),
1333 arb_error_message().prop_map(|msg| Error::websocket(msg)),
1335 arb_error_message().prop_map(|msg| Error::insufficient_balance(msg)),
1337 ]
1338 }
1339
1340 fn arb_network_error() -> impl Strategy<Value = NetworkError> {
1342 prop_oneof![
1343 Just(()).prop_map(|_| NetworkError::Timeout),
1345 arb_error_message().prop_map(NetworkError::ConnectionFailed),
1346 arb_error_message().prop_map(NetworkError::DnsResolution),
1347 arb_error_message().prop_map(NetworkError::Ssl),
1348 (100u16..600, arb_error_message()).prop_map(|(status, msg)| {
1349 NetworkError::RequestFailed {
1350 status,
1351 message: msg,
1352 }
1353 }),
1354 ]
1355 }
1356
1357 fn arb_parse_error() -> impl Strategy<Value = ParseError> {
1359 prop_oneof![
1360 arb_error_message().prop_map(|msg| ParseError::MissingField(Cow::Owned(msg))),
1361 arb_error_message().prop_map(|msg| ParseError::Timestamp(Cow::Owned(msg))),
1362 (arb_error_message(), arb_error_message()).prop_map(|(field, msg)| {
1363 ParseError::InvalidValue {
1364 field: Cow::Owned(field),
1365 message: Cow::Owned(msg),
1366 }
1367 }),
1368 (arb_error_message(), arb_error_message()).prop_map(|(field, msg)| {
1369 ParseError::InvalidFormat {
1370 field: Cow::Owned(field),
1371 message: Cow::Owned(msg),
1372 }
1373 }),
1374 ]
1375 }
1376
1377 fn arb_order_error() -> impl Strategy<Value = OrderError> {
1379 prop_oneof![
1380 arb_error_message().prop_map(OrderError::CreationFailed),
1381 arb_error_message().prop_map(OrderError::CancellationFailed),
1382 arb_error_message().prop_map(OrderError::ModificationFailed),
1383 arb_error_message().prop_map(OrderError::InvalidParameters),
1384 ]
1385 }
1386
1387 proptest! {
1390 #![proptest_config(ProptestConfig::with_cases(100))]
1391
1392 #[test]
1399 fn prop_error_is_send_sync(error in arb_error()) {
1400 fn assert_send_sync_static<T: Send + Sync + 'static>(_: &T) {}
1402 assert_send_sync_static(&error);
1403
1404 let error_string = error.to_string();
1406 let handle = thread::spawn(move || {
1407 error.to_string()
1409 });
1410 let result = handle.join().expect("Thread should not panic");
1411 prop_assert_eq!(result, error_string);
1412 }
1413
1414 #[test]
1421 fn prop_error_thread_safety_with_static_bound(error in arb_error()) {
1422 fn assert_std_error<T: StdError + Send + Sync + 'static>(_: &T) {}
1424 assert_std_error(&error);
1425
1426 let boxed: Box<dyn StdError + Send + Sync + 'static> = Box::new(error);
1428
1429 let handle = thread::spawn(move || {
1431 boxed.to_string()
1432 });
1433 let _ = handle.join().expect("Thread should not panic");
1434 }
1435
1436 #[test]
1438 fn prop_network_error_is_send_sync(error in arb_network_error()) {
1439 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1440 assert_send_sync_static(&error);
1441
1442 let error_string = error.to_string();
1443 let handle = thread::spawn(move || error.to_string());
1444 let result = handle.join().expect("Thread should not panic");
1445 prop_assert_eq!(result, error_string);
1446 }
1447
1448 #[test]
1450 fn prop_parse_error_is_send_sync(error in arb_parse_error()) {
1451 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1452 assert_send_sync_static(&error);
1453
1454 let error_string = error.to_string();
1455 let handle = thread::spawn(move || error.to_string());
1456 let result = handle.join().expect("Thread should not panic");
1457 prop_assert_eq!(result, error_string);
1458 }
1459
1460 #[test]
1462 fn prop_order_error_is_send_sync(error in arb_order_error()) {
1463 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1464 assert_send_sync_static(&error);
1465
1466 let error_string = error.to_string();
1467 let handle = thread::spawn(move || error.to_string());
1468 let result = handle.join().expect("Thread should not panic");
1469 prop_assert_eq!(result, error_string);
1470 }
1471
1472 #[test]
1474 fn prop_error_with_context_is_send_sync(
1475 base_error in arb_error(),
1476 context1 in arb_error_message(),
1477 context2 in arb_error_message()
1478 ) {
1479 let error_with_context = base_error
1480 .context(context1)
1481 .context(context2);
1482
1483 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1484 assert_send_sync_static(&error_with_context);
1485
1486 let error_string = error_with_context.to_string();
1487 let handle = thread::spawn(move || error_with_context.to_string());
1488 let result = handle.join().expect("Thread should not panic");
1489 prop_assert_eq!(result, error_string);
1490 }
1491
1492 #[test]
1494 fn prop_error_from_network_error_is_send_sync(network_error in arb_network_error()) {
1495 let error: Error = network_error.into();
1496
1497 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1498 assert_send_sync_static(&error);
1499
1500 let error_string = error.to_string();
1501 let handle = thread::spawn(move || error.to_string());
1502 let result = handle.join().expect("Thread should not panic");
1503 prop_assert_eq!(result, error_string);
1504 }
1505
1506 #[test]
1508 fn prop_error_from_parse_error_is_send_sync(parse_error in arb_parse_error()) {
1509 let error: Error = parse_error.into();
1510
1511 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1512 assert_send_sync_static(&error);
1513
1514 let error_string = error.to_string();
1515 let handle = thread::spawn(move || error.to_string());
1516 let result = handle.join().expect("Thread should not panic");
1517 prop_assert_eq!(result, error_string);
1518 }
1519
1520 #[test]
1522 fn prop_error_from_order_error_is_send_sync(order_error in arb_order_error()) {
1523 let error: Error = order_error.into();
1524
1525 fn assert_send_sync_static<T: Send + Sync + 'static + StdError>(_: &T) {}
1526 assert_send_sync_static(&error);
1527
1528 let error_string = error.to_string();
1529 let handle = thread::spawn(move || error.to_string());
1530 let result = handle.join().expect("Thread should not panic");
1531 prop_assert_eq!(result, error_string);
1532 }
1533 }
1534
1535 #[test]
1540 fn static_assert_error_traits() {
1541 const fn assert_send<T: Send>() {}
1543 const fn assert_sync<T: Sync>() {}
1544 const fn assert_static<T: 'static>() {}
1545 const fn assert_std_error<T: StdError>() {}
1546
1547 assert_send::<Error>();
1549 assert_sync::<Error>();
1550 assert_static::<Error>();
1551 assert_std_error::<Error>();
1552
1553 assert_send::<NetworkError>();
1555 assert_sync::<NetworkError>();
1556 assert_static::<NetworkError>();
1557 assert_std_error::<NetworkError>();
1558
1559 assert_send::<ParseError>();
1561 assert_sync::<ParseError>();
1562 assert_static::<ParseError>();
1563 assert_std_error::<ParseError>();
1564
1565 assert_send::<OrderError>();
1567 assert_sync::<OrderError>();
1568 assert_static::<OrderError>();
1569 assert_std_error::<OrderError>();
1570
1571 assert_send::<ExchangeErrorDetails>();
1573 assert_sync::<ExchangeErrorDetails>();
1574 assert_static::<ExchangeErrorDetails>();
1575 }
1576
1577 #[test]
1579 fn static_assert_anyhow_compatibility() {
1580 fn can_convert_to_anyhow<E: StdError + Send + Sync + 'static>(_: E) -> anyhow::Error {
1581 anyhow::Error::msg("test")
1582 }
1583
1584 let _ = can_convert_to_anyhow(Error::authentication("test"));
1586 let _ = can_convert_to_anyhow(NetworkError::Timeout);
1587 let _ = can_convert_to_anyhow(ParseError::missing_field("test"));
1588 let _ = can_convert_to_anyhow(OrderError::CreationFailed("test".to_string()));
1589 }
1590
1591 #[test]
1593 fn static_assert_async_compatibility() {
1594 fn can_be_spawned<F: std::future::Future + Send + 'static>(_: F) {}
1595
1596 async fn returns_error() -> std::result::Result<(), Error> {
1598 Err(Error::authentication("test"))
1599 }
1600
1601 can_be_spawned(returns_error());
1602 }
1603
1604 proptest! {
1607 #![proptest_config(ProptestConfig::with_cases(100))]
1608
1609 #[test]
1616 fn prop_http_status_code_preservation(status in 100u16..600u16, message in arb_error_message()) {
1617 let network_error = NetworkError::RequestFailed {
1619 status,
1620 message: message.clone(),
1621 };
1622
1623 if let NetworkError::RequestFailed { status: preserved_status, message: preserved_message } = &network_error {
1625 prop_assert_eq!(
1626 *preserved_status, status,
1627 "HTTP status code {} was not preserved, got {}",
1628 status, preserved_status
1629 );
1630 prop_assert_eq!(
1631 preserved_message, &message,
1632 "Error message was not preserved"
1633 );
1634 } else {
1635 prop_assert!(false, "Expected RequestFailed variant");
1636 }
1637
1638 let display = network_error.to_string();
1640 prop_assert!(
1641 display.contains(&status.to_string()),
1642 "Status code {} not found in display output: {}",
1643 status, display
1644 );
1645 }
1646
1647 #[test]
1654 fn prop_http_status_code_preservation_through_error(status in 100u16..600u16, message in arb_error_message()) {
1655 let network_error = NetworkError::RequestFailed {
1657 status,
1658 message: message.clone(),
1659 };
1660 let error: Error = network_error.into();
1661
1662 if let Error::Network(boxed_network_error) = &error {
1664 if let NetworkError::RequestFailed { status: preserved_status, .. } = boxed_network_error.as_ref() {
1665 prop_assert_eq!(
1666 *preserved_status, status,
1667 "HTTP status code {} was not preserved through Error wrapper, got {}",
1668 status, preserved_status
1669 );
1670 } else {
1671 prop_assert!(false, "Expected RequestFailed variant inside Network");
1672 }
1673 } else {
1674 prop_assert!(false, "Expected Network variant");
1675 }
1676
1677 let display = error.to_string();
1679 prop_assert!(
1680 display.contains(&status.to_string()),
1681 "Status code {} not found in Error display output: {}",
1682 status, display
1683 );
1684 }
1685
1686 #[test]
1694 fn prop_http_status_code_preservation_through_context(
1695 status in 100u16..600u16,
1696 message in arb_error_message(),
1697 context in "[a-zA-Z0-9 ]{1,50}"
1698 ) {
1699 let network_error = NetworkError::RequestFailed {
1701 status,
1702 message: message.clone(),
1703 };
1704 let error: Error = network_error.into();
1705 let error_with_context = error.context(context);
1706
1707 let root = error_with_context.root_cause();
1709 if let Error::Network(boxed_network_error) = root {
1710 if let NetworkError::RequestFailed { status: preserved_status, .. } = boxed_network_error.as_ref() {
1711 prop_assert_eq!(
1712 *preserved_status, status,
1713 "HTTP status code {} was not preserved through context, got {}",
1714 status, preserved_status
1715 );
1716 } else {
1717 prop_assert!(false, "Expected RequestFailed variant in root_cause");
1718 }
1719 } else {
1720 prop_assert!(false, "Expected Network variant in root_cause");
1721 }
1722
1723 let report = error_with_context.report();
1725 prop_assert!(
1726 report.contains(&status.to_string()),
1727 "Status code {} not found in error report: {}",
1728 status, report
1729 );
1730 }
1731 }
1732
1733 proptest! {
1736 #![proptest_config(ProptestConfig::with_cases(100))]
1737
1738 #[test]
1748 fn prop_error_context_chain_preservation_single_context(
1749 base_error in arb_error(),
1750 context_str in "[a-zA-Z0-9 .,!?-]{1,100}"
1751 ) {
1752 let base_error_string = base_error.to_string();
1754
1755 let wrapped = base_error.context(context_str.clone());
1757
1758 let source = wrapped.source();
1760 prop_assert!(
1761 source.is_some(),
1762 "source() should return Some for Context variant"
1763 );
1764 let source_string = source.unwrap().to_string();
1765 prop_assert!(
1766 source_string.contains(&base_error_string) || base_error_string.contains(&source_string) || source_string == base_error_string,
1767 "source() should return the base error. Expected to contain '{}', got '{}'",
1768 base_error_string, source_string
1769 );
1770
1771 let root = wrapped.root_cause();
1773 let root_string = root.to_string();
1774 prop_assert!(
1775 root_string == base_error_string || root_string.contains(&base_error_string) || base_error_string.contains(&root_string),
1776 "root_cause() should return the original base error. Expected '{}', got '{}'",
1777 base_error_string, root_string
1778 );
1779
1780 let report = wrapped.report();
1782 prop_assert!(
1783 report.contains(&context_str),
1784 "report() should contain context string '{}'. Got: {}",
1785 context_str, report
1786 );
1787
1788 prop_assert!(
1790 report.contains(&base_error_string),
1791 "report() should contain base error message '{}'. Got: {}",
1792 base_error_string, report
1793 );
1794
1795 let wrapped_display = wrapped.to_string();
1797 prop_assert!(
1798 wrapped_display.contains(&context_str),
1799 "Wrapped error Display should contain context '{}'. Got: {}",
1800 context_str, wrapped_display
1801 );
1802 }
1803
1804 #[test]
1811 fn prop_error_context_chain_preservation_multiple_contexts(
1812 base_error in arb_error(),
1813 context1 in "[a-zA-Z0-9]{5,20}",
1814 context2 in "[a-zA-Z0-9]{5,20}",
1815 context3 in "[a-zA-Z0-9]{5,20}"
1816 ) {
1817 let base_error_string = base_error.to_string();
1819
1820 let wrapped = base_error
1822 .context(context1.clone())
1823 .context(context2.clone())
1824 .context(context3.clone());
1825
1826 let source1 = wrapped.source();
1829 prop_assert!(source1.is_some(), "First source() should return Some");
1830
1831 let source2 = source1.unwrap().source();
1832 prop_assert!(source2.is_some(), "Second source() should return Some");
1833
1834 let source3 = source2.unwrap().source();
1835 prop_assert!(source3.is_some(), "Third source() should return Some");
1836
1837 let root = wrapped.root_cause();
1839 let root_string = root.to_string();
1840 prop_assert!(
1841 root_string == base_error_string || root_string.contains(&base_error_string) || base_error_string.contains(&root_string),
1842 "root_cause() should return the original base error. Expected '{}', got '{}'",
1843 base_error_string, root_string
1844 );
1845
1846 let report = wrapped.report();
1848 prop_assert!(
1849 report.contains(&context1),
1850 "report() should contain context1 '{}'. Got: {}",
1851 context1, report
1852 );
1853 prop_assert!(
1854 report.contains(&context2),
1855 "report() should contain context2 '{}'. Got: {}",
1856 context2, report
1857 );
1858 prop_assert!(
1859 report.contains(&context3),
1860 "report() should contain context3 '{}'. Got: {}",
1861 context3, report
1862 );
1863
1864 prop_assert!(
1866 report.contains(&base_error_string),
1867 "report() should contain base error message '{}'. Got: {}",
1868 base_error_string, report
1869 );
1870
1871 let wrapped_display = wrapped.to_string();
1873 prop_assert!(
1874 wrapped_display.contains(&context3),
1875 "Wrapped error Display should contain outermost context '{}'. Got: {}",
1876 context3, wrapped_display
1877 );
1878 }
1879
1880 #[test]
1887 fn prop_error_context_chain_depth(
1888 base_error in arb_error(),
1889 contexts in proptest::collection::vec("[a-zA-Z0-9]{3,15}", 1..=10)
1890 ) {
1891 let base_error_string = base_error.to_string();
1893 let num_contexts = contexts.len();
1894
1895 let mut wrapped = base_error;
1897 for ctx in &contexts {
1898 wrapped = wrapped.context(ctx.clone());
1899 }
1900
1901 let mut depth = 0;
1903 let mut current: Option<&(dyn StdError + 'static)> = wrapped.source();
1904 while let Some(err) = current {
1905 depth += 1;
1906 current = err.source();
1907 }
1908 prop_assert!(
1910 depth >= num_contexts,
1911 "Chain depth {} should be at least {} (number of contexts)",
1912 depth, num_contexts
1913 );
1914
1915 let root = wrapped.root_cause();
1917 let root_string = root.to_string();
1918 prop_assert!(
1919 root_string == base_error_string || root_string.contains(&base_error_string) || base_error_string.contains(&root_string),
1920 "root_cause() should return the original base error. Expected '{}', got '{}'",
1921 base_error_string, root_string
1922 );
1923
1924 let report = wrapped.report();
1926 for ctx in &contexts {
1927 prop_assert!(
1928 report.contains(ctx),
1929 "report() should contain context '{}'. Got: {}",
1930 ctx, report
1931 );
1932 }
1933
1934 prop_assert!(
1936 report.contains(&base_error_string),
1937 "report() should contain base error message '{}'. Got: {}",
1938 base_error_string, report
1939 );
1940 }
1941
1942 #[test]
1949 fn prop_error_context_preserves_source_error_variant(
1950 context_str in "[a-zA-Z0-9 ]{1,50}"
1951 ) {
1952 let rate_limit_err = Error::rate_limit("test rate limit", Some(Duration::from_secs(30)));
1956 let wrapped_rate_limit = rate_limit_err.context(context_str.clone());
1957
1958 prop_assert!(
1960 wrapped_rate_limit.as_rate_limit().is_some(),
1961 "as_rate_limit() should work through context"
1962 );
1963 prop_assert!(
1964 wrapped_rate_limit.is_retryable(),
1965 "is_retryable() should work through context for RateLimit"
1966 );
1967 prop_assert_eq!(
1968 wrapped_rate_limit.retry_after(),
1969 Some(Duration::from_secs(30)),
1970 "retry_after() should work through context"
1971 );
1972
1973 let auth_err = Error::authentication("test auth error");
1975 let wrapped_auth = auth_err.context(context_str.clone());
1976
1977 prop_assert!(
1978 wrapped_auth.as_authentication().is_some(),
1979 "as_authentication() should work through context"
1980 );
1981
1982 let timeout_err = Error::timeout("test timeout");
1984 let wrapped_timeout = timeout_err.context(context_str.clone());
1985
1986 prop_assert!(
1987 wrapped_timeout.is_retryable(),
1988 "is_retryable() should work through context for Timeout"
1989 );
1990
1991 let network_err = Error::from(NetworkError::Timeout);
1993 let wrapped_network = network_err.context(context_str);
1994
1995 prop_assert!(
1996 wrapped_network.is_retryable(),
1997 "is_retryable() should work through context for NetworkError::Timeout"
1998 );
1999 }
2000 }
2001
2002 #[test]
2016 fn static_assert_error_size_constraint() {
2017 const ERROR_SIZE: usize = std::mem::size_of::<Error>();
2019 const MAX_ALLOWED_SIZE: usize = 56;
2020
2021 const _: () = assert!(
2023 ERROR_SIZE <= MAX_ALLOWED_SIZE,
2024 );
2026
2027 assert!(
2029 ERROR_SIZE <= MAX_ALLOWED_SIZE,
2030 "Error enum size {} bytes exceeds maximum allowed {} bytes. \
2031 Consider boxing large variants to reduce enum size.",
2032 ERROR_SIZE,
2033 MAX_ALLOWED_SIZE
2034 );
2035
2036 let network_error_size = std::mem::size_of::<NetworkError>();
2038 let parse_error_size = std::mem::size_of::<ParseError>();
2039 let order_error_size = std::mem::size_of::<OrderError>();
2040
2041 assert!(
2044 network_error_size <= 80,
2045 "NetworkError size {} bytes is unexpectedly large",
2046 network_error_size
2047 );
2048 assert!(
2049 parse_error_size <= 80,
2050 "ParseError size {} bytes is unexpectedly large",
2051 parse_error_size
2052 );
2053 assert!(
2054 order_error_size <= 48,
2055 "OrderError size {} bytes is unexpectedly large",
2056 order_error_size
2057 );
2058 }
2059
2060 proptest! {
2061 #![proptest_config(ProptestConfig::with_cases(100))]
2062
2063 #[test]
2071 fn prop_error_size_constraint_with_arbitrary_data(error in arb_error()) {
2072 const MAX_ALLOWED_SIZE: usize = 56;
2076 let error_size = std::mem::size_of_val(&error);
2077
2078 prop_assert!(
2081 error_size <= MAX_ALLOWED_SIZE,
2082 "Error size {} bytes exceeds {} bytes for variant: {:?}",
2083 error_size,
2084 MAX_ALLOWED_SIZE,
2085 std::mem::discriminant(&error)
2086 );
2087
2088 prop_assert_eq!(
2090 error_size,
2091 std::mem::size_of::<Error>(),
2092 "size_of_val should equal size_of::<Error>()"
2093 );
2094 }
2095
2096 #[test]
2098 fn prop_error_size_with_context_layers(
2099 base_error in arb_error(),
2100 context1 in "[a-zA-Z0-9 ]{1,50}",
2101 context2 in "[a-zA-Z0-9 ]{1,50}",
2102 context3 in "[a-zA-Z0-9 ]{1,50}"
2103 ) {
2104 const MAX_ALLOWED_SIZE: usize = 56;
2105
2106 let wrapped = base_error
2108 .context(context1)
2109 .context(context2)
2110 .context(context3);
2111
2112 let wrapped_size = std::mem::size_of_val(&wrapped);
2113
2114 prop_assert!(
2117 wrapped_size <= MAX_ALLOWED_SIZE,
2118 "Error with context size {} bytes exceeds {} bytes",
2119 wrapped_size,
2120 MAX_ALLOWED_SIZE
2121 );
2122
2123 prop_assert_eq!(
2124 wrapped_size,
2125 std::mem::size_of::<Error>(),
2126 "Wrapped error size should equal base Error size"
2127 );
2128 }
2129
2130 #[test]
2132 fn prop_network_error_size_constraint(error in arb_network_error()) {
2133 const MAX_ALLOWED_SIZE: usize = 80;
2134 let error_size = std::mem::size_of_val(&error);
2135
2136 prop_assert!(
2137 error_size <= MAX_ALLOWED_SIZE,
2138 "NetworkError size {} bytes exceeds {} bytes",
2139 error_size,
2140 MAX_ALLOWED_SIZE
2141 );
2142 }
2143
2144 #[test]
2146 fn prop_parse_error_size_constraint(error in arb_parse_error()) {
2147 const MAX_ALLOWED_SIZE: usize = 80;
2148 let error_size = std::mem::size_of_val(&error);
2149
2150 prop_assert!(
2151 error_size <= MAX_ALLOWED_SIZE,
2152 "ParseError size {} bytes exceeds {} bytes",
2153 error_size,
2154 MAX_ALLOWED_SIZE
2155 );
2156 }
2157
2158 #[test]
2160 fn prop_order_error_size_constraint(error in arb_order_error()) {
2161 const MAX_ALLOWED_SIZE: usize = 48;
2162 let error_size = std::mem::size_of_val(&error);
2163
2164 prop_assert!(
2165 error_size <= MAX_ALLOWED_SIZE,
2166 "OrderError size {} bytes exceeds {} bytes",
2167 error_size,
2168 MAX_ALLOWED_SIZE
2169 );
2170 }
2171 }
2172
2173 proptest! {
2176 #![proptest_config(ProptestConfig::with_cases(100))]
2177
2178 #[test]
2185 fn prop_error_display_non_empty(error in arb_error()) {
2186 let display = error.to_string();
2187 prop_assert!(
2188 !display.is_empty(),
2189 "Error::to_string() returned empty string for error: {:?}",
2190 error
2191 );
2192 prop_assert!(
2193 display.trim().len() > 0,
2194 "Error::to_string() returned whitespace-only string for error: {:?}",
2195 error
2196 );
2197 }
2198
2199 #[test]
2205 fn prop_network_error_display_non_empty(error in arb_network_error()) {
2206 let display = error.to_string();
2207 prop_assert!(
2208 !display.is_empty(),
2209 "NetworkError::to_string() returned empty string for error: {:?}",
2210 error
2211 );
2212 }
2213
2214 #[test]
2220 fn prop_parse_error_display_non_empty(error in arb_parse_error()) {
2221 let display = error.to_string();
2222 prop_assert!(
2223 !display.is_empty(),
2224 "ParseError::to_string() returned empty string for error: {:?}",
2225 error
2226 );
2227 }
2228
2229 #[test]
2235 fn prop_order_error_display_non_empty(error in arb_order_error()) {
2236 let display = error.to_string();
2237 prop_assert!(
2238 !display.is_empty(),
2239 "OrderError::to_string() returned empty string for error: {:?}",
2240 error
2241 );
2242 }
2243
2244 #[test]
2250 fn prop_error_with_context_display_non_empty(
2251 base_error in arb_error(),
2252 context in "[a-zA-Z0-9 ]{1,50}"
2253 ) {
2254 let wrapped = base_error.context(context);
2255 let display = wrapped.to_string();
2256 prop_assert!(
2257 !display.is_empty(),
2258 "Error with context::to_string() returned empty string for error: {:?}",
2259 wrapped
2260 );
2261 }
2262 }
2263
2264 proptest! {
2267 #![proptest_config(ProptestConfig::with_cases(100))]
2268
2269 #[test]
2277 fn prop_retryable_error_classification_rate_limit(
2278 message in arb_error_message(),
2279 retry_after in arb_optional_duration()
2280 ) {
2281 let error = Error::rate_limit(message, retry_after);
2282 prop_assert!(
2283 error.is_retryable(),
2284 "RateLimit error should be retryable"
2285 );
2286 }
2287
2288 #[test]
2294 fn prop_retryable_error_classification_timeout(message in arb_error_message()) {
2295 let error = Error::timeout(message);
2296 prop_assert!(
2297 error.is_retryable(),
2298 "Timeout error should be retryable"
2299 );
2300 }
2301
2302 #[test]
2308 fn prop_retryable_error_classification_network_timeout(_dummy in Just(())) {
2309 let error: Error = NetworkError::Timeout.into();
2310 prop_assert!(
2311 error.is_retryable(),
2312 "NetworkError::Timeout should be retryable"
2313 );
2314 }
2315
2316 #[test]
2322 fn prop_retryable_error_classification_connection_failed(message in arb_error_message()) {
2323 let error: Error = NetworkError::ConnectionFailed(message).into();
2324 prop_assert!(
2325 error.is_retryable(),
2326 "NetworkError::ConnectionFailed should be retryable"
2327 );
2328 }
2329
2330 #[test]
2336 fn prop_non_retryable_error_classification_authentication(message in arb_error_message()) {
2337 let error = Error::authentication(message);
2338 prop_assert!(
2339 !error.is_retryable(),
2340 "Authentication error should NOT be retryable"
2341 );
2342 }
2343
2344 #[test]
2350 fn prop_non_retryable_error_classification_invalid_request(message in arb_error_message()) {
2351 let error = Error::invalid_request(message);
2352 prop_assert!(
2353 !error.is_retryable(),
2354 "InvalidRequest error should NOT be retryable"
2355 );
2356 }
2357
2358 #[test]
2364 fn prop_non_retryable_error_classification_market_not_found(message in arb_error_message()) {
2365 let error = Error::market_not_found(message);
2366 prop_assert!(
2367 !error.is_retryable(),
2368 "MarketNotFound error should NOT be retryable"
2369 );
2370 }
2371
2372 #[test]
2378 fn prop_non_retryable_error_classification_exchange(
2379 code in arb_error_code(),
2380 message in arb_error_message()
2381 ) {
2382 let error = Error::exchange(code, message);
2383 prop_assert!(
2384 !error.is_retryable(),
2385 "Exchange error should NOT be retryable"
2386 );
2387 }
2388
2389 #[test]
2395 fn prop_retryable_error_through_context(
2396 message in arb_error_message(),
2397 retry_after in arb_optional_duration(),
2398 context1 in "[a-zA-Z0-9 ]{1,30}",
2399 context2 in "[a-zA-Z0-9 ]{1,30}"
2400 ) {
2401 let error = Error::rate_limit(message, retry_after)
2402 .context(context1)
2403 .context(context2);
2404 prop_assert!(
2405 error.is_retryable(),
2406 "RateLimit error wrapped in context should still be retryable"
2407 );
2408 }
2409
2410 #[test]
2416 fn prop_non_retryable_error_through_context(
2417 message in arb_error_message(),
2418 context1 in "[a-zA-Z0-9 ]{1,30}",
2419 context2 in "[a-zA-Z0-9 ]{1,30}"
2420 ) {
2421 let error = Error::authentication(message)
2422 .context(context1)
2423 .context(context2);
2424 prop_assert!(
2425 !error.is_retryable(),
2426 "Authentication error wrapped in context should still NOT be retryable"
2427 );
2428 }
2429
2430 #[test]
2436 fn prop_non_retryable_error_classification_request_failed(
2437 status in 100u16..600u16,
2438 message in arb_error_message()
2439 ) {
2440 let error: Error = NetworkError::RequestFailed { status, message }.into();
2441 prop_assert!(
2442 !error.is_retryable(),
2443 "NetworkError::RequestFailed should NOT be retryable"
2444 );
2445 }
2446
2447 #[test]
2453 fn prop_non_retryable_error_classification_dns_resolution(message in arb_error_message()) {
2454 let error: Error = NetworkError::DnsResolution(message).into();
2455 prop_assert!(
2456 !error.is_retryable(),
2457 "NetworkError::DnsResolution should NOT be retryable"
2458 );
2459 }
2460
2461 #[test]
2467 fn prop_non_retryable_error_classification_ssl(message in arb_error_message()) {
2468 let error: Error = NetworkError::Ssl(message).into();
2469 prop_assert!(
2470 !error.is_retryable(),
2471 "NetworkError::Ssl should NOT be retryable"
2472 );
2473 }
2474 }
2475
2476 #[test]
2481 fn test_from_network_error_preserves_info() {
2482 let network_err = NetworkError::Timeout;
2484 let error: Error = network_err.into();
2485 assert!(matches!(error, Error::Network(_)));
2486 assert!(error.to_string().contains("timeout") || error.to_string().contains("Timeout"));
2487
2488 let network_err = NetworkError::ConnectionFailed("Connection refused".to_string());
2490 let error: Error = network_err.into();
2491 assert!(matches!(error, Error::Network(_)));
2492 assert!(error.to_string().contains("Connection refused"));
2493
2494 let network_err = NetworkError::RequestFailed {
2496 status: 404,
2497 message: "Not Found".to_string(),
2498 };
2499 let error: Error = network_err.into();
2500 assert!(matches!(error, Error::Network(_)));
2501 assert!(error.to_string().contains("404"));
2502 assert!(error.to_string().contains("Not Found"));
2503
2504 let network_err = NetworkError::DnsResolution("DNS lookup failed".to_string());
2506 let error: Error = network_err.into();
2507 assert!(matches!(error, Error::Network(_)));
2508 assert!(error.to_string().contains("DNS"));
2509
2510 let network_err = NetworkError::Ssl("Certificate expired".to_string());
2512 let error: Error = network_err.into();
2513 assert!(matches!(error, Error::Network(_)));
2514 assert!(error.to_string().contains("Certificate expired"));
2515 }
2516
2517 #[test]
2518 fn test_from_parse_error_preserves_info() {
2519 let parse_err = ParseError::missing_field("price");
2521 let error: Error = parse_err.into();
2522 assert!(matches!(error, Error::Parse(_)));
2523 assert!(error.to_string().contains("price"));
2524
2525 let parse_err = ParseError::invalid_value("amount", "must be positive");
2527 let error: Error = parse_err.into();
2528 assert!(matches!(error, Error::Parse(_)));
2529 assert!(error.to_string().contains("amount"));
2530 assert!(error.to_string().contains("must be positive"));
2531
2532 let parse_err = ParseError::timestamp("invalid timestamp format");
2534 let error: Error = parse_err.into();
2535 assert!(matches!(error, Error::Parse(_)));
2536 assert!(error.to_string().contains("timestamp"));
2537
2538 let parse_err = ParseError::invalid_format("date", "expected ISO 8601");
2540 let error: Error = parse_err.into();
2541 assert!(matches!(error, Error::Parse(_)));
2542 assert!(error.to_string().contains("date"));
2543 assert!(error.to_string().contains("ISO 8601"));
2544 }
2545
2546 #[test]
2547 fn test_from_order_error_preserves_info() {
2548 let order_err = OrderError::CreationFailed("Insufficient margin".to_string());
2550 let error: Error = order_err.into();
2551 assert!(matches!(error, Error::Order(_)));
2552 assert!(error.to_string().contains("Insufficient margin"));
2553
2554 let order_err = OrderError::CancellationFailed("Order already filled".to_string());
2556 let error: Error = order_err.into();
2557 assert!(matches!(error, Error::Order(_)));
2558 assert!(error.to_string().contains("Order already filled"));
2559
2560 let order_err = OrderError::ModificationFailed("Cannot modify filled order".to_string());
2562 let error: Error = order_err.into();
2563 assert!(matches!(error, Error::Order(_)));
2564 assert!(error.to_string().contains("Cannot modify"));
2565
2566 let order_err = OrderError::InvalidParameters("Invalid quantity".to_string());
2568 let error: Error = order_err.into();
2569 assert!(matches!(error, Error::Order(_)));
2570 assert!(error.to_string().contains("Invalid quantity"));
2571 }
2572
2573 #[test]
2574 fn test_from_serde_json_error_preserves_info() {
2575 let json_err = serde_json::from_str::<serde_json::Value>("{ invalid json }").unwrap_err();
2576 let error: Error = json_err.into();
2577 assert!(matches!(error, Error::Parse(_)));
2578 let display = error.to_string();
2580 assert!(
2581 display.contains("JSON") || display.contains("json") || display.contains("parse"),
2582 "Expected JSON-related error message, got: {}",
2583 display
2584 );
2585 }
2586
2587 #[test]
2588 fn test_from_rust_decimal_error_preserves_info() {
2589 use rust_decimal::Decimal;
2590 use std::str::FromStr;
2591
2592 let decimal_err = Decimal::from_str("not_a_number").unwrap_err();
2593 let error: Error = decimal_err.into();
2594 assert!(matches!(error, Error::Parse(_)));
2595 let display = error.to_string();
2597 assert!(
2598 display.contains("decimal") || display.contains("Decimal") || display.contains("parse"),
2599 "Expected decimal-related error message, got: {}",
2600 display
2601 );
2602 }
2603
2604 #[test]
2605 fn test_from_boxed_network_error_preserves_info() {
2606 let network_err = Box::new(NetworkError::Timeout);
2607 let error: Error = network_err.into();
2608 assert!(matches!(error, Error::Network(_)));
2609 assert!(error.to_string().contains("timeout") || error.to_string().contains("Timeout"));
2610 }
2611
2612 #[test]
2613 fn test_from_boxed_parse_error_preserves_info() {
2614 let parse_err = Box::new(ParseError::missing_field("symbol"));
2615 let error: Error = parse_err.into();
2616 assert!(matches!(error, Error::Parse(_)));
2617 assert!(error.to_string().contains("symbol"));
2618 }
2619
2620 #[test]
2621 fn test_from_boxed_order_error_preserves_info() {
2622 let order_err = Box::new(OrderError::CreationFailed("Test failure".to_string()));
2623 let error: Error = order_err.into();
2624 assert!(matches!(error, Error::Order(_)));
2625 assert!(error.to_string().contains("Test failure"));
2626 }
2627
2628 #[tokio::test]
2637 async fn test_from_reqwest_timeout_error() {
2638 let client = reqwest::Client::builder()
2640 .timeout(std::time::Duration::from_nanos(1))
2641 .build()
2642 .expect("Failed to build client");
2643
2644 let result = client.get("https://httpbin.org/delay/10").send().await;
2646
2647 if let Err(reqwest_err) = result {
2648 let network_err: NetworkError = reqwest_err.into();
2650
2651 let is_timeout_or_connection = matches!(
2655 network_err,
2656 NetworkError::Timeout
2657 | NetworkError::ConnectionFailed(_)
2658 | NetworkError::Transport(_)
2659 );
2660 assert!(
2661 is_timeout_or_connection,
2662 "Expected Timeout, ConnectionFailed, or Transport variant, got: {:?}",
2663 network_err
2664 );
2665 }
2666 }
2668
2669 #[tokio::test]
2671 async fn test_from_reqwest_connection_error() {
2672 let client = reqwest::Client::new();
2673
2674 let result = client.get("http://127.0.0.1:1").send().await;
2676
2677 if let Err(reqwest_err) = result {
2678 assert!(
2680 reqwest_err.is_connect(),
2681 "Expected connection error, got: {:?}",
2682 reqwest_err
2683 );
2684
2685 let network_err: NetworkError = reqwest_err.into();
2687
2688 assert!(
2690 matches!(network_err, NetworkError::ConnectionFailed(_)),
2691 "Expected ConnectionFailed variant, got: {:?}",
2692 network_err
2693 );
2694
2695 let error: Error = network_err.into();
2697 assert!(matches!(error, Error::Network(_)));
2698 }
2699 }
2700
2701 #[tokio::test]
2703 async fn test_from_reqwest_error_to_error() {
2704 let client = reqwest::Client::new();
2705
2706 let result = client.get("http://127.0.0.1:1").send().await;
2708
2709 if let Err(reqwest_err) = result {
2710 let error: Error = reqwest_err.into();
2712
2713 assert!(
2715 matches!(error, Error::Network(_)),
2716 "Expected Network variant, got: {:?}",
2717 error
2718 );
2719
2720 let display = error.to_string();
2722 assert!(!display.is_empty(), "Error display should not be empty");
2723 }
2724 }
2725
2726 #[tokio::test]
2728 async fn test_reqwest_error_preserves_info_through_chain() {
2729 let client = reqwest::Client::new();
2730
2731 let result = client.get("http://127.0.0.1:1").send().await;
2733
2734 if let Err(reqwest_err) = result {
2735 let _original_message = reqwest_err.to_string();
2736
2737 let error: Error = reqwest_err.into();
2739
2740 assert!(
2742 error.is_retryable(),
2743 "Connection failed errors should be retryable"
2744 );
2745
2746 let error_with_context = error.context("Failed to fetch data");
2748 assert!(
2749 error_with_context.is_retryable(),
2750 "Retryable status should be preserved through context"
2751 );
2752
2753 let report = error_with_context.report();
2755 assert!(
2756 report.contains("Failed to fetch data"),
2757 "Report should contain context"
2758 );
2759 assert!(
2761 report.contains("Network")
2762 || report.contains("Connection")
2763 || report.contains("connect"),
2764 "Report should contain network error info, got: {}",
2765 report
2766 );
2767 }
2768 }
2769
2770 #[test]
2772 fn test_truncate_message_preserves_short_messages() {
2773 let short = "Short message".to_string();
2774 let result = truncate_message(short.clone());
2775 assert_eq!(result, short);
2776 }
2777
2778 #[test]
2779 fn test_truncate_message_truncates_long_messages() {
2780 let long = "x".repeat(2000);
2781 let result = truncate_message(long);
2782 assert!(result.len() < 2000);
2783 assert!(result.len() <= MAX_ERROR_MESSAGE_LEN + 20); assert!(result.ends_with("... (truncated)"));
2785 }
2786
2787 #[test]
2788 fn test_truncate_message_boundary() {
2789 let exact = "x".repeat(MAX_ERROR_MESSAGE_LEN);
2791 let result = truncate_message(exact.clone());
2792 assert_eq!(result, exact); let over = "x".repeat(MAX_ERROR_MESSAGE_LEN + 1);
2796 let result = truncate_message(over);
2797 assert!(result.ends_with("... (truncated)"));
2798 }
2799
2800 #[test]
2805 fn test_all_network_error_variants_convert_to_error() {
2806 let err: Error = NetworkError::Timeout.into();
2808 assert!(matches!(err, Error::Network(_)));
2809 assert!(err.to_string().to_lowercase().contains("timeout"));
2810
2811 let err: Error = NetworkError::ConnectionFailed("refused".to_string()).into();
2813 assert!(matches!(err, Error::Network(_)));
2814 assert!(err.to_string().contains("refused"));
2815
2816 let err: Error = NetworkError::DnsResolution("lookup failed".to_string()).into();
2818 assert!(matches!(err, Error::Network(_)));
2819 assert!(err.to_string().contains("lookup failed"));
2820
2821 let err: Error = NetworkError::Ssl("cert error".to_string()).into();
2823 assert!(matches!(err, Error::Network(_)));
2824 assert!(err.to_string().contains("cert error"));
2825
2826 let err: Error = NetworkError::RequestFailed {
2828 status: 500,
2829 message: "Internal Server Error".to_string(),
2830 }
2831 .into();
2832 assert!(matches!(err, Error::Network(_)));
2833 assert!(err.to_string().contains("500"));
2834 assert!(err.to_string().contains("Internal Server Error"));
2835
2836 let simple_err = std::io::Error::new(std::io::ErrorKind::Other, "transport error");
2838 let err: Error = NetworkError::Transport(Box::new(simple_err)).into();
2839 assert!(matches!(err, Error::Network(_)));
2840 }
2841
2842 #[test]
2844 fn test_all_parse_error_variants_convert_to_error() {
2845 let err: Error = ParseError::missing_field("price").into();
2847 assert!(matches!(err, Error::Parse(_)));
2848 assert!(err.to_string().contains("price"));
2849
2850 let err: Error = ParseError::missing_field_owned("dynamic_field".to_string()).into();
2852 assert!(matches!(err, Error::Parse(_)));
2853 assert!(err.to_string().contains("dynamic_field"));
2854
2855 let err: Error = ParseError::invalid_value("amount", "negative value").into();
2857 assert!(matches!(err, Error::Parse(_)));
2858 assert!(err.to_string().contains("amount"));
2859 assert!(err.to_string().contains("negative value"));
2860
2861 let err: Error = ParseError::timestamp("invalid format").into();
2863 assert!(matches!(err, Error::Parse(_)));
2864 assert!(err.to_string().contains("timestamp"));
2865
2866 let err: Error = ParseError::timestamp_owned("dynamic timestamp error".to_string()).into();
2868 assert!(matches!(err, Error::Parse(_)));
2869 assert!(err.to_string().contains("dynamic timestamp error"));
2870
2871 let err: Error = ParseError::invalid_format("date", "expected YYYY-MM-DD").into();
2873 assert!(matches!(err, Error::Parse(_)));
2874 assert!(err.to_string().contains("date"));
2875 assert!(err.to_string().contains("YYYY-MM-DD"));
2876
2877 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
2879 let err: Error = ParseError::Json(json_err).into();
2880 assert!(matches!(err, Error::Parse(_)));
2881
2882 use rust_decimal::Decimal;
2884 use std::str::FromStr;
2885 let decimal_err = Decimal::from_str("not_a_number").unwrap_err();
2886 let err: Error = ParseError::Decimal(decimal_err).into();
2887 assert!(matches!(err, Error::Parse(_)));
2888 }
2889
2890 #[test]
2892 fn test_all_order_error_variants_convert_to_error() {
2893 let err: Error = OrderError::CreationFailed("insufficient funds".to_string()).into();
2895 assert!(matches!(err, Error::Order(_)));
2896 assert!(err.to_string().contains("insufficient funds"));
2897
2898 let err: Error = OrderError::CancellationFailed("order not found".to_string()).into();
2900 assert!(matches!(err, Error::Order(_)));
2901 assert!(err.to_string().contains("order not found"));
2902
2903 let err: Error = OrderError::ModificationFailed("order already filled".to_string()).into();
2905 assert!(matches!(err, Error::Order(_)));
2906 assert!(err.to_string().contains("order already filled"));
2907
2908 let err: Error = OrderError::InvalidParameters("invalid quantity".to_string()).into();
2910 assert!(matches!(err, Error::Order(_)));
2911 assert!(err.to_string().contains("invalid quantity"));
2912 }
2913
2914 #[test]
2916 fn test_from_implementations_with_question_mark() {
2917 fn parse_json() -> Result<serde_json::Value> {
2918 let value: serde_json::Value = serde_json::from_str("invalid")?;
2919 Ok(value)
2920 }
2921
2922 let result = parse_json();
2923 assert!(result.is_err());
2924 let err = result.unwrap_err();
2925 assert!(matches!(err, Error::Parse(_)));
2926 }
2927
2928 #[test]
2930 fn test_from_implementations_preserve_source() {
2931 let json_err = serde_json::from_str::<serde_json::Value>("invalid").unwrap_err();
2933 let parse_err = ParseError::Json(json_err);
2934 let error: Error = parse_err.into();
2935
2936 if let Error::Parse(boxed_parse) = &error {
2938 if let ParseError::Json(inner) = boxed_parse.as_ref() {
2939 assert!(!inner.to_string().is_empty());
2941 } else {
2942 panic!("Expected ParseError::Json variant");
2943 }
2944 } else {
2945 panic!("Expected Error::Parse variant");
2946 }
2947
2948 use rust_decimal::Decimal;
2950 use std::str::FromStr;
2951 let decimal_err = Decimal::from_str("not_a_number").unwrap_err();
2952 let parse_err = ParseError::Decimal(decimal_err);
2953 let error: Error = parse_err.into();
2954
2955 if let Error::Parse(boxed_parse) = &error {
2956 if let ParseError::Decimal(inner) = boxed_parse.as_ref() {
2957 assert!(!inner.to_string().is_empty());
2958 } else {
2959 panic!("Expected ParseError::Decimal variant");
2960 }
2961 } else {
2962 panic!("Expected Error::Parse variant");
2963 }
2964 }
2965}