[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.
This commit is contained in:
Eryn Wells 2025-05-19 14:18:31 -07:00
parent 72eeba84ba
commit 9010f1e9c2
12 changed files with 331 additions and 226 deletions

View file

@ -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"

View file

@ -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<CommandResult, String> {
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<CommandResult> {
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<CommandResult, String> {
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::<String>("from").ok_or("Missing square")?,
)
.map_err(|_| "Error: invalid square specifier")?;
matches
.get_one::<String>("from")
.ok_or(CommandHandlingError::MissingArgument("from"))?,
)?;
let to_square = Square::from_algebraic_str(
matches.get_one::<String>("to").ok_or("Missing square")?,
)
.map_err(|_| "Error: invalid square specifier")?;
matches
.get_one::<String>("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::<String>("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::<Color>()?;
let shape = matches
.get_one::<String>("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::<Shape>()?;
let square = matches
.get_one::<String>("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::<String>("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::<Square>()?;
let sight = state.position.sight(square);
@ -167,9 +160,8 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
Some(("moves", matches)) => {
let square = matches
.get_one::<String>("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::<Square>()?;
let movement = state.position.movement(square);
@ -182,7 +174,7 @@ fn respond(line: &str, state: &mut State) -> Result<CommandResult, String> {
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<CommandResult, String> {
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<CommandResult> {
match matches.subcommand() {
None | Some(("clear", _)) => state.position = Position::empty(),
Some(("starting", _)) => state.position = Position::starting(),
Some(("fen", matches)) => {
let fen = matches.get_one::<String>("fen").ok_or("Missing FEN")?;
let board = Board::from_fen_str(fen).map_err(|err| format!("{err}"))?;
let fen = matches
.get_one::<String>("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> {