From 9010f1e9c24599eb1e5a372bc2f4f6f1cc1eb5ca Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Mon, 19 May 2025 14:18:31 -0700 Subject: [PATCH] [explorer, moves, core] Improve error handling in explorer Implement thiserror::Error for a bunch of error types, and remove string errors from the implementation of the command handler in explorer. Clean up parsing of basic types all over the place. Update Cargo files to include thiserror and anyhow. --- Cargo.lock | 8 +++ core/src/colors.rs | 52 +++++++++++--- core/src/coordinates.rs | 60 +++++++++++----- core/src/errors.rs | 7 -- core/src/lib.rs | 5 +- core/src/macros.rs | 21 ------ core/src/pieces.rs | 137 ++++--------------------------------- core/src/pieces/display.rs | 28 ++++++++ core/src/shapes.rs | 137 +++++++++++++++++++++++++++++++++++++ explorer/Cargo.toml | 2 + explorer/src/main.rs | 93 ++++++++++++------------- moves/src/builder.rs | 7 +- 12 files changed, 331 insertions(+), 226 deletions(-) delete mode 100644 core/src/errors.rs create mode 100644 core/src/pieces/display.rs create mode 100644 core/src/shapes.rs diff --git a/Cargo.lock b/Cargo.lock index 7414d4e..238efc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "bitflags" version = "2.4.2" @@ -188,6 +194,7 @@ checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" name = "explorer" version = "0.1.0" dependencies = [ + "anyhow", "chessfriend_board", "chessfriend_core", "chessfriend_moves", @@ -195,6 +202,7 @@ dependencies = [ "clap", "rustyline", "shlex", + "thiserror", ] [[package]] diff --git a/core/src/colors.rs b/core/src/colors.rs index fee6558..5cf633b 100644 --- a/core/src/colors.rs +++ b/core/src/colors.rs @@ -1,7 +1,7 @@ // Eryn Wells -use crate::{errors::TryFromCharError, try_from_string, Direction}; -use std::fmt; +use crate::Direction; +use thiserror::Error; #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] pub enum Color { @@ -48,31 +48,65 @@ impl Color { pub const fn next(&self) -> Color { Self::ALL[((*self as usize) + 1) % Self::NUM] } + + #[must_use] + pub const fn name(self) -> &'static str { + match self { + Color::White => "white", + Color::Black => "black", + } + } } -impl fmt::Display for Color { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{}", match self { - Color::White => "White", - Color::Black => "Black", + Color::White => "white", + Color::Black => "black", }, ) } } +#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] +#[error("no matching color for character '{0}'")] +pub struct ColorFromCharError(char); + impl TryFrom for Color { - type Error = TryFromCharError; + type Error = ColorFromCharError; fn try_from(value: char) -> Result { match value { 'w' | 'W' => Ok(Color::White), 'b' | 'B' => Ok(Color::Black), - _ => Err(TryFromCharError), + _ => Err(ColorFromCharError(value)), } } } -try_from_string!(Color); +#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] +#[error("no matching color for string")] +pub struct ColorFromStrError; + +impl TryFrom<&str> for Color { + type Error = ColorFromStrError; + + fn try_from(value: &str) -> Result { + match value { + "w" | "white" => Ok(Color::White), + "b" | "black" => Ok(Color::Black), + _ => Err(ColorFromStrError), + } + } +} + +impl std::str::FromStr for Color { + type Err = ColorFromStrError; + + fn from_str(s: &str) -> Result { + Self::try_from(s.to_lowercase().as_str()) + } +} diff --git a/core/src/coordinates.rs b/core/src/coordinates.rs index 0a01ba4..1a13ced 100644 --- a/core/src/coordinates.rs +++ b/core/src/coordinates.rs @@ -1,7 +1,7 @@ // Eryn Wells use crate::Color; -use std::{fmt, str::FromStr}; +use std::fmt; use thiserror::Error; macro_rules! try_from_integer { @@ -10,6 +10,7 @@ macro_rules! try_from_integer { type Error = (); fn try_from(value: $int_type) -> Result { + #[allow(clippy::cast_possible_truncation)] Self::try_from(value as u8) } } @@ -55,6 +56,7 @@ macro_rules! range_bound_struct { #[allow(dead_code)] impl $type { + $vis const NUM: usize = $max; $vis const FIRST: $type = $type(0); $vis const LAST: $type = $type($max - 1); } @@ -148,7 +150,7 @@ impl File { pub const G: File = File(6); pub const H: File = File(7); - pub const ALL: [File; 8] = [ + pub const ALL: [File; File::NUM] = [ File::A, File::B, File::C, @@ -173,7 +175,7 @@ impl Rank { pub const SEVEN: Rank = Rank(6); pub const EIGHT: Rank = Rank(7); - pub const ALL: [Rank; 8] = [ + pub const ALL: [Rank; Self::NUM] = [ Rank::ONE, Rank::TWO, Rank::THREE, @@ -344,7 +346,27 @@ pub enum ParseSquareError { FileError(#[from] ParseFileError), } -impl FromStr for Square { +impl TryFrom<&str> for Square { + type Error = ParseSquareError; + + fn try_from(value: &str) -> Result { + let mut chars = value.chars(); + + let file: File = chars + .next() + .and_then(|c| c.try_into().ok()) + .ok_or(ParseSquareError::FileError(ParseFileError))?; + + let rank: Rank = chars + .next() + .and_then(|c| c.try_into().ok()) + .ok_or(ParseSquareError::RankError(ParseRankError))?; + + Ok(Square::from_file_rank(file, rank)) + } +} + +impl std::str::FromStr for Square { type Err = ParseSquareError; fn from_str(s: &str) -> Result { @@ -377,10 +399,13 @@ impl std::str::FromStr for Rank { .nth(0) .ok_or(ParseRankError) .map(|ch| ch.to_ascii_lowercase())?; - let offset = 'a' as usize - (ch as usize); - let rank = Rank::ALL[offset]; - Ok(rank) + let offset = 'a' as usize - (ch as usize); + if offset >= Rank::ALL.len() { + return Err(ParseRankError); + } + + Ok(Rank::ALL[offset]) } } @@ -397,10 +422,13 @@ impl std::str::FromStr for File { .nth(0) .ok_or(ParseFileError) .map(|ch| ch.to_ascii_lowercase())?; - let offset = '1' as usize - (ch as usize); - let file = File::ALL[offset]; - Ok(file) + let offset = '1' as usize - (ch as usize); + if offset >= File::ALL.len() { + return Err(ParseFileError); + } + + Ok(File::ALL[offset]) } } @@ -489,12 +517,12 @@ mod tests { #[test] fn bad_algebraic_input() { - assert!(Square::from_algebraic_str("a0").is_err()); - assert!(Square::from_algebraic_str("j3").is_err()); - assert!(Square::from_algebraic_str("a11").is_err()); - assert!(Square::from_algebraic_str("b-1").is_err()); - assert!(Square::from_algebraic_str("a 1").is_err()); - assert!(Square::from_algebraic_str("").is_err()); + assert!("a0".parse::().is_err()); + assert!("j3".parse::().is_err()); + assert!("a11".parse::().is_err()); + assert!("b-1".parse::().is_err()); + assert!("a 1".parse::().is_err()); + assert!("".parse::().is_err()); } #[test] diff --git a/core/src/errors.rs b/core/src/errors.rs deleted file mode 100644 index c897997..0000000 --- a/core/src/errors.rs +++ /dev/null @@ -1,7 +0,0 @@ -// Eryn Wells - -#[derive(Debug, Eq, PartialEq)] -pub struct TryFromCharError; - -#[derive(Debug, Eq, PartialEq)] -pub struct TryFromStrError; diff --git a/core/src/lib.rs b/core/src/lib.rs index fc3dfbc..4d54037 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,11 +2,12 @@ pub mod colors; pub mod coordinates; -pub mod errors; pub mod pieces; +pub mod shapes; mod macros; pub use colors::Color; pub use coordinates::{Direction, File, Rank, Square}; -pub use pieces::{Piece, PlacedPiece, Shape}; +pub use pieces::{Piece, PlacedPiece}; +pub use shapes::Shape; diff --git a/core/src/macros.rs b/core/src/macros.rs index 9cce129..dc99d9a 100644 --- a/core/src/macros.rs +++ b/core/src/macros.rs @@ -1,26 +1,5 @@ // Eryn Wells -#[macro_export] -macro_rules! try_from_string { - ($type:ty) => { - try_from_string!($type, &str); - try_from_string!($type, &String); - }; - ($type:ty, $from_type:ty) => { - impl TryFrom<$from_type> for $type { - type Error = $crate::errors::TryFromStrError; - - fn try_from(value: $from_type) -> Result { - let first_char = value - .chars() - .nth(0) - .ok_or($crate::errors::TryFromStrError)?; - Self::try_from(first_char).map_err(|_| $crate::errors::TryFromStrError) - } - } - }; -} - #[macro_export] macro_rules! piece { ($color:ident $shape:ident) => { diff --git a/core/src/pieces.rs b/core/src/pieces.rs index dd6de93..a580967 100644 --- a/core/src/pieces.rs +++ b/core/src/pieces.rs @@ -1,124 +1,10 @@ // Eryn Wells -use crate::{errors::TryFromCharError, try_from_string, Color, Square}; -use std::{array, fmt, slice}; +mod display; -trait _Shape { - fn symbol(&self) -> char; - fn index(&self) -> usize; -} +pub use self::display::{PieceDisplay, PieceDisplayStyle}; -macro_rules! shape { - ($name:ident, $index:expr, $symbol:expr) => { - struct $name; - - impl _Shape for $name { - fn symbol(&self) -> char { - $symbol - } - - fn index(&self) -> usize { - $index - } - } - }; -} - -shape!(Pawn, 0, 'P'); -shape!(Knight, 1, 'K'); -shape!(Bishop, 2, 'B'); -shape!(Rook, 3, 'R'); -shape!(Queen, 4, 'Q'); -shape!(King, 5, 'K'); - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Shape { - Pawn = 0, - Knight = 1, - Bishop = 2, - Rook = 3, - Queen = 4, - King = 5, -} - -impl Shape { - /// Number of piece shapes - pub const NUM: usize = 6; - - /// A slice of all piece shapes - pub const ALL: [Shape; Self::NUM] = [ - Shape::Pawn, - Shape::Knight, - Shape::Bishop, - Shape::Rook, - Shape::Queen, - Shape::King, - ]; - - pub fn iter() -> slice::Iter<'static, Self> { - Shape::ALL.iter() - } - - pub fn into_iter() -> array::IntoIter { - Shape::ALL.into_iter() - } - - /// An iterator over the shapes that a pawn can promote to - pub fn promotable() -> slice::Iter<'static, Shape> { - const PROMOTABLE_SHAPES: [Shape; 4] = - [Shape::Queen, Shape::Rook, Shape::Bishop, Shape::Knight]; - - PROMOTABLE_SHAPES.iter() - } - - const fn to_ascii(self) -> char { - match self { - Shape::Pawn => 'P', - Shape::Knight => 'N', - Shape::Bishop => 'B', - Shape::Rook => 'R', - Shape::Queen => 'Q', - Shape::King => 'K', - } - } -} - -impl TryFrom for Shape { - type Error = TryFromCharError; - - fn try_from(value: char) -> Result { - match value { - 'P' | 'p' => Ok(Shape::Pawn), - 'N' | 'n' => Ok(Shape::Knight), - 'B' | 'b' => Ok(Shape::Bishop), - 'R' | 'r' => Ok(Shape::Rook), - 'Q' | 'q' => Ok(Shape::Queen), - 'K' | 'k' => Ok(Shape::King), - _ => Err(TryFromCharError), - } - } -} - -try_from_string!(Shape); - -impl From<&Shape> for char { - fn from(shape: &Shape) -> char { - char::from(*shape) - } -} - -impl From for char { - fn from(shape: Shape) -> char { - shape.to_ascii() - } -} - -impl fmt::Display for Shape { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let self_char: char = self.into(); - write!(f, "{self_char}") - } -} +use crate::{Color, Shape, Square}; #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct Piece { @@ -195,9 +81,16 @@ impl Piece { } } -impl fmt::Display for Piece { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.to_unicode()) +impl std::fmt::Display for Piece { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{}", + PieceDisplay { + piece: *self, + style: PieceDisplayStyle::default() + } + ) } } @@ -268,8 +161,8 @@ impl PlacedPiece { } } -impl fmt::Display for PlacedPiece { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +impl std::fmt::Display for PlacedPiece { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}{}", self.piece, self.square) } } diff --git a/core/src/pieces/display.rs b/core/src/pieces/display.rs new file mode 100644 index 0000000..77fadb6 --- /dev/null +++ b/core/src/pieces/display.rs @@ -0,0 +1,28 @@ +// Eryn Wells + +use super::Piece; + +#[derive(Default)] +pub enum PieceDisplayStyle { + #[default] + Unicode, + ASCII, + LongForm, +} + +pub struct PieceDisplay { + pub(super) piece: Piece, + pub(super) style: PieceDisplayStyle, +} + +impl std::fmt::Display for PieceDisplay { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.style { + PieceDisplayStyle::Unicode => write!(f, "{}", self.piece.to_unicode()), + PieceDisplayStyle::ASCII => write!(f, "{}", self.piece.to_ascii()), + PieceDisplayStyle::LongForm => { + write!(f, "{} {}", self.piece.color.name(), self.piece.shape.name()) + } + } + } +} diff --git a/core/src/shapes.rs b/core/src/shapes.rs new file mode 100644 index 0000000..0bdfea8 --- /dev/null +++ b/core/src/shapes.rs @@ -0,0 +1,137 @@ +// Eryn Wells + +use std::{array, slice}; +use thiserror::Error; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Shape { + Pawn = 0, + Knight = 1, + Bishop = 2, + Rook = 3, + Queen = 4, + King = 5, +} + +impl Shape { + /// Number of piece shapes + pub const NUM: usize = 6; + + /// A slice of all piece shapes + pub const ALL: [Shape; Self::NUM] = [ + Shape::Pawn, + Shape::Knight, + Shape::Bishop, + Shape::Rook, + Shape::Queen, + Shape::King, + ]; + + pub fn iter() -> slice::Iter<'static, Self> { + Shape::ALL.iter() + } + + #[must_use] + pub fn into_iter() -> array::IntoIter { + Shape::ALL.into_iter() + } + + /// An iterator over the shapes that a pawn can promote to + pub fn promotable() -> slice::Iter<'static, Shape> { + const PROMOTABLE_SHAPES: [Shape; 4] = + [Shape::Queen, Shape::Rook, Shape::Bishop, Shape::Knight]; + + PROMOTABLE_SHAPES.iter() + } + + #[must_use] + pub const fn to_ascii(self) -> char { + match self { + Shape::Pawn => 'P', + Shape::Knight => 'N', + Shape::Bishop => 'B', + Shape::Rook => 'R', + Shape::Queen => 'Q', + Shape::King => 'K', + } + } + + #[must_use] + pub const fn name(self) -> &'static str { + match self { + Shape::Pawn => "pawn", + Shape::Knight => "knight", + Shape::Bishop => "bishop", + Shape::Rook => "rook", + Shape::Queen => "queen", + Shape::King => "king", + } + } +} + +#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] +#[error("no matching piece shape for character '{0:?}'")] +pub struct ShapeFromCharError(char); + +impl TryFrom for Shape { + type Error = ShapeFromCharError; + + fn try_from(value: char) -> Result { + match value { + 'P' | 'p' => Ok(Shape::Pawn), + 'N' | 'n' => Ok(Shape::Knight), + 'B' | 'b' => Ok(Shape::Bishop), + 'R' | 'r' => Ok(Shape::Rook), + 'Q' | 'q' => Ok(Shape::Queen), + 'K' | 'k' => Ok(Shape::King), + _ => Err(ShapeFromCharError(value)), + } + } +} + +#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] +#[error("no matching piece shape for string")] +pub struct ShapeFromStrError; + +impl TryFrom<&str> for Shape { + type Error = ShapeFromStrError; + + fn try_from(value: &str) -> Result { + match value.to_lowercase().as_str() { + "p" | "pawn" => Ok(Shape::Pawn), + "n" | "knight" => Ok(Shape::Knight), + "b" | "bishop" => Ok(Shape::Bishop), + "r" | "rook" => Ok(Shape::Rook), + "q" | "queen" => Ok(Shape::Queen), + "k" | "king" => Ok(Shape::King), + _ => Err(ShapeFromStrError), + } + } +} + +impl std::str::FromStr for Shape { + type Err = ShapeFromStrError; + + fn from_str(s: &str) -> Result { + Self::try_from(s) + } +} + +impl From<&Shape> for char { + fn from(shape: &Shape) -> char { + char::from(*shape) + } +} + +impl From for char { + fn from(shape: Shape) -> char { + shape.to_ascii() + } +} + +impl std::fmt::Display for Shape { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let self_char: char = self.into(); + write!(f, "{self_char}") + } +} diff --git a/explorer/Cargo.toml b/explorer/Cargo.toml index a81f966..00fa045 100644 --- a/explorer/Cargo.toml +++ b/explorer/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.98" chessfriend_core = { path = "../core" } chessfriend_board = { path = "../board" } chessfriend_moves = { path = "../moves" } @@ -13,3 +14,4 @@ chessfriend_position = { path = "../position" } clap = { version = "4.4.12", features = ["derive"] } rustyline = "13.0.0" shlex = "1.2.0" +thiserror = "2" diff --git a/explorer/src/main.rs b/explorer/src/main.rs index bf688d1..8ba0a6b 100644 --- a/explorer/src/main.rs +++ b/explorer/src/main.rs @@ -4,9 +4,11 @@ use chessfriend_board::{fen::FromFenStr, Board}; use chessfriend_core::{Color, Piece, Shape, Square}; use chessfriend_moves::Builder as MoveBuilder; use chessfriend_position::{fen::ToFenStr, PlacePieceStrategy, Position, ValidateMove}; + use clap::{Arg, Command}; use rustyline::error::ReadlineError; use rustyline::DefaultEditor; +use thiserror::Error; struct CommandResult { should_continue: bool, @@ -79,11 +81,17 @@ fn command_line() -> Command { .subcommand(Command::new("starting").about("Reset the board to the starting position")) } -fn respond(line: &str, state: &mut State) -> Result { - let args = shlex::split(line).ok_or("error: Invalid quoting")?; - let matches = command_line() - .try_get_matches_from(args) - .map_err(|e| e.to_string())?; +#[derive(Clone, Debug, Error, Eq, PartialEq)] +enum CommandHandlingError<'a> { + #[error("lexer error")] + LexerError, + #[error("missing {0} argument")] + MissingArgument(&'a str), +} + +fn respond(line: &str, state: &mut State) -> anyhow::Result { + let args = shlex::split(line).ok_or(CommandHandlingError::LexerError)?; + let matches = command_line().try_get_matches_from(args)?; let mut result = CommandResult::default(); @@ -94,68 +102,53 @@ fn respond(line: &str, state: &mut State) -> Result { result.should_print_position = false; } Some(("fen", _matches)) => { - println!( - "{}", - state - .position - .to_fen_str() - .map_err(|_| "error: Unable to generate FEN for current position")? - ); - + println!("{}", state.position.to_fen_str()?); result.should_print_position = false; } Some(("make", matches)) => { let from_square = Square::from_algebraic_str( - matches.get_one::("from").ok_or("Missing square")?, - ) - .map_err(|_| "Error: invalid square specifier")?; + matches + .get_one::("from") + .ok_or(CommandHandlingError::MissingArgument("from"))?, + )?; let to_square = Square::from_algebraic_str( - matches.get_one::("to").ok_or("Missing square")?, - ) - .map_err(|_| "Error: invalid square specifier")?; + matches + .get_one::("to") + .ok_or(CommandHandlingError::MissingArgument("to"))?, + )?; - let ply = MoveBuilder::new() - .from(from_square) - .to(to_square) - .build() - .map_err(|err| format!("Error: {err:?}"))?; + let ply = MoveBuilder::new().from(from_square).to(to_square).build()?; - state - .position - .make_move(ply, ValidateMove::Yes) - .map_err(|err| format!("Error: {err}"))?; + state.position.make_move(ply, ValidateMove::Yes)?; } Some(("place", matches)) => { let color = matches .get_one::("color") - .ok_or("Missing color descriptor")?; - let color = Color::try_from(color).map_err(|_| "Invalid color descriptor")?; + .ok_or(CommandHandlingError::MissingArgument("color"))?; + let color = color.parse::()?; let shape = matches .get_one::("piece") - .ok_or("Missing piece descriptor")?; - let shape = Shape::try_from(shape).map_err(|_| "Invalid piece descriptor")?; + .ok_or(CommandHandlingError::MissingArgument("piece"))?; + let shape = shape.parse::()?; let square = matches .get_one::("square") - .ok_or("Missing square")?; - let square = Square::from_algebraic_str(square) - .map_err(|_| "Error: invalid square specifier")?; + .ok_or(CommandHandlingError::MissingArgument("square"))?; + let square = Square::from_algebraic_str(square)?; let piece = Piece::new(color, shape); state .position - .place_piece(piece, square, PlacePieceStrategy::default()) - .map_err(|err| format!("Error: could not place piece: {err:?}"))?; + .place_piece(piece, square, PlacePieceStrategy::default())?; } Some(("sight", matches)) => { let square = matches .get_one::("square") - .ok_or("Missing square")?; - let square = Square::from_algebraic_str(square) - .map_err(|_| "Error: invalid square specifier")?; + .ok_or(CommandHandlingError::MissingArgument("square"))?; + let square = square.parse::()?; let sight = state.position.sight(square); @@ -167,9 +160,8 @@ fn respond(line: &str, state: &mut State) -> Result { Some(("moves", matches)) => { let square = matches .get_one::("square") - .ok_or("Missing square")?; - let square = Square::from_algebraic_str(square) - .map_err(|_| "Error: invalid square specifier")?; + .ok_or(CommandHandlingError::MissingArgument("square"))?; + let square = square.parse::()?; let movement = state.position.movement(square); @@ -182,7 +174,7 @@ fn respond(line: &str, state: &mut State) -> Result { let starting_position = Position::starting(); state.position = starting_position; } - Some(("reset", matches)) => do_reset_command(state, matches)?, + Some(("reset", matches)) => result = do_reset_command(state, matches)?, Some((name, _matches)) => unimplemented!("{name}"), None => unreachable!("Subcommand required"), } @@ -190,19 +182,24 @@ fn respond(line: &str, state: &mut State) -> Result { Ok(result) } -fn do_reset_command(state: &mut State, matches: &clap::ArgMatches) -> Result<(), String> { +fn do_reset_command( + state: &mut State, + matches: &clap::ArgMatches, +) -> anyhow::Result { match matches.subcommand() { None | Some(("clear", _)) => state.position = Position::empty(), Some(("starting", _)) => state.position = Position::starting(), Some(("fen", matches)) => { - let fen = matches.get_one::("fen").ok_or("Missing FEN")?; - let board = Board::from_fen_str(fen).map_err(|err| format!("{err}"))?; + let fen = matches + .get_one::("fen") + .ok_or(CommandHandlingError::MissingArgument("fen"))?; + let board = Board::from_fen_str(fen)?; state.position = Position::new(board); } Some((name, _matches)) => unimplemented!("{name}"), } - Ok(()) + Ok(CommandResult::default()) } fn main() -> Result<(), String> { diff --git a/moves/src/builder.rs b/moves/src/builder.rs index e75a511..b6a0794 100644 --- a/moves/src/builder.rs +++ b/moves/src/builder.rs @@ -4,15 +4,20 @@ use crate::{defs::Kind, Move, PromotionShape}; use chessfriend_board::{castle, en_passant::EnPassant}; use chessfriend_core::{Color, File, PlacedPiece, Rank, Square}; use std::result::Result as StdResult; +use thiserror::Error; pub type Result = std::result::Result; type EncodedMoveResult = std::result::Result; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Error, Eq, PartialEq)] pub enum Error { + #[error("no origin square")] MissingOriginSquare, + #[error("no target square")] MissingTargetSquare, + #[error("no capture square")] MissingCaptureSquare, + #[error("invalid en passant square")] InvalidEnPassantSquare, }