[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:
parent
72eeba84ba
commit
9010f1e9c2
12 changed files with 331 additions and 226 deletions
|
@ -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"
|
||||
|
|
|
@ -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> {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue