1use super::*;
2use TimecatError::*;
3
4#[cfg(feature = "pyo3")]
5#[derive(Debug)]
6pub enum Pyo3Error {
7 #[cfg(feature = "pyo3")]
8 Pyo3TypeConversionError {
9 from: Cow<'static, str>,
10 to: Cow<'static, str>,
11 },
12}
13
14#[cfg(feature = "pyo3")]
15impl From<Pyo3Error> for PyErr {
16 fn from(err: Pyo3Error) -> Self {
17 match err {
18 Pyo3Error::Pyo3TypeConversionError { from, to } => {
19 pyo3::exceptions::PyTypeError::new_err(format!(
20 "Failed to convert {from} into {to}"
21 ))
22 }
23 }
24 }
25}
26
27#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
28#[derive(Clone, PartialEq, Eq, Debug, Hash)]
29pub enum TimecatError {
30 UnknownCommand,
31 NoInput,
32 NotImplemented,
33 EngineNotRunning,
34 BadFen {
35 fen: Cow<'static, str>,
36 },
37 InvalidDepth {
38 depth: Depth,
39 },
40 IllegalMove {
41 valid_or_null_move: ValidOrNullMove,
42 board_fen: Cow<'static, str>,
43 },
44 ColoredOutputUnchanged {
45 b: bool,
46 },
47 UCIModeUnchanged,
48 ConsoleModeUnchanged,
49 EmptyStack,
50 BestMoveNotFound {
51 fen: Cow<'static, str>,
52 },
53 NullMoveInCheck {
54 fen: Cow<'static, str>,
55 },
56 WTimeNotMentioned,
57 BTimeNotMentioned,
58 GameAlreadyOver,
59 UnknownDebugCommand {
60 command: Cow<'static, str>,
61 },
62 InvalidSpinValue {
63 name: Cow<'static, str>,
64 value: Spin,
65 min: Spin,
66 max: Spin,
67 },
68 SameSourceAndDestination {
69 move_: Move,
70 },
71 InvalidPromotion {
72 move_: Move,
73 },
74 InvalidSanOrLanMove {
75 valid_or_null_move: ValidOrNullMove,
76 fen: Cow<'static, str>,
77 },
78 InvalidSanMoveString {
79 s: Cow<'static, str>,
80 },
81 InvalidLanMoveString {
82 s: Cow<'static, str>,
83 },
84 InvalidMoveString {
85 s: Cow<'static, str>,
86 },
87 InvalidRankString {
88 s: Cow<'static, str>,
89 },
90 InvalidFileString {
91 s: Cow<'static, str>,
92 },
93 InvalidColorString {
94 s: Cow<'static, str>,
95 },
96 InvalidCastleRightsString {
97 s: Cow<'static, str>,
98 },
99 InvalidSquareString {
100 s: Cow<'static, str>,
101 },
102 InvalidPieceTypeString {
103 s: Cow<'static, str>,
104 },
105 InvalidPieceString {
106 s: Cow<'static, str>,
107 },
108 InvalidUciMoveString {
109 s: Cow<'static, str>,
110 },
111 InvalidBoardPosition {
112 position: Box<ChessPosition>,
113 },
114 InvalidGoCommand {
115 s: Cow<'static, str>,
116 },
117 IllegalSearchMoves {
118 illegal_moves: Vec<Move>,
119 },
120 FeatureNotEnabled {
121 s: Cow<'static, str>,
122 },
123 BadNNUEFile,
124 BadPolyglotFile,
125 PolyglotTableParseError,
126 DecompressionFailed {
127 value: Cow<'static, str>,
128 type_name: Cow<'static, str>,
129 },
130 CustomError {
131 err_msg: Cow<'static, str>,
132 },
133}
134
135impl TimecatError {
136 pub fn get_custom_error<E: Error>(error: E) -> Self {
137 Self::CustomError {
138 err_msg: format!("{error}! Please try again!").into(),
139 }
140 }
141}
142
143impl fmt::Display for TimecatError {
144 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
145 match self {
146 UnknownCommand => write!(
147 f,
148 "{}",
149 UnknownCommand.stringify_with_optional_raw_input(None)
150 ),
151 NoInput => write!(f, "No input! Please try again!"),
152 NotImplemented => write!(f, "Sorry, this command is not implemented yet :("),
153 EngineNotRunning => write!(f, "Engine is not running! Please try again!"),
154 BadFen { fen } => write!(f, "Bad FEN string: {fen}! Please try Again!"),
155 InvalidDepth { depth } => write!(f, "Invalid depth {depth}! Please try again!"),
156 IllegalMove {
157 valid_or_null_move,
158 board_fen,
159 } => write!(
160 f,
161 "Illegal move {valid_or_null_move} in position {board_fen}! Please try again!"
162 ),
163 ColoredOutputUnchanged { b } => {
164 write!(f, "Colored output already set to {b}! Please try again!")
165 }
166 UCIModeUnchanged => write!(f, "Already in UCI Mode! Please try again!"),
167 ConsoleModeUnchanged => write!(f, "Already in Console Mode! Please try again!"),
168 EmptyStack => write!(
169 f,
170 "Move Stack is empty, pop not possible! Please try again!"
171 ),
172 BestMoveNotFound { fen } => write!(
173 f,
174 "Best move not found in position {fen}! Please try again!"
175 ),
176 NullMoveInCheck { fen } => write!(
177 f,
178 "Cannot apply null move in position {fen}, as king is in check! Please try again!"
179 ),
180 WTimeNotMentioned => write!(f, "You didn't mention wtime! Please try again!"),
181 BTimeNotMentioned => write!(f, "You didn't mention btime! Please try again!"),
182 GameAlreadyOver => write!(
183 f,
184 "Game is already over! Please start a game from another position!"
185 ),
186 UnknownDebugCommand { command } => write!(
187 f,
188 "Debug command {command} is unknown! The possible commands are on or off! Please try again!"
189 ),
190 InvalidSpinValue {
191 name,
192 value,
193 min,
194 max,
195 } => write!(
196 f,
197 "Cannot set value of {name} to {value}, the value must be from {min} to {max}! Please try again!"
198 ),
199 SameSourceAndDestination { move_ } => {
200 write!(
201 f,
202 "The source and destination squares of the move {} cannot be the same!",
203 move_
204 )
205 }
206 InvalidPromotion { move_ } => {
207 write!(f, "Invalid promotion for the move {}!", move_)
208 }
209 InvalidSanOrLanMove {
210 valid_or_null_move,
211 fen,
212 } => write!(
213 f,
214 "san() and lan() expect move to be legal or null, but got {} in {}",
215 valid_or_null_move, fen
216 ),
217 InvalidSanMoveString { s } => {
218 write!(f, "Got invalid SAN move string {s}! Please try again!")
219 }
220 InvalidLanMoveString { s } => {
221 write!(f, "Got invalid LAN move string {s}! Please try again!")
222 }
223 InvalidMoveString { s } => write!(f, "Got invalid move string {s}! Please try again!"),
224 InvalidRankString { s } => write!(f, "Got invalid rank string {s}! Please try again!"),
225 InvalidFileString { s } => write!(f, "Got invalid file string {s}! Please try again!"),
226 InvalidColorString { s } => {
227 write!(f, "Got invalid color string {s}! Please try again!")
228 }
229 InvalidCastleRightsString { s } => {
230 write!(f, "Got invalid castle rights string {s}! Please try again!")
231 }
232 InvalidSquareString { s } => {
233 write!(f, "Got invalid square string {s}! Please try again!")
234 }
235 InvalidPieceTypeString { s } => {
236 write!(f, "Got invalid piece type string {s}! Please try again!")
237 }
238 InvalidPieceString { s } => {
239 write!(f, "Got invalid piece string {s}! Please try again!")
240 }
241 InvalidUciMoveString { s } => {
242 write!(f, "Invalid uci move string {s}! Please try again!")
243 }
244 InvalidBoardPosition { position } => {
245 write!(f, "Invalid position generated:\n\n{position:#?}")
246 }
247 InvalidGoCommand { s } => write!(f, "Got invalid go command: {s:?}! Please try again!"),
248 IllegalSearchMoves { illegal_moves } => write!(
249 f,
250 "Got illegal search moves: {}! Please try again!",
251 illegal_moves.iter().map(ToString::to_string).join(", ")
252 ),
253 FeatureNotEnabled { s } => write!(
254 f,
255 "The feature {s:?} is not enabled. Please recompile the chess engine with this feature enabled!"
256 ),
257 BadNNUEFile => write!(
258 f,
259 "The NNUE file cannot be parsed properly! Try again with a different NNUE file!"
260 ),
261 BadPolyglotFile => write!(
262 f,
263 "The Polyglot file cannot be parsed properly! Try again with a different Polyglot file!"
264 ),
265 PolyglotTableParseError => write!(
266 f,
267 "The Polyglot Table cannot be parsed properly! Try again with a different Polyglot file!"
268 ),
269 DecompressionFailed { value, type_name } => write!(
270 f,
271 "Failed to decompress value {} into {:?}",
272 value, type_name
273 ),
274 CustomError { err_msg } => write!(f, "{err_msg}"),
275 }
276 }
277}
278
279impl Error for TimecatError {}
280
281impl TimecatError {
282 pub fn stringify_with_optional_raw_input(&self, optional_raw_input: Option<&str>) -> String {
283 match self {
284 Self::UnknownCommand => {
285 let command_type = if GLOBAL_TIMECAT_STATE.is_in_console_mode() {
286 "Console"
287 } else {
288 "UCI"
289 };
290 optional_raw_input.map_or_else(
291 || format!("Unknown {command_type} Command!\nPlease try again!"),
292 |raw_input| {
293 format!(
294 "Unknown {command_type} Command: {:?}\nType help for more information!",
295 raw_input.trim_end_matches('\n')
296 )
297 },
298 )
299 }
300 other_err => other_err.to_string(),
301 }
302 }
303}
304
305impl From<TimecatError> for String {
306 fn from(error: TimecatError) -> Self {
307 error.stringify().into()
308 }
309}
310
311impl From<&Self> for TimecatError {
312 fn from(error: &Self) -> Self {
313 error.clone()
314 }
315}
316
317impl From<ParseBoolError> for TimecatError {
318 fn from(error: ParseBoolError) -> Self {
319 CustomError {
320 err_msg: format!("Failed to parse bool, {error}! Please try again!").into(),
321 }
322 }
323}
324
325impl From<ParseIntError> for TimecatError {
326 fn from(error: ParseIntError) -> Self {
327 CustomError {
328 err_msg: format!("Failed to parse integer, {error}! Please try again!").into(),
329 }
330 }
331}
332
333macro_rules! impl_error_convert {
334 ($class:ty) => {
335 impl From<$class> for TimecatError {
336 fn from(error: $class) -> Self {
337 Self::get_custom_error(error)
338 }
339 }
340 };
341}
342
343impl_error_convert!(std::io::Error);
344impl_error_convert!(std::array::TryFromSliceError);
345
346impl From<String> for TimecatError {
347 fn from(err_msg: String) -> Self {
348 Cow::from(err_msg).into()
349 }
350}
351
352impl From<&'static str> for TimecatError {
353 fn from(err_msg: &'static str) -> Self {
354 Cow::from(err_msg).into()
355 }
356}
357
358impl From<Cow<'static, str>> for TimecatError {
359 fn from(err_msg: Cow<'static, str>) -> Self {
360 CustomError { err_msg }
361 }
362}
363
364#[cfg(feature = "pyo3")]
365impl From<TimecatError> for PyErr {
366 fn from(err: TimecatError) -> Self {
367 pyo3::exceptions::PyRuntimeError::new_err(format!("TimecatError occurred: {:?}", err))
368 }
369}