1use std::error::Error;
86use std::fmt;
87use std::fmt::Display;
88
89use crate::{Frame, PlayerHandle};
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
102pub struct IndexOutOfBounds {
103 pub name: &'static str,
105 pub index: usize,
107 pub length: usize,
109}
110
111impl Display for IndexOutOfBounds {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 write!(
114 f,
115 "{} index {} out of bounds (length: {})",
116 self.name, self.index, self.length
117 )
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
126pub enum InvalidFrameReason {
127 NullFrame,
129 Negative,
131 MustBeNonNegative,
133 NotInPast {
135 current_frame: Frame,
137 },
138 OutsidePredictionWindow {
140 current_frame: Frame,
142 max_prediction: usize,
144 },
145 WrongSavedFrame {
147 saved_frame: Frame,
149 },
150 NotConfirmed {
152 confirmed_frame: Frame,
154 },
155 NullOrNegative,
157 MissingState,
167 Custom(&'static str),
169}
170
171impl Display for InvalidFrameReason {
172 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
173 match self {
174 Self::NullFrame => write!(f, "cannot load NULL_FRAME"),
175 Self::Negative => write!(f, "frame is negative"),
176 Self::MustBeNonNegative => write!(f, "frame must be non-negative"),
177 Self::NotInPast { current_frame } => {
178 write!(
179 f,
180 "must load frame in the past (current: {})",
181 current_frame
182 )
183 },
184 Self::OutsidePredictionWindow {
185 current_frame,
186 max_prediction,
187 } => {
188 write!(
189 f,
190 "cannot load frame outside of prediction window (current: {}, max_prediction: {})",
191 current_frame, max_prediction
192 )
193 },
194 Self::WrongSavedFrame { saved_frame } => {
195 write!(f, "saved state has wrong frame (found: {})", saved_frame)
196 },
197 Self::NotConfirmed { confirmed_frame } => {
198 write!(
199 f,
200 "frame is not confirmed yet (confirmed_frame: {})",
201 confirmed_frame
202 )
203 },
204 Self::NullOrNegative => write!(f, "frame is NULL or negative"),
205 Self::MissingState => write!(f, "no saved state exists for this frame"),
206 Self::Custom(s) => write!(f, "{}", s),
207 }
208 }
209}
210
211#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
216pub enum RleDecodeReason {
217 BitfieldIndexOutOfBounds,
219 DestinationSliceOutOfBounds,
221 SourceSliceOutOfBounds,
223 TruncatedData {
225 offset: usize,
227 buffer_len: usize,
229 },
230 Unknown,
235}
236
237impl Display for RleDecodeReason {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 match self {
240 Self::BitfieldIndexOutOfBounds => {
241 write!(f, "bitfield index out of bounds")
242 },
243 Self::DestinationSliceOutOfBounds => {
244 write!(f, "destination slice out of bounds")
245 },
246 Self::SourceSliceOutOfBounds => {
247 write!(f, "source slice out of bounds")
248 },
249 Self::TruncatedData { offset, buffer_len } => {
250 write!(
251 f,
252 "truncated data: offset {} exceeds buffer length {}",
253 offset, buffer_len
254 )
255 },
256 Self::Unknown => {
257 write!(f, "unknown RLE decode error")
258 },
259 }
260 }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
268pub enum DeltaDecodeReason {
269 EmptyReference,
271 DataLengthMismatch {
273 data_len: usize,
275 reference_len: usize,
277 },
278 ReferenceIndexOutOfBounds {
280 index: usize,
282 length: usize,
284 },
285 DataIndexOutOfBounds {
287 index: usize,
289 length: usize,
291 },
292 Unknown,
297}
298
299impl Display for DeltaDecodeReason {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match self {
302 Self::EmptyReference => write!(f, "reference bytes is empty"),
303 Self::DataLengthMismatch {
304 data_len,
305 reference_len,
306 } => {
307 write!(
308 f,
309 "data length {} is not a multiple of reference length {}",
310 data_len, reference_len
311 )
312 },
313 Self::ReferenceIndexOutOfBounds { index, length } => {
314 write!(
315 f,
316 "reference bytes index {} out of bounds (length: {})",
317 index, length
318 )
319 },
320 Self::DataIndexOutOfBounds { index, length } => {
321 write!(f, "data index {} out of bounds (length: {})", index, length)
322 },
323 Self::Unknown => {
324 write!(f, "unknown delta decode error")
325 },
326 }
327 }
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
335pub enum InternalErrorKind {
336 IndexOutOfBounds(IndexOutOfBounds),
338 SynchronizedInputsFailed {
340 frame: Frame,
342 },
343 EmptyPlayerInputs,
345 BufferIndexOutOfBounds,
347 DisconnectStatusNotFound {
349 player_handle: PlayerHandle,
351 },
352 EndpointNotFoundForRemote {
354 player_handle: PlayerHandle,
356 },
357 EndpointNotFoundForSpectator {
359 player_handle: PlayerHandle,
361 },
362 ConnectionStatusIndexOutOfBounds {
364 player_handle: PlayerHandle,
366 },
367 RleDecodeError {
369 reason: RleDecodeReason,
371 },
372 DeltaDecodeError {
374 reason: DeltaDecodeReason,
376 },
377 Custom(&'static str),
379}
380
381impl Display for InternalErrorKind {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 match self {
384 Self::IndexOutOfBounds(iob) => write!(f, "{}", iob),
385 Self::SynchronizedInputsFailed { frame } => {
386 write!(f, "failed to get synchronized inputs for frame {}", frame)
387 },
388 Self::EmptyPlayerInputs => write!(f, "player inputs vector is empty"),
389 Self::BufferIndexOutOfBounds => write!(f, "buffer index out of bounds"),
390 Self::DisconnectStatusNotFound { player_handle } => {
391 write!(
392 f,
393 "disconnect status not found for player handle {}",
394 player_handle.as_usize()
395 )
396 },
397 Self::EndpointNotFoundForRemote { player_handle } => {
398 write!(
399 f,
400 "endpoint not found for registered remote player {}",
401 player_handle.as_usize()
402 )
403 },
404 Self::EndpointNotFoundForSpectator { player_handle } => {
405 write!(
406 f,
407 "endpoint not found for registered spectator {}",
408 player_handle.as_usize()
409 )
410 },
411 Self::ConnectionStatusIndexOutOfBounds { player_handle } => {
412 write!(
413 f,
414 "connection status index out of bounds for player handle {}",
415 player_handle.as_usize()
416 )
417 },
418 Self::RleDecodeError { reason } => {
419 write!(f, "RLE decode failed: {}", reason)
420 },
421 Self::DeltaDecodeError { reason } => {
422 write!(f, "delta decode failed: {}", reason)
423 },
424 Self::Custom(s) => write!(f, "{}", s),
425 }
426 }
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
434pub enum InvalidRequestKind {
435 PlayerHandleInUse {
438 handle: PlayerHandle,
440 },
441 NotLocalPlayer {
443 handle: PlayerHandle,
445 },
446 NotRemotePlayerOrSpectator {
448 handle: PlayerHandle,
450 },
451 InvalidLocalPlayerHandle {
453 handle: PlayerHandle,
455 num_players: usize,
457 },
458 InvalidRemotePlayerHandle {
460 handle: PlayerHandle,
462 num_players: usize,
464 },
465 InvalidSpectatorHandle {
467 handle: PlayerHandle,
469 num_players: usize,
471 },
472
473 MissingLocalInput,
476 NoConfirmedInput {
478 frame: Frame,
480 },
481
482 ConfigValueOutOfRange {
485 field: &'static str,
487 min: u64,
489 max: u64,
491 actual: u64,
493 },
494 DurationConfigOutOfRange {
496 field: &'static str,
498 min_ms: u64,
500 max_ms: u64,
502 actual_ms: u64,
504 },
505 FrameDelayTooLarge {
507 delay: usize,
509 max_delay: usize,
511 },
512 InputDelayTooLarge {
514 delay_frames: usize,
516 fps: usize,
518 max_seconds_limit: usize,
520 },
521 QueueLengthTooSmall {
523 length: usize,
525 },
526 EventQueueSizeTooSmall {
528 size: usize,
530 },
531
532 ZeroPlayers,
535 ZeroFps,
537 ZeroBufferSize,
539 NotEnoughPlayers {
541 expected: usize,
543 actual: usize,
545 },
546 CheckDistanceTooLarge {
548 check_dist: usize,
550 max_prediction: usize,
552 },
553 MaxFramesBehindInvalid {
555 value: usize,
557 buffer_size: usize,
559 },
560 CatchupSpeedInvalid {
562 speed: usize,
564 max_frames_behind: usize,
566 },
567
568 DisconnectInvalidHandle {
571 handle: PlayerHandle,
573 },
574 DisconnectLocalPlayer {
576 handle: PlayerHandle,
578 },
579 AlreadyDisconnected {
581 handle: PlayerHandle,
583 },
584
585 WrongProtocolState {
588 current_state: &'static str,
590 expected_state: &'static str,
592 },
593
594 Custom(&'static str),
596}
597
598impl Display for InvalidRequestKind {
599 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
600 match self {
601 Self::PlayerHandleInUse { handle } => {
602 write!(f, "player handle {} is already in use", handle.as_usize())
603 },
604 Self::NotLocalPlayer { handle } => {
605 write!(
606 f,
607 "player handle {} does not refer to a local player",
608 handle.as_usize()
609 )
610 },
611 Self::NotRemotePlayerOrSpectator { handle } => {
612 write!(
613 f,
614 "player handle {} does not refer to a remote player or spectator",
615 handle.as_usize()
616 )
617 },
618 Self::InvalidLocalPlayerHandle {
619 handle,
620 num_players,
621 } => {
622 write!(
623 f,
624 "invalid local player handle {}: num_players is {}",
625 handle.as_usize(),
626 num_players
627 )
628 },
629 Self::InvalidRemotePlayerHandle {
630 handle,
631 num_players,
632 } => {
633 write!(
634 f,
635 "invalid remote player handle {}: num_players is {}",
636 handle.as_usize(),
637 num_players
638 )
639 },
640 Self::InvalidSpectatorHandle {
641 handle,
642 num_players,
643 } => {
644 write!(
645 f,
646 "invalid spectator handle {}: num_players is {}",
647 handle.as_usize(),
648 num_players
649 )
650 },
651 Self::MissingLocalInput => write!(f, "missing local input for one or more players"),
652 Self::NoConfirmedInput { frame } => {
653 write!(f, "no confirmed input available for frame {}", frame)
654 },
655 Self::ConfigValueOutOfRange {
656 field,
657 min,
658 max,
659 actual,
660 } => {
661 write!(
662 f,
663 "configuration value '{}' is out of range: {} not in [{}, {}]",
664 field, actual, min, max
665 )
666 },
667 Self::DurationConfigOutOfRange {
668 field,
669 min_ms,
670 max_ms,
671 actual_ms,
672 } => {
673 write!(
674 f,
675 "duration configuration '{}' is out of range: {}ms not in [{}ms, {}ms]",
676 field, actual_ms, min_ms, max_ms
677 )
678 },
679 Self::FrameDelayTooLarge { delay, max_delay } => {
680 write!(
681 f,
682 "frame delay {} exceeds maximum allowed delay {}",
683 delay, max_delay
684 )
685 },
686 Self::InputDelayTooLarge {
687 delay_frames,
688 fps,
689 max_seconds_limit,
690 } => {
691 let actual_seconds = if *fps > 0 {
693 *delay_frames as f64 / *fps as f64
694 } else {
695 f64::INFINITY
696 };
697 write!(
698 f,
699 "input delay {} frames ({:.2}s) exceeds maximum allowed {}s",
700 delay_frames, actual_seconds, max_seconds_limit
701 )
702 },
703 Self::QueueLengthTooSmall { length } => {
704 write!(
705 f,
706 "input queue length {} is too small (minimum is 2)",
707 length
708 )
709 },
710 Self::EventQueueSizeTooSmall { size } => {
711 write!(f, "event queue size {} is too small (minimum is 10)", size)
712 },
713 Self::ZeroPlayers => write!(f, "number of players must be greater than 0"),
714 Self::ZeroFps => write!(f, "FPS must be greater than 0"),
715 Self::ZeroBufferSize => write!(f, "buffer size must be greater than 0"),
716 Self::NotEnoughPlayers { expected, actual } => {
717 write!(
718 f,
719 "not enough players registered: expected {}, got {}",
720 expected, actual
721 )
722 },
723 Self::CheckDistanceTooLarge {
724 check_dist,
725 max_prediction,
726 } => {
727 write!(
728 f,
729 "check distance {} is too large for prediction window {}",
730 check_dist, max_prediction
731 )
732 },
733 Self::MaxFramesBehindInvalid { value, buffer_size } => {
734 write!(
735 f,
736 "max frames behind {} is invalid for buffer size {}",
737 value, buffer_size
738 )
739 },
740 Self::CatchupSpeedInvalid {
741 speed,
742 max_frames_behind,
743 } => {
744 write!(
745 f,
746 "catchup speed {} is invalid for max frames behind {}",
747 speed, max_frames_behind
748 )
749 },
750 Self::DisconnectInvalidHandle { handle } => {
751 write!(
752 f,
753 "cannot disconnect: player handle {} is invalid",
754 handle.as_usize()
755 )
756 },
757 Self::DisconnectLocalPlayer { handle } => {
758 write!(f, "cannot disconnect local player {}", handle.as_usize())
759 },
760 Self::AlreadyDisconnected { handle } => {
761 write!(f, "player {} is already disconnected", handle.as_usize())
762 },
763 Self::WrongProtocolState {
764 current_state,
765 expected_state,
766 } => {
767 write!(
768 f,
769 "operation called in wrong protocol state: current '{}', expected '{}'",
770 current_state, expected_state
771 )
772 },
773 Self::Custom(s) => write!(f, "{}", s),
774 }
775 }
776}
777
778#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
783pub enum SerializationErrorKind {
784 EndpointCreationFailed,
786 SpectatorEndpointCreationFailed,
788 Custom(&'static str),
790}
791
792impl Display for SerializationErrorKind {
793 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
794 match self {
795 Self::EndpointCreationFailed => {
796 write!(f, "failed to create protocol endpoint for remote players")
797 },
798 Self::SpectatorEndpointCreationFailed => {
799 write!(f, "failed to create protocol endpoint for spectators")
800 },
801 Self::Custom(s) => write!(f, "{}", s),
802 }
803 }
804}
805
806#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
811pub enum SocketErrorKind {
812 BindFailed {
814 port: u16,
816 },
817 BindFailedAfterRetries {
819 port: u16,
821 attempts: u8,
823 },
824 Custom(&'static str),
826}
827
828impl Display for SocketErrorKind {
829 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
830 match self {
831 Self::BindFailed { port } => {
832 write!(f, "failed to bind socket to port {}", port)
833 },
834 Self::BindFailedAfterRetries { port, attempts } => {
835 write!(
836 f,
837 "failed to bind socket to port {} after {} attempts",
838 port, attempts
839 )
840 },
841 Self::Custom(s) => write!(f, "{}", s),
842 }
843 }
844}
845
846#[derive(Debug, Clone, PartialEq, Eq, Hash)]
866pub enum FortressError {
867 PredictionThreshold,
869 InvalidRequest {
874 info: String,
876 },
877 MismatchedChecksum {
881 current_frame: Frame,
883 mismatched_frames: Vec<Frame>,
885 },
886 NotSynchronized,
888 SpectatorTooFarBehind,
890 InvalidFrame {
892 frame: Frame,
894 reason: String,
896 },
897 InvalidFrameStructured {
902 frame: Frame,
904 reason: InvalidFrameReason,
906 },
907 InvalidPlayerHandle {
909 handle: PlayerHandle,
911 max_handle: PlayerHandle,
913 },
914 MissingInput {
916 player_handle: PlayerHandle,
918 frame: Frame,
920 },
921 SerializationError {
926 context: String,
928 },
929 InternalError {
932 context: String,
934 },
935 InternalErrorStructured {
940 kind: InternalErrorKind,
942 },
943 SocketError {
948 context: String,
950 },
951 InvalidRequestStructured {
956 kind: InvalidRequestKind,
958 },
959 SerializationErrorStructured {
964 kind: SerializationErrorKind,
966 },
967 SocketErrorStructured {
972 kind: SocketErrorKind,
974 },
975 FrameArithmeticOverflow {
980 frame: Frame,
982 operand: i32,
984 operation: &'static str,
986 },
987 FrameValueTooLarge {
992 value: usize,
994 },
995}
996
997impl Display for FortressError {
998 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
999 match self {
1000 Self::PredictionThreshold => {
1001 write!(
1002 f,
1003 "Prediction threshold is reached, cannot proceed without catching up."
1004 )
1005 },
1006 Self::InvalidRequest { info } => {
1007 write!(f, "Invalid Request: {}", info)
1008 },
1009 Self::NotSynchronized => {
1010 write!(
1011 f,
1012 "The session is not yet synchronized with all remote sessions."
1013 )
1014 },
1015 Self::MismatchedChecksum {
1016 current_frame,
1017 mismatched_frames,
1018 } => {
1019 write!(
1020 f,
1021 "Detected checksum mismatch during rollback on frame {}, mismatched frames: {:?}",
1022 current_frame, mismatched_frames
1023 )
1024 },
1025 Self::SpectatorTooFarBehind => {
1026 write!(
1027 f,
1028 "The spectator got so far behind the host that catching up is impossible."
1029 )
1030 },
1031 Self::InvalidFrame { frame, reason } => {
1032 write!(f, "Invalid frame {}: {}", frame, reason)
1033 },
1034 Self::InvalidFrameStructured { frame, reason } => {
1035 write!(f, "Invalid frame {}: {}", frame, reason)
1036 },
1037 Self::InvalidPlayerHandle { handle, max_handle } => {
1038 write!(
1039 f,
1040 "Invalid player handle {}: must be less than or equal to {}",
1041 handle, max_handle
1042 )
1043 },
1044 Self::MissingInput {
1045 player_handle,
1046 frame,
1047 } => {
1048 write!(
1049 f,
1050 "Missing input for player {} at frame {}",
1051 player_handle.as_usize(),
1052 frame
1053 )
1054 },
1055 Self::SerializationError { context } => {
1056 write!(f, "Serialization error: {}", context)
1057 },
1058 Self::InternalError { context } => {
1059 write!(f, "Internal error (please report as bug): {}", context)
1060 },
1061 Self::InternalErrorStructured { kind } => {
1062 write!(f, "Internal error (please report as bug): {}", kind)
1063 },
1064 Self::SocketError { context } => {
1065 write!(f, "Socket error: {}", context)
1066 },
1067 Self::InvalidRequestStructured { kind } => {
1068 write!(f, "Invalid Request: {}", kind)
1069 },
1070 Self::SerializationErrorStructured { kind } => {
1071 write!(f, "Serialization error: {}", kind)
1072 },
1073 Self::SocketErrorStructured { kind } => {
1074 write!(f, "Socket error: {}", kind)
1075 },
1076 Self::FrameArithmeticOverflow {
1077 frame,
1078 operand,
1079 operation,
1080 } => {
1081 write!(
1082 f,
1083 "Frame arithmetic overflow: {} {} on frame {}",
1084 operation, operand, frame
1085 )
1086 },
1087 Self::FrameValueTooLarge { value } => {
1088 write!(
1089 f,
1090 "Frame value too large: {} exceeds i32::MAX ({})",
1091 value,
1092 i32::MAX
1093 )
1094 },
1095 }
1096 }
1097}
1098
1099impl Error for FortressError {
1100 fn source(&self) -> Option<&(dyn Error + 'static)> {
1111 None
1114 }
1115}
1116
1117impl From<InvalidRequestKind> for FortressError {
1118 fn from(kind: InvalidRequestKind) -> Self {
1119 Self::InvalidRequestStructured { kind }
1120 }
1121}
1122
1123impl From<SerializationErrorKind> for FortressError {
1124 fn from(kind: SerializationErrorKind) -> Self {
1125 Self::SerializationErrorStructured { kind }
1126 }
1127}
1128
1129impl From<SocketErrorKind> for FortressError {
1130 fn from(kind: SocketErrorKind) -> Self {
1131 Self::SocketErrorStructured { kind }
1132 }
1133}
1134
1135#[cfg(test)]
1136#[allow(
1137 clippy::panic,
1138 clippy::unwrap_used,
1139 clippy::expect_used,
1140 clippy::indexing_slicing
1141)]
1142mod tests {
1143 use super::*;
1144
1145 #[test]
1146 fn test_prediction_threshold_display() {
1147 let err = FortressError::PredictionThreshold;
1148 let display = format!("{}", err);
1149 assert!(display.contains("Prediction threshold"));
1150 assert!(display.contains("cannot proceed"));
1151 }
1152
1153 #[test]
1154 fn test_invalid_request_display() {
1155 let err = FortressError::InvalidRequest {
1156 info: "test error info".to_string(),
1157 };
1158 let display = format!("{}", err);
1159 assert!(display.contains("Invalid Request"));
1160 assert!(display.contains("test error info"));
1161 }
1162
1163 #[test]
1164 fn test_not_synchronized_display() {
1165 let err = FortressError::NotSynchronized;
1166 let display = format!("{}", err);
1167 assert!(display.contains("not yet synchronized"));
1168 }
1169
1170 #[test]
1171 fn test_mismatched_checksum_display() {
1172 let err = FortressError::MismatchedChecksum {
1173 current_frame: Frame::new(100),
1174 mismatched_frames: vec![Frame::new(95), Frame::new(96)],
1175 };
1176 let display = format!("{}", err);
1177 assert!(display.contains("checksum mismatch"));
1178 assert!(display.contains("100"));
1179 }
1180
1181 #[test]
1182 fn test_spectator_too_far_behind_display() {
1183 let err = FortressError::SpectatorTooFarBehind;
1184 let display = format!("{}", err);
1185 assert!(display.contains("spectator"));
1186 assert!(display.contains("behind"));
1187 }
1188
1189 #[test]
1190 fn test_invalid_frame_display() {
1191 let err = FortressError::InvalidFrame {
1192 frame: Frame::new(42),
1193 reason: "frame is in the past".to_string(),
1194 };
1195 let display = format!("{}", err);
1196 assert!(display.contains("Invalid frame"));
1197 assert!(display.contains("42"));
1198 assert!(display.contains("frame is in the past"));
1199 }
1200
1201 #[test]
1202 fn test_invalid_player_handle_display() {
1203 let err = FortressError::InvalidPlayerHandle {
1204 handle: PlayerHandle(5),
1205 max_handle: PlayerHandle(3),
1206 };
1207 let display = format!("{}", err);
1208 assert!(display.contains("Invalid player handle"));
1209 assert!(display.contains('5'));
1210 assert!(display.contains('3'));
1211 }
1212
1213 #[test]
1214 fn test_missing_input_display() {
1215 let err = FortressError::MissingInput {
1216 player_handle: PlayerHandle(1),
1217 frame: Frame::new(50),
1218 };
1219 let display = format!("{}", err);
1220 assert!(display.contains("Missing input"));
1221 assert!(display.contains("player 1"));
1222 assert!(display.contains("frame 50"));
1223 }
1224
1225 #[test]
1226 fn test_serialization_error_display() {
1227 let err = FortressError::SerializationError {
1228 context: "failed to serialize game state".to_string(),
1229 };
1230 let display = format!("{}", err);
1231 assert!(display.contains("Serialization error"));
1232 assert!(display.contains("failed to serialize game state"));
1233 }
1234
1235 #[test]
1236 fn test_internal_error_display() {
1237 let err = FortressError::InternalError {
1238 context: "unexpected state transition".to_string(),
1239 };
1240 let display = format!("{}", err);
1241 assert!(display.contains("Internal error"));
1242 assert!(display.contains("please report as bug"));
1243 assert!(display.contains("unexpected state transition"));
1244 }
1245
1246 #[test]
1247 fn test_socket_error_display() {
1248 let err = FortressError::SocketError {
1249 context: "connection refused".to_string(),
1250 };
1251 let display = format!("{}", err);
1252 assert!(display.contains("Socket error"));
1253 assert!(display.contains("connection refused"));
1254 }
1255
1256 #[test]
1257 fn test_error_debug() {
1258 let err = FortressError::PredictionThreshold;
1259 let debug = format!("{:?}", err);
1260 assert!(debug.contains("PredictionThreshold"));
1261 }
1262
1263 #[test]
1264 #[allow(clippy::redundant_clone)] fn test_error_clone() {
1266 let err = FortressError::InvalidRequest {
1267 info: "test".to_string(),
1268 };
1269 let cloned = err.clone();
1270 assert_eq!(err, cloned);
1271 }
1272
1273 #[test]
1274 fn test_error_partial_eq() {
1275 let err1 = FortressError::NotSynchronized;
1276 let err2 = FortressError::NotSynchronized;
1277 let err3 = FortressError::PredictionThreshold;
1278 assert_eq!(err1, err2);
1279 assert_ne!(err1, err3);
1280 }
1281
1282 #[test]
1283 fn test_error_implements_std_error() {
1284 let err: Box<dyn Error> = Box::new(FortressError::NotSynchronized);
1285 assert!(err.source().is_none());
1287 }
1288
1289 #[test]
1294 fn test_index_out_of_bounds_display() {
1295 let err = IndexOutOfBounds {
1296 name: "input_queues",
1297 index: 5,
1298 length: 3,
1299 };
1300 let display = format!("{}", err);
1301 assert!(display.contains("input_queues"));
1302 assert!(display.contains('5'));
1303 assert!(display.contains('3'));
1304 assert!(display.contains("out of bounds"));
1305 }
1306
1307 #[test]
1308 fn test_invalid_frame_reason_null_frame() {
1309 let reason = InvalidFrameReason::NullFrame;
1310 let display = format!("{}", reason);
1311 assert!(display.contains("NULL_FRAME"));
1312 }
1313
1314 #[test]
1315 fn test_invalid_frame_reason_not_in_past() {
1316 let reason = InvalidFrameReason::NotInPast {
1317 current_frame: Frame::new(10),
1318 };
1319 let display = format!("{}", reason);
1320 assert!(display.contains("past"));
1321 assert!(display.contains("10"));
1322 }
1323
1324 #[test]
1325 fn test_invalid_frame_reason_outside_prediction_window() {
1326 let reason = InvalidFrameReason::OutsidePredictionWindow {
1327 current_frame: Frame::new(100),
1328 max_prediction: 8,
1329 };
1330 let display = format!("{}", reason);
1331 assert!(display.contains("prediction window"));
1332 assert!(display.contains("100"));
1333 assert!(display.contains('8'));
1334 }
1335
1336 #[test]
1337 fn test_invalid_frame_reason_wrong_saved_frame() {
1338 let reason = InvalidFrameReason::WrongSavedFrame {
1339 saved_frame: Frame::new(42),
1340 };
1341 let display = format!("{}", reason);
1342 assert!(display.contains("wrong frame"));
1343 assert!(display.contains("42"));
1344 }
1345
1346 #[test]
1347 fn test_invalid_frame_reason_not_confirmed() {
1348 let reason = InvalidFrameReason::NotConfirmed {
1349 confirmed_frame: Frame::new(50),
1350 };
1351 let display = format!("{}", reason);
1352 assert!(display.contains("not confirmed"));
1353 assert!(display.contains("50"));
1354 }
1355
1356 #[test]
1357 fn invalid_frame_reason_missing_state_display() {
1358 let reason = InvalidFrameReason::MissingState;
1359 let display = format!("{reason}");
1360 assert!(
1361 display.contains("saved state"),
1362 "Expected 'saved state' in: {display}"
1363 );
1364 }
1365
1366 #[test]
1367 fn test_internal_error_kind_index_out_of_bounds() {
1368 let kind = InternalErrorKind::IndexOutOfBounds(IndexOutOfBounds {
1369 name: "states",
1370 index: 10,
1371 length: 5,
1372 });
1373 let display = format!("{}", kind);
1374 assert!(display.contains("states"));
1375 assert!(display.contains("10"));
1376 assert!(display.contains('5'));
1377 }
1378
1379 #[test]
1380 fn test_internal_error_kind_synchronized_inputs_failed() {
1381 let kind = InternalErrorKind::SynchronizedInputsFailed {
1382 frame: Frame::new(25),
1383 };
1384 let display = format!("{}", kind);
1385 assert!(display.contains("synchronized inputs"));
1386 assert!(display.contains("25"));
1387 }
1388
1389 #[test]
1390 fn test_internal_error_kind_empty_player_inputs() {
1391 let kind = InternalErrorKind::EmptyPlayerInputs;
1392 let display = format!("{}", kind);
1393 assert!(display.contains("empty"));
1394 }
1395
1396 #[test]
1397 fn test_invalid_frame_structured_display() {
1398 let err = FortressError::InvalidFrameStructured {
1399 frame: Frame::new(42),
1400 reason: InvalidFrameReason::NullFrame,
1401 };
1402 let display = format!("{}", err);
1403 assert!(display.contains("Invalid frame"));
1404 assert!(display.contains("42"));
1405 assert!(display.contains("NULL_FRAME"));
1406 }
1407
1408 #[test]
1409 fn test_internal_error_structured_display() {
1410 let err = FortressError::InternalErrorStructured {
1411 kind: InternalErrorKind::BufferIndexOutOfBounds,
1412 };
1413 let display = format!("{}", err);
1414 assert!(display.contains("Internal error"));
1415 assert!(display.contains("buffer index out of bounds"));
1416 }
1417
1418 #[test]
1419 fn test_internal_error_kind_disconnect_status_not_found() {
1420 let kind = InternalErrorKind::DisconnectStatusNotFound {
1421 player_handle: PlayerHandle(3),
1422 };
1423 let display = format!("{}", kind);
1424 assert!(display.contains("disconnect status"));
1425 assert!(display.contains("player handle 3"));
1426 }
1427
1428 #[test]
1429 fn test_internal_error_kind_endpoint_not_found_for_remote() {
1430 let kind = InternalErrorKind::EndpointNotFoundForRemote {
1431 player_handle: PlayerHandle(5),
1432 };
1433 let display = format!("{}", kind);
1434 assert!(display.contains("endpoint not found"));
1435 assert!(display.contains("remote player 5"));
1436 }
1437
1438 #[test]
1439 fn test_internal_error_kind_endpoint_not_found_for_spectator() {
1440 let kind = InternalErrorKind::EndpointNotFoundForSpectator {
1441 player_handle: PlayerHandle(7),
1442 };
1443 let display = format!("{}", kind);
1444 assert!(display.contains("endpoint not found"));
1445 assert!(display.contains("spectator 7"));
1446 }
1447
1448 #[test]
1449 fn test_structured_errors_are_copy() {
1450 let iob = IndexOutOfBounds {
1452 name: "test",
1453 index: 1,
1454 length: 2,
1455 };
1456 let iob2 = iob; assert_eq!(iob, iob2);
1458
1459 let reason = InvalidFrameReason::NullFrame;
1460 let reason2 = reason; assert_eq!(reason, reason2);
1462
1463 let kind = InternalErrorKind::EmptyPlayerInputs;
1464 let kind2 = kind; assert_eq!(kind, kind2);
1466 }
1467
1468 #[test]
1473 fn test_rle_decode_reason_bitfield_index_out_of_bounds() {
1474 let reason = RleDecodeReason::BitfieldIndexOutOfBounds;
1475 let display = format!("{}", reason);
1476 assert!(display.contains("bitfield index out of bounds"));
1477 }
1478
1479 #[test]
1480 fn test_rle_decode_reason_destination_slice_out_of_bounds() {
1481 let reason = RleDecodeReason::DestinationSliceOutOfBounds;
1482 let display = format!("{}", reason);
1483 assert!(display.contains("destination slice out of bounds"));
1484 }
1485
1486 #[test]
1487 fn test_rle_decode_reason_source_slice_out_of_bounds() {
1488 let reason = RleDecodeReason::SourceSliceOutOfBounds;
1489 let display = format!("{}", reason);
1490 assert!(display.contains("source slice out of bounds"));
1491 }
1492
1493 #[test]
1494 fn test_rle_decode_reason_truncated_data() {
1495 let reason = RleDecodeReason::TruncatedData {
1496 offset: 100,
1497 buffer_len: 50,
1498 };
1499 let display = format!("{}", reason);
1500 assert!(display.contains("truncated data"));
1501 assert!(display.contains("100"));
1502 assert!(display.contains("50"));
1503 }
1504
1505 #[test]
1506 fn test_rle_decode_reason_unknown() {
1507 let reason = RleDecodeReason::Unknown;
1508 let display = format!("{}", reason);
1509 assert!(display.contains("unknown RLE decode error"));
1510 }
1511
1512 #[test]
1513 fn test_internal_error_kind_rle_decode_error() {
1514 let kind = InternalErrorKind::RleDecodeError {
1515 reason: RleDecodeReason::BitfieldIndexOutOfBounds,
1516 };
1517 let display = format!("{}", kind);
1518 assert!(display.contains("RLE decode failed"));
1519 assert!(display.contains("bitfield index out of bounds"));
1520 }
1521
1522 #[test]
1523 fn test_rle_decode_reason_is_copy() {
1524 let reason = RleDecodeReason::BitfieldIndexOutOfBounds;
1526 let reason2 = reason; assert_eq!(reason, reason2);
1528
1529 let reason_with_data = RleDecodeReason::TruncatedData {
1530 offset: 10,
1531 buffer_len: 5,
1532 };
1533 let reason_with_data2 = reason_with_data; assert_eq!(reason_with_data, reason_with_data2);
1535
1536 let reason_unknown = RleDecodeReason::Unknown;
1537 let reason_unknown2 = reason_unknown; assert_eq!(reason_unknown, reason_unknown2);
1539 }
1540
1541 #[test]
1546 fn test_invalid_request_kind_player_handle_in_use() {
1547 let kind = InvalidRequestKind::PlayerHandleInUse {
1548 handle: PlayerHandle(3),
1549 };
1550 let display = format!("{}", kind);
1551 assert!(display.contains("player handle 3"));
1552 assert!(display.contains("already in use"));
1553 }
1554
1555 #[test]
1556 fn test_invalid_request_kind_not_local_player() {
1557 let kind = InvalidRequestKind::NotLocalPlayer {
1558 handle: PlayerHandle(2),
1559 };
1560 let display = format!("{}", kind);
1561 assert!(display.contains("player handle 2"));
1562 assert!(display.contains("local player"));
1563 }
1564
1565 #[test]
1566 fn test_invalid_request_kind_not_remote_player_or_spectator() {
1567 let kind = InvalidRequestKind::NotRemotePlayerOrSpectator {
1568 handle: PlayerHandle(1),
1569 };
1570 let display = format!("{}", kind);
1571 assert!(display.contains("player handle 1"));
1572 assert!(display.contains("remote player or spectator"));
1573 }
1574
1575 #[test]
1576 fn test_invalid_request_kind_missing_local_input() {
1577 let kind = InvalidRequestKind::MissingLocalInput;
1578 let display = format!("{}", kind);
1579 assert!(display.contains("missing local input"));
1580 }
1581
1582 #[test]
1583 fn test_invalid_request_kind_no_confirmed_input() {
1584 let kind = InvalidRequestKind::NoConfirmedInput {
1585 frame: Frame::new(42),
1586 };
1587 let display = format!("{}", kind);
1588 assert!(display.contains("no confirmed input"));
1589 assert!(display.contains("42"));
1590 }
1591
1592 #[test]
1593 fn test_invalid_request_kind_config_value_out_of_range() {
1594 let kind = InvalidRequestKind::ConfigValueOutOfRange {
1595 field: "fps",
1596 min: 1,
1597 max: 120,
1598 actual: 0,
1599 };
1600 let display = format!("{}", kind);
1601 assert!(display.contains("fps"));
1602 assert!(display.contains("out of range"));
1603 assert!(display.contains('0'));
1604 assert!(display.contains('1'));
1605 assert!(display.contains("120"));
1606 }
1607
1608 #[test]
1609 fn test_invalid_request_kind_frame_delay_too_large() {
1610 let kind = InvalidRequestKind::FrameDelayTooLarge {
1611 delay: 10,
1612 max_delay: 5,
1613 };
1614 let display = format!("{}", kind);
1615 assert!(display.contains("frame delay"));
1616 assert!(display.contains("10"));
1617 assert!(display.contains('5'));
1618 }
1619
1620 #[test]
1621 fn test_invalid_request_kind_queue_length_too_small() {
1622 let kind = InvalidRequestKind::QueueLengthTooSmall { length: 1 };
1623 let display = format!("{}", kind);
1624 assert!(display.contains("queue length"));
1625 assert!(display.contains('1'));
1626 assert!(display.contains("minimum is 2"));
1627 }
1628
1629 #[test]
1630 fn test_invalid_request_kind_event_queue_size_too_small() {
1631 let kind = InvalidRequestKind::EventQueueSizeTooSmall { size: 5 };
1632 let display = format!("{}", kind);
1633 assert!(display.contains("event queue size"));
1634 assert!(display.contains('5'));
1635 assert!(display.contains("minimum is 10"));
1636 }
1637
1638 #[test]
1639 fn test_invalid_request_kind_zero_players() {
1640 let kind = InvalidRequestKind::ZeroPlayers;
1641 let display = format!("{}", kind);
1642 assert!(display.contains("players"));
1643 assert!(display.contains("greater than 0"));
1644 }
1645
1646 #[test]
1647 fn test_invalid_request_kind_zero_fps() {
1648 let kind = InvalidRequestKind::ZeroFps;
1649 let display = format!("{}", kind);
1650 assert!(display.contains("FPS"));
1651 assert!(display.contains("greater than 0"));
1652 }
1653
1654 #[test]
1655 fn test_invalid_request_kind_zero_buffer_size() {
1656 let kind = InvalidRequestKind::ZeroBufferSize;
1657 let display = format!("{}", kind);
1658 assert!(display.contains("buffer size"));
1659 assert!(display.contains("greater than 0"));
1660 }
1661
1662 #[test]
1663 fn test_invalid_request_kind_not_enough_players() {
1664 let kind = InvalidRequestKind::NotEnoughPlayers {
1665 expected: 4,
1666 actual: 2,
1667 };
1668 let display = format!("{}", kind);
1669 assert!(display.contains("not enough players"));
1670 assert!(display.contains('4'));
1671 assert!(display.contains('2'));
1672 }
1673
1674 #[test]
1675 fn test_invalid_request_kind_check_distance_too_large() {
1676 let kind = InvalidRequestKind::CheckDistanceTooLarge {
1677 check_dist: 20,
1678 max_prediction: 10,
1679 };
1680 let display = format!("{}", kind);
1681 assert!(display.contains("check distance"));
1682 assert!(display.contains("20"));
1683 assert!(display.contains("10"));
1684 }
1685
1686 #[test]
1687 fn test_invalid_request_kind_max_frames_behind_invalid() {
1688 let kind = InvalidRequestKind::MaxFramesBehindInvalid {
1689 value: 100,
1690 buffer_size: 50,
1691 };
1692 let display = format!("{}", kind);
1693 assert!(display.contains("max frames behind"));
1694 assert!(display.contains("100"));
1695 assert!(display.contains("50"));
1696 }
1697
1698 #[test]
1699 fn test_invalid_request_kind_catchup_speed_invalid() {
1700 let kind = InvalidRequestKind::CatchupSpeedInvalid {
1701 speed: 5,
1702 max_frames_behind: 2,
1703 };
1704 let display = format!("{}", kind);
1705 assert!(display.contains("catchup speed"));
1706 assert!(display.contains('5'));
1707 assert!(display.contains('2'));
1708 }
1709
1710 #[test]
1711 fn test_invalid_request_kind_disconnect_invalid_handle() {
1712 let kind = InvalidRequestKind::DisconnectInvalidHandle {
1713 handle: PlayerHandle(99),
1714 };
1715 let display = format!("{}", kind);
1716 assert!(display.contains("disconnect"));
1717 assert!(display.contains("99"));
1718 assert!(display.contains("invalid"));
1719 }
1720
1721 #[test]
1722 fn test_invalid_request_kind_disconnect_local_player() {
1723 let kind = InvalidRequestKind::DisconnectLocalPlayer {
1724 handle: PlayerHandle(0),
1725 };
1726 let display = format!("{}", kind);
1727 assert!(display.contains("disconnect"));
1728 assert!(display.contains("local player"));
1729 assert!(display.contains('0'));
1730 }
1731
1732 #[test]
1733 fn test_invalid_request_kind_already_disconnected() {
1734 let kind = InvalidRequestKind::AlreadyDisconnected {
1735 handle: PlayerHandle(2),
1736 };
1737 let display = format!("{}", kind);
1738 assert!(display.contains("already disconnected"));
1739 assert!(display.contains('2'));
1740 }
1741
1742 #[test]
1743 fn test_invalid_request_kind_wrong_protocol_state() {
1744 let kind = InvalidRequestKind::WrongProtocolState {
1745 current_state: "Running",
1746 expected_state: "Synchronizing",
1747 };
1748 let display = format!("{}", kind);
1749 assert!(display.contains("wrong protocol state"));
1750 assert!(display.contains("Running"));
1751 assert!(display.contains("Synchronizing"));
1752 }
1753
1754 #[test]
1755 fn test_invalid_request_kind_custom() {
1756 let kind = InvalidRequestKind::Custom("custom error message");
1757 let display = format!("{}", kind);
1758 assert!(display.contains("custom error message"));
1759 }
1760
1761 #[test]
1762 fn test_invalid_request_kind_is_copy() {
1763 let kind = InvalidRequestKind::ZeroPlayers;
1765 let kind2 = kind; assert_eq!(kind, kind2);
1767
1768 let kind_with_data = InvalidRequestKind::PlayerHandleInUse {
1769 handle: PlayerHandle(1),
1770 };
1771 let kind_with_data2 = kind_with_data; assert_eq!(kind_with_data, kind_with_data2);
1773 }
1774
1775 #[test]
1780 fn test_serialization_error_kind_endpoint_creation_failed() {
1781 let kind = SerializationErrorKind::EndpointCreationFailed;
1782 let display = format!("{}", kind);
1783 assert!(display.contains("failed to create"));
1784 assert!(display.contains("endpoint"));
1785 assert!(display.contains("remote players"));
1786 }
1787
1788 #[test]
1789 fn test_serialization_error_kind_spectator_endpoint_creation_failed() {
1790 let kind = SerializationErrorKind::SpectatorEndpointCreationFailed;
1791 let display = format!("{}", kind);
1792 assert!(display.contains("failed to create"));
1793 assert!(display.contains("endpoint"));
1794 assert!(display.contains("spectators"));
1795 }
1796
1797 #[test]
1798 fn test_serialization_error_kind_custom() {
1799 let kind = SerializationErrorKind::Custom("custom serialization error");
1800 let display = format!("{}", kind);
1801 assert!(display.contains("custom serialization error"));
1802 }
1803
1804 #[test]
1805 fn test_serialization_error_kind_is_copy() {
1806 let kind = SerializationErrorKind::EndpointCreationFailed;
1808 let kind2 = kind; assert_eq!(kind, kind2);
1810 }
1811
1812 #[test]
1817 fn test_socket_error_kind_bind_failed() {
1818 let kind = SocketErrorKind::BindFailed { port: 8080 };
1819 let display = format!("{}", kind);
1820 assert!(display.contains("failed to bind"));
1821 assert!(display.contains("8080"));
1822 }
1823
1824 #[test]
1825 fn test_socket_error_kind_bind_failed_after_retries() {
1826 let kind = SocketErrorKind::BindFailedAfterRetries {
1827 port: 9000,
1828 attempts: 5,
1829 };
1830 let display = format!("{}", kind);
1831 assert!(display.contains("failed to bind"));
1832 assert!(display.contains("9000"));
1833 assert!(display.contains('5'));
1834 assert!(display.contains("attempts"));
1835 }
1836
1837 #[test]
1838 fn test_socket_error_kind_custom() {
1839 let kind = SocketErrorKind::Custom("custom socket error");
1840 let display = format!("{}", kind);
1841 assert!(display.contains("custom socket error"));
1842 }
1843
1844 #[test]
1845 fn test_socket_error_kind_is_copy() {
1846 let kind = SocketErrorKind::BindFailed { port: 8080 };
1848 let kind2 = kind; assert_eq!(kind, kind2);
1850 }
1851
1852 #[test]
1857 fn test_invalid_request_structured_display() {
1858 let err = FortressError::InvalidRequestStructured {
1859 kind: InvalidRequestKind::ZeroPlayers,
1860 };
1861 let display = format!("{}", err);
1862 assert!(display.contains("Invalid Request"));
1863 assert!(display.contains("players"));
1864 }
1865
1866 #[test]
1867 fn test_serialization_error_structured_display() {
1868 let err = FortressError::SerializationErrorStructured {
1869 kind: SerializationErrorKind::EndpointCreationFailed,
1870 };
1871 let display = format!("{}", err);
1872 assert!(display.contains("Serialization error"));
1873 assert!(display.contains("endpoint"));
1874 }
1875
1876 #[test]
1877 fn test_socket_error_structured_display() {
1878 let err = FortressError::SocketErrorStructured {
1879 kind: SocketErrorKind::BindFailed { port: 8080 },
1880 };
1881 let display = format!("{}", err);
1882 assert!(display.contains("Socket error"));
1883 assert!(display.contains("8080"));
1884 }
1885
1886 #[test]
1891 fn test_from_invalid_request_kind() {
1892 let kind = InvalidRequestKind::ZeroPlayers;
1893 let err: FortressError = kind.into();
1894 assert_eq!(
1895 err,
1896 FortressError::InvalidRequestStructured {
1897 kind: InvalidRequestKind::ZeroPlayers
1898 }
1899 );
1900 }
1901
1902 #[test]
1903 fn test_from_serialization_error_kind() {
1904 let kind = SerializationErrorKind::EndpointCreationFailed;
1905 let err: FortressError = kind.into();
1906 assert_eq!(
1907 err,
1908 FortressError::SerializationErrorStructured {
1909 kind: SerializationErrorKind::EndpointCreationFailed
1910 }
1911 );
1912 }
1913
1914 #[test]
1915 fn test_from_socket_error_kind() {
1916 let kind = SocketErrorKind::BindFailed { port: 8080 };
1917 let err: FortressError = kind.into();
1918 assert_eq!(
1919 err,
1920 FortressError::SocketErrorStructured {
1921 kind: SocketErrorKind::BindFailed { port: 8080 }
1922 }
1923 );
1924 }
1925
1926 #[test]
1931 fn test_invalid_request_kind_duration_config_out_of_range() {
1932 let kind = InvalidRequestKind::DurationConfigOutOfRange {
1933 field: "disconnect_timeout",
1934 min_ms: 100,
1935 max_ms: 60000,
1936 actual_ms: 50,
1937 };
1938 let display = format!("{}", kind);
1939 assert!(display.contains("duration configuration"));
1940 assert!(display.contains("disconnect_timeout"));
1941 assert!(display.contains("50ms"));
1942 assert!(display.contains("100ms"));
1943 assert!(display.contains("60000ms"));
1944 }
1945
1946 #[test]
1947 fn test_invalid_request_kind_invalid_local_player_handle() {
1948 let kind = InvalidRequestKind::InvalidLocalPlayerHandle {
1949 handle: PlayerHandle(5),
1950 num_players: 4,
1951 };
1952 let display = format!("{}", kind);
1953 assert!(display.contains("invalid local player handle"));
1954 assert!(display.contains('5'));
1955 assert!(display.contains("num_players is 4"));
1956 }
1957
1958 #[test]
1959 fn test_invalid_request_kind_invalid_remote_player_handle() {
1960 let kind = InvalidRequestKind::InvalidRemotePlayerHandle {
1961 handle: PlayerHandle(10),
1962 num_players: 2,
1963 };
1964 let display = format!("{}", kind);
1965 assert!(display.contains("invalid remote player handle"));
1966 assert!(display.contains("10"));
1967 assert!(display.contains("num_players is 2"));
1968 }
1969
1970 #[test]
1971 fn test_invalid_request_kind_invalid_spectator_handle() {
1972 let kind = InvalidRequestKind::InvalidSpectatorHandle {
1973 handle: PlayerHandle(3),
1974 num_players: 2,
1975 };
1976 let display = format!("{}", kind);
1977 assert!(display.contains("invalid spectator handle"));
1978 assert!(display.contains('3'));
1979 assert!(display.contains("num_players is 2"));
1980 }
1981
1982 #[test]
1983 fn test_invalid_request_kind_input_delay_too_large() {
1984 let kind = InvalidRequestKind::InputDelayTooLarge {
1985 delay_frames: 660,
1986 fps: 60,
1987 max_seconds_limit: 10,
1988 };
1989 let display = format!("{}", kind);
1990 assert!(display.contains("input delay"));
1991 assert!(display.contains("660"));
1992 assert!(display.contains("11.00s")); assert!(display.contains("10s")); }
1995
1996 #[test]
1997 fn test_invalid_request_kind_input_delay_too_large_with_zero_fps() {
1998 let kind = InvalidRequestKind::InputDelayTooLarge {
2000 delay_frames: 100,
2001 fps: 0,
2002 max_seconds_limit: 10,
2003 };
2004 let display = format!("{}", kind);
2005 assert!(display.contains("input delay"));
2006 assert!(display.contains("100"));
2007 assert!(
2008 display.contains("inf"),
2009 "Should display infinity for zero fps: {display}"
2010 );
2011 }
2012
2013 #[test]
2014 fn test_new_variants_are_copy() {
2015 let kind1 = InvalidRequestKind::DurationConfigOutOfRange {
2017 field: "test",
2018 min_ms: 0,
2019 max_ms: 100,
2020 actual_ms: 50,
2021 };
2022 let kind1_copy = kind1; assert_eq!(kind1, kind1_copy);
2024
2025 let kind2 = InvalidRequestKind::InvalidLocalPlayerHandle {
2026 handle: PlayerHandle(1),
2027 num_players: 2,
2028 };
2029 let kind2_copy = kind2; assert_eq!(kind2, kind2_copy);
2031
2032 let kind3 = InvalidRequestKind::InputDelayTooLarge {
2033 delay_frames: 10,
2034 fps: 60,
2035 max_seconds_limit: 1,
2036 };
2037 let kind3_copy = kind3; assert_eq!(kind3, kind3_copy);
2039 }
2040
2041 #[test]
2046 fn test_frame_arithmetic_overflow_display() {
2047 let err = FortressError::FrameArithmeticOverflow {
2048 frame: Frame::new(i32::MAX),
2049 operand: 1,
2050 operation: "add",
2051 };
2052 let display = format!("{}", err);
2053 assert!(display.contains("Frame arithmetic overflow"));
2054 assert!(display.contains("add"));
2055 assert!(display.contains('1'));
2056 }
2057
2058 #[test]
2059 fn test_frame_arithmetic_overflow_sub_display() {
2060 let err = FortressError::FrameArithmeticOverflow {
2061 frame: Frame::new(i32::MIN),
2062 operand: 1,
2063 operation: "sub",
2064 };
2065 let display = format!("{}", err);
2066 assert!(display.contains("Frame arithmetic overflow"));
2067 assert!(display.contains("sub"));
2068 }
2069
2070 #[test]
2071 fn test_frame_value_too_large_display() {
2072 let too_large = (i32::MAX as usize) + 1;
2073 let err = FortressError::FrameValueTooLarge { value: too_large };
2074 let display = format!("{}", err);
2075 assert!(display.contains("Frame value too large"));
2076 assert!(display.contains(&too_large.to_string()));
2077 assert!(display.contains(&i32::MAX.to_string()));
2078 }
2079
2080 #[test]
2081 fn test_frame_arithmetic_overflow_debug() {
2082 let err = FortressError::FrameArithmeticOverflow {
2083 frame: Frame::new(100),
2084 operand: 50,
2085 operation: "add",
2086 };
2087 let debug = format!("{:?}", err);
2088 assert!(debug.contains("FrameArithmeticOverflow"));
2089 assert!(debug.contains("100"));
2090 assert!(debug.contains("50"));
2091 assert!(debug.contains("add"));
2092 }
2093
2094 #[test]
2095 fn test_frame_value_too_large_debug() {
2096 let err = FortressError::FrameValueTooLarge { value: 12345 };
2097 let debug = format!("{:?}", err);
2098 assert!(debug.contains("FrameValueTooLarge"));
2099 assert!(debug.contains("12345"));
2100 }
2101
2102 #[test]
2103 fn test_frame_errors_are_clone_and_eq() {
2104 let err1 = FortressError::FrameArithmeticOverflow {
2105 frame: Frame::new(100),
2106 operand: 1,
2107 operation: "add",
2108 };
2109 let err1_clone = err1.clone();
2110 assert_eq!(err1, err1_clone);
2111
2112 let err2 = FortressError::FrameValueTooLarge { value: 999 };
2113 let err2_clone = err2.clone();
2114 assert_eq!(err2, err2_clone);
2115 }
2116}