Skip to main content

timecat/
error.rs

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}