Skip to main content

alice_physics/
error.rs

1//! Physics Error Types
2//!
3//! Unified error type for the ALICE-Physics engine. Functions that can fail
4//! (deserialization, body lookup, constraint validation) return
5//! `Result<T, PhysicsError>` instead of raw booleans or panicking.
6//!
7//! Author: Moroya Sakamoto
8
9use core::fmt;
10
11/// Unified error type for physics operations.
12#[derive(Clone, Debug, PartialEq, Eq)]
13pub enum PhysicsError {
14    /// Body index is out of range.
15    InvalidBodyIndex {
16        /// The invalid index that was provided
17        index: usize,
18        /// Current number of bodies in the world
19        count: usize,
20    },
21    /// State deserialization failed (corrupted or incompatible data).
22    DeserializationFailed,
23    /// A constraint references a body that does not exist.
24    InvalidConstraint {
25        /// Human-readable description of the problem
26        reason: &'static str,
27    },
28    /// A zero-length direction or normal was provided where a unit vector is required.
29    ZeroLengthVector {
30        /// Context describing where the zero-length vector was encountered
31        context: &'static str,
32    },
33    /// I/O error (reading/writing scene files).
34    #[cfg(feature = "std")]
35    IoError {
36        /// Description of the I/O error
37        message: &'static str,
38    },
39    /// A capacity limit was exceeded (too many bodies, constraints, etc.).
40    CapacityExceeded {
41        /// What resource was exhausted
42        resource: &'static str,
43        /// The limit that was exceeded
44        limit: usize,
45    },
46    /// Invalid configuration parameter.
47    InvalidConfiguration {
48        /// Description of the invalid configuration
49        reason: &'static str,
50    },
51}
52
53impl fmt::Display for PhysicsError {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::InvalidBodyIndex { index, count } => {
57                write!(f, "body index {index} out of range (count={count})")
58            }
59            Self::DeserializationFailed => write!(f, "state deserialization failed"),
60            Self::InvalidConstraint { reason } => {
61                write!(f, "invalid constraint: {reason}")
62            }
63            Self::ZeroLengthVector { context } => {
64                write!(f, "zero-length vector in {context}")
65            }
66            #[cfg(feature = "std")]
67            Self::IoError { message } => write!(f, "I/O error: {message}"),
68            Self::CapacityExceeded { resource, limit } => {
69                write!(f, "{resource} capacity exceeded (limit={limit})")
70            }
71            Self::InvalidConfiguration { reason } => {
72                write!(f, "invalid configuration: {reason}")
73            }
74        }
75    }
76}
77
78#[cfg(feature = "std")]
79impl std::error::Error for PhysicsError {}
80
81// ============================================================================
82// Tests
83// ============================================================================
84
85#[cfg(all(test, feature = "std"))]
86mod tests {
87    use super::*;
88
89    #[cfg(feature = "std")]
90    #[test]
91    fn test_error_display() {
92        let e = PhysicsError::InvalidBodyIndex { index: 5, count: 3 };
93        let s = format!("{e}");
94        assert!(s.contains('5'), "Should contain index");
95        assert!(s.contains('3'), "Should contain count");
96    }
97
98    #[test]
99    fn test_error_debug() {
100        let e = PhysicsError::DeserializationFailed;
101        let s = format!("{e:?}");
102        assert!(s.contains("DeserializationFailed"));
103    }
104
105    #[test]
106    fn test_error_variants() {
107        let e1 = PhysicsError::InvalidBodyIndex { index: 0, count: 0 };
108        let e2 = PhysicsError::DeserializationFailed;
109        let e3 = PhysicsError::InvalidConstraint {
110            reason: "body A == body B",
111        };
112        let e4 = PhysicsError::ZeroLengthVector {
113            context: "ray direction",
114        };
115        assert_ne!(e1, e2);
116        assert_ne!(e3, e4);
117    }
118
119    #[test]
120    fn test_capacity_exceeded() {
121        let e = PhysicsError::CapacityExceeded {
122            resource: "bodies",
123            limit: 10000,
124        };
125        let s = format!("{e}");
126        assert!(s.contains("bodies"));
127        assert!(s.contains("10000"));
128    }
129
130    #[test]
131    fn test_invalid_configuration() {
132        let e = PhysicsError::InvalidConfiguration {
133            reason: "substeps must be > 0",
134        };
135        let s = format!("{e}");
136        assert!(s.contains("substeps"));
137    }
138
139    #[cfg(feature = "std")]
140    #[test]
141    fn test_io_error() {
142        let e = PhysicsError::IoError {
143            message: "file not found",
144        };
145        let s = format!("{e}");
146        assert!(s.contains("file not found"));
147    }
148}