1use thiserror::Error;
42
43#[derive(Error, Debug)]
45pub enum PhiloteError {
46 #[error("Variable '{0}' not found")]
47 VariableNotFound(String),
48
49 #[error("Shape mismatch: expected {expected:?}, got {actual:?}")]
50 ShapeMismatch {
51 expected: Vec<usize>,
52 actual: Vec<usize>,
53 },
54
55 #[error("Invalid variable type: {0}")]
56 InvalidVariableType(String),
57
58 #[error("Discipline not initialized")]
59 DisciplineNotInitialized,
60
61 #[error("Setup not called")]
62 SetupNotCalled,
63
64 #[error("Option '{name}' not found or has invalid type")]
65 InvalidOption { name: String },
66
67 #[error("Array index out of bounds: {index} >= {size}")]
68 IndexOutOfBounds { index: usize, size: usize },
69
70 #[error("gRPC communication error: {0}")]
71 GrpcError(Box<tonic::Status>),
72
73 #[error("Protocol buffer error: {0}")]
74 ProtobufError(#[from] prost::DecodeError),
75
76 #[error("I/O error: {0}")]
77 IoError(#[from] std::io::Error),
78
79 #[error("Array operation error: {0}")]
80 ArrayError(String),
81
82 #[error("Not implemented: {0}")]
83 NotImplemented(String),
84
85 #[error("Configuration error: {0}")]
86 ConfigurationError(String),
87
88 #[error("Operation cancelled")]
89 Cancelled,
90}
91
92impl PhiloteError {
93 pub fn array_error<S: Into<String>>(msg: S) -> Self {
95 PhiloteError::ArrayError(msg.into())
96 }
97
98 pub fn not_implemented<S: Into<String>>(feature: S) -> Self {
100 PhiloteError::NotImplemented(feature.into())
101 }
102
103 pub fn config_error<S: Into<String>>(msg: S) -> Self {
105 PhiloteError::ConfigurationError(msg.into())
106 }
107}
108
109impl From<tonic::Status> for PhiloteError {
110 fn from(status: tonic::Status) -> Self {
111 PhiloteError::GrpcError(Box::new(status))
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118
119 #[test]
120 fn test_array_error_helper() {
121 let err = PhiloteError::array_error("test message");
122 assert!(matches!(err, PhiloteError::ArrayError(_)));
123 assert_eq!(err.to_string(), "Array operation error: test message");
124 }
125
126 #[test]
127 fn test_not_implemented_helper() {
128 let err = PhiloteError::not_implemented("test feature");
129 assert!(matches!(err, PhiloteError::NotImplemented(_)));
130 assert_eq!(err.to_string(), "Not implemented: test feature");
131 }
132
133 #[test]
134 fn test_config_error_helper() {
135 let err = PhiloteError::config_error("test config");
136 assert!(matches!(err, PhiloteError::ConfigurationError(_)));
137 assert_eq!(err.to_string(), "Configuration error: test config");
138 }
139
140 #[test]
141 fn test_variable_not_found_display() {
142 let err = PhiloteError::VariableNotFound("x".to_string());
143 assert_eq!(err.to_string(), "Variable 'x' not found");
144 }
145
146 #[test]
147 fn test_shape_mismatch_display() {
148 let err = PhiloteError::ShapeMismatch {
149 expected: vec![2, 3],
150 actual: vec![3, 2],
151 };
152 assert_eq!(
153 err.to_string(),
154 "Shape mismatch: expected [2, 3], got [3, 2]"
155 );
156 }
157
158 #[test]
159 fn test_invalid_variable_type_display() {
160 let err = PhiloteError::InvalidVariableType("unknown".to_string());
161 assert_eq!(err.to_string(), "Invalid variable type: unknown");
162 }
163
164 #[test]
165 fn test_discipline_not_initialized() {
166 let err = PhiloteError::DisciplineNotInitialized;
167 assert_eq!(err.to_string(), "Discipline not initialized");
168 }
169
170 #[test]
171 fn test_setup_not_called() {
172 let err = PhiloteError::SetupNotCalled;
173 assert_eq!(err.to_string(), "Setup not called");
174 }
175
176 #[test]
177 fn test_invalid_option_display() {
178 let err = PhiloteError::InvalidOption {
179 name: "test_opt".to_string(),
180 };
181 assert_eq!(
182 err.to_string(),
183 "Option 'test_opt' not found or has invalid type"
184 );
185 }
186
187 #[test]
188 fn test_index_out_of_bounds_display() {
189 let err = PhiloteError::IndexOutOfBounds { index: 5, size: 3 };
190 assert_eq!(err.to_string(), "Array index out of bounds: 5 >= 3");
191 }
192
193 #[test]
194 fn test_from_tonic_status() {
195 let status = tonic::Status::internal("test error");
196 let err: PhiloteError = status.into();
197 assert!(matches!(err, PhiloteError::GrpcError(_)));
198 assert!(err.to_string().contains("test error"));
199 }
200
201 #[test]
202 fn test_from_io_error() {
203 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
204 let err: PhiloteError = io_err.into();
205 assert!(matches!(err, PhiloteError::IoError(_)));
206 assert!(err.to_string().contains("file not found"));
207 }
208}