timecat/
error.rs

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