op : O,
}
trait ApiPieceOp : Debug {
- #[throws(GameError)]
+ #[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
lens: &dyn Lens /* used for LogEntry and PieceId but not Pos */)
-> (PieceUpdateOp<()>, Vec<LogEntry>);
}
+#[derive(Error,Debug)]
+enum ApiPieceOpError {
+ ReportViaResponse(#[from] OnlineError),
+ ReportViaUpdate(#[from] PieceOpError),
+}
+display_as_debug!(ApiPieceOpError);
+
pub trait Lens : Debug {
fn pieceid2visible(&self, piece: PieceId) -> VisiblePieceId;
fn log_pri(&self, piece: PieceId, pc: &PieceState)
eprintln!("Q_GEN={:?} U_GEN={:?}", u_gen, q_gen);
- if u_gen > q_gen { throw!(GameError::Conflict) }
+ if u_gen > q_gen { throw!(PieceOpError::Conflict) }
if pc.held != None && pc.held != Some(player) {
- throw!(GameError::PieceHeld)
+ throw!(OnlineError::PieceHeld)
};
let (update, logents) = form.op.op(gs,player,piece,&lens)?;
- Ok((update, logents))
+ Ok::<_,ApiPieceOpError>((update, logents))
})() {
- Err(err) => {
- let err : GameError = err;
- if let GameError::InternalErrorSVG(svg) = err { throw!(svg) }
- eprintln!("API {:?} => {:?}", &form, &err);
- // Restating the state of this piece (with a new generation)
- // will forcibly synchronise the client which made the failing
- // request.
- let mut buf = PrepareUpdatesBuffer::new(g, None, None);
- buf.piece_update(piece, PieceUpdateOp::Modify(()), &lens);
- throw!(err);
- },
+ Err(err) => err.report(&mut ig, piece, player, client, &lens)?,
+
Ok((update, logents)) => {
let mut buf = PrepareUpdatesBuffer::new(g, Some((client, form.cseq)),
Some(1 + logents.len()));
""
}
+impl ApiPieceOpError {
+ pub fn report(self, ig: &mut InstanceGuard,
+ piece: PieceId, player: PlayerId, client: ClientId,
+ lens: &dyn Lens) -> Result<(),OE> {
+ use ApiPieceOpError::*;
+
+ match self {
+ ReportViaUpdate(poe) => {
+ let gen = ig.gs.gen;
+ ig.updates.get_mut(player)
+ .ok_or(OE::NoPlayer)?
+ .push(Arc::new(PreparedUpdate {
+ gen,
+ us : vec![ PreparedUpdateEntry::Error(
+ Some(client),
+ ErrorSignaledViaUpdate::PieceOpError(piece, poe),
+ )],
+ }));
+
+ let mut buf = PrepareUpdatesBuffer::new(ig, None, None);
+ buf.piece_update(piece, PieceUpdateOp::Modify(()), lens);
+ },
+
+ ReportViaResponse(err) => {
+ eprintln!("API ERROR => {:?}", &err);
+ Err(err)?;
+ },
+ }
+ Ok(())
+ }
+}
+
#[derive(Debug,Serialize,Deserialize)]
struct ApiPieceGrab {
}
api_piece_op(form)
}
impl ApiPieceOp for ApiPieceGrab {
- #[throws(GameError)]
+ #[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
lens: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
let pl = gs.players.byid(player).unwrap();
let pc = gs.pieces.byid_mut(piece).unwrap();
- if pc.held.is_some() { throw!(GameError::PieceHeld) }
+ if pc.held.is_some() { throw!(OnlineError::PieceHeld) }
pc.held = Some(player);
let update = PieceUpdateOp::Modify(());
api_piece_op(form)
}
impl ApiPieceOp for ApiPieceUngrab {
- #[throws(GameError)]
+ #[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, player: PlayerId, piece: PieceId,
lens: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
let pl = gs.players.byid(player).unwrap();
let pc = gs.pieces.byid_mut(piece).unwrap();
- if pc.held != Some(player) { throw!(GameError::PieceHeld) }
+ if pc.held != Some(player) { throw!(OnlineError::PieceHeld) }
pc.held = None;
let update = PieceUpdateOp::Modify(());
api_piece_op(form)
}
impl ApiPieceOp for ApiPieceRaise {
- #[throws(GameError)]
+ #[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
_: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
api_piece_op(form)
}
impl ApiPieceOp for ApiPieceMove {
- #[throws(GameError)]
+ #[throws(ApiPieceOpError)]
fn op(&self, gs: &mut GameState, _: PlayerId, piece: PieceId,
_lens: &dyn Lens)
-> (PieceUpdateOp<()>, Vec<LogEntry>) {
let pc = gs.pieces.byid_mut(piece).unwrap();
if let (_,true) = self.0.clamped(gs.table_size) {
- throw!(GameError::PosOffTable);
+ Err(ApiPieceOpError::ReportViaUpdate(PieceOpError::PosOffTable))?;
}
pc.pos = self.0;
let update = PieceUpdateOp::Move(self.0);
PlayerNotFound,
PieceNotFound,
LimitExceeded,
- SVGProcessingFailed(#[from] SVGProcessingError),
- GameError(#[from] GameError),
ServerFailure(String),
+ BadSpec(#[from] SpecError),
}
impl Display for MgmtError {
#[throws(fmt::Error)]
}
}
-impl From<ServerFailure> for MgmtError {
- fn from(e: ServerFailure) -> MgmtError {
+impl From<InternalError> for MgmtError {
+ fn from(e: InternalError) -> MgmtError {
MgmtError::ServerFailure(format!("ServerFailure {}\n", &e))
}
}
use crate::imports::*;
-#[derive(Error,Clone,Debug,Serialize,Deserialize)]
-#[error("operation error {:?}",self)]
-pub enum GameError {
- Conflict,
- PieceGone,
- PieceHeld,
- FaceNotFound,
- PosOffTable,
- InternalErrorSVG(#[from] SVGProcessingError),
-}
-
#[derive(Error,Debug)]
pub enum OnlineError {
#[error("Game in process of being destroyed")]
GameBeingDestroyed,
- #[error("Game corrupted by previous crash - consult administrator")]
- GameCorrupted,
#[error("client session not recognised (terminated by server?)")]
NoClient,
#[error("player not part of game (removed?)")]
NoPlayer,
- #[error("game operation error")]
- GameError(#[from] GameError),
#[error("invalid Z coordinate")]
InvalidZCoord,
- #[error("JSON~ serialisation error: {0:?}")]
- JSONSerializeFailed(#[from] serde_json::error::Error),
- #[error("SVG processing/generation error {0:?}")]
- SVGProcessingFailed(#[from] SVGProcessingError),
- #[error("Server operational problems: {0:?}")]
- ServerFailure(#[from] ServerFailure),
+ #[error("improper piece hold status for op (client should have known)")]
+ PieceHeld,
+ #[error("Server operational problems - consult administrator: {0:?}")]
+ ServerFailure(#[from] InternalError),
+ #[error("JSON deserialisation error: {0:?}")]
+ BadJSON(serde_json::Error),
}
from_instance_lock_error!{OnlineError}
#[derive(Error,Debug)]
-pub enum ServerFailure {
+pub enum InternalError {
+ #[error("Game corrupted by previous crash")]
+ GameCorrupted,
#[error("Server MessagePack encoding error {0}")]
MessagePackEncodeFail(#[from] rmp_serde::encode::Error),
#[error("Server MessagePack decoding error (game load failed) {0}")]
MessagePackDecodeFail(#[from] rmp_serde::decode::Error),
#[error("Server internal logic error {0}")]
InternalLogicError(String),
+ #[error("SVG processing/generation error {0:?}")]
+ SVGProcessingFailed(#[from] SVGProcessingError),
+ #[error("String formatting error {0}")]
+ StringFormatting(#[from] fmt::Error),
+ #[error("JSON deserialisation error: {0:?}")]
+ JSONEncode(serde_json::Error),
#[error("Server error {0:?}")]
Anyhow(#[from] anyhow::Error),
}
+impl From<InternalError> for SpecError {
+ fn from(ie: InternalError) -> SpecError {
+ SpecError::InternalError(format!("{:?}",ie))
+ }
+}
+
#[derive(Error,Debug,Serialize,Copy,Clone)]
pub enum ErrorSignaledViaUpdate {
- RenderingError,
+ InternalError,
PlayerRemoved,
+ PieceOpError(PieceId, PieceOpError),
}
display_as_debug!{ErrorSignaledViaUpdate}
+#[derive(Error,Debug,Serialize,Copy,Clone)]
+pub enum PieceOpError {
+ Gone,
+ Conflict,
+ PosOffTable,
+}
+display_as_debug!{PieceOpError}
+
pub type StartupError = anyhow::Error;
pub use OnlineError::{NoClient,NoPlayer};
fn from(e: InstanceLockError) -> $into {
use InstanceLockError::*;
match e {
- GameCorrupted => $into::GameCorrupted,
GameBeingDestroyed => $into::GameBeingDestroyed,
+ GameCorrupted => InternalError::GameCorrupted.into(),
}
}
}
}
impl IdForById for PieceId {
- type Error = GameError;
- const ERROR : GameError = GameError::PieceGone;
+ type Error = PieceOpError;
+ const ERROR : PieceOpError = PieceOpError::Gone;
}
#[macro_export]
macro_rules! display_as_debug {
{$x:ty} => {
- impl Display for $x {
- fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
- <Self as Debug>::fmt(self, f)
+ impl std::fmt::Display for $x {
+ #[throws(std::fmt::Error)]
+ fn fmt(&self, f: &mut std::fmt::Formatter) {
+ <Self as Debug>::fmt(self, f)?
}
}
}
}
}
pub use crate::error_from_losedetails;
+
+impl From<SVGProcessingError> for SpecError {
+ fn from(se: SVGProcessingError) -> SpecError {
+ InternalError::SVGProcessingFailed(se).into()
+ }
+}
// ---------- piece trait, and rendering ----------
+type IE = InternalError;
+type IR = Result<(),IE>;
type SE = SVGProcessingError;
-type SR = Result<(),SE>;
#[typetag::serde]
pub trait Piece : Send + Debug {
// #[throws] doesn't work here for some reason
- fn svg_piece(&self, f: &mut String, pri: &PieceRenderInstructions) -> SR;
+ fn svg_piece(&self, f: &mut String, pri: &PieceRenderInstructions) -> IR;
- #[throws(SE)]
+ #[throws(IE)]
fn surround_path(&self, pri : &PieceRenderInstructions) -> String;
- fn svg_x_defs(&self, f: &mut String, pri : &PieceRenderInstructions) -> SR;
+ fn svg_x_defs(&self, f: &mut String, pri : &PieceRenderInstructions) -> IR;
- #[throws(SE)]
+ #[throws(IE)]
fn thresh_dragraise(&self, pri : &PieceRenderInstructions)
-> Option<Coord>;
#[typetag::serde(tag="type")]
pub trait PieceSpec : Debug {
- fn load(&self) -> Result<Box<dyn Piece>,SE>;
+ fn load(&self) -> Result<Box<dyn Piece>,SpecError>;
fn resolve_spec_face(&self, face : Option<FaceId>)
- -> Result<FaceId,GameError>;
+ -> Result<FaceId,SpecError>;
}
// ========== implementations ==========
// ---------- game state - rendering etc. ----------
impl PieceState {
- #[throws(SE)]
+ #[throws(IE)]
pub fn make_defs(&self, pri : &PieceRenderInstructions) -> String {
let pr = self;
let mut defs = String::new();
defs
}
- #[throws(SE)]
+ #[throws(IE)]
pub fn prep_piecestate(&self, pri : &PieceRenderInstructions)
-> PreparedPieceState {
PreparedPieceState {
.clone()
}
- #[throws(ServerFailure)]
+ #[throws(InternalError)]
pub fn destroy_game(mut g: InstanceGuard) {
let a_savefile = savefilename(&g.name, "a-", "");
// #[throws(ServerFailure)]
// https://github.com/withoutboats/fehler/issues/62
pub fn player_remove(&mut self, oldplayer: PlayerId)
- -> Result<Option<PlayerState>,ServerFailure> {
+ -> Result<Option<PlayerState>,InternalError> {
// We have to filter this player out of everything
// Then save
// Then send updates
updates. push(PreparedUpdate {
gen: self.c.g.gs.gen,
us : vec![ PreparedUpdateEntry::Error(
+ None,
ErrorSignaledViaUpdate::PlayerRemoved
)],
});
}
impl InstanceGuard<'_> {
- #[throws(ServerFailure)]
+ #[throws(InternalError)]
fn save_something(
&self, prefix: &str,
w: fn(s: &Self, w: &mut BufWriter<fs::File>)
eprintln!("saved to {}", &out);
}
- #[throws(ServerFailure)]
+ #[throws(InternalError)]
pub fn save_game_now(&mut self) {
self.save_something("g-", |s,w| {
rmp_serde::encode::write_named(w, &s.c.g.gs)
self.c.game_dirty = false;
}
- #[throws(ServerFailure)]
+ #[throws(InternalError)]
fn save_access_now(&mut self) {
self.save_something("a-", |s,w| {
let tokens_players : Vec<(&str, PlayerId)> = {
})?;
}
- #[throws(ServerFailure)]
+ #[throws(InternalError)]
fn load_something<T:DeserializeOwned>(name: &InstanceName, prefix: &str)
-> T {
let inp = savefilename(name, prefix, "");
let mut access_load : InstanceSaveAccesses<String>
= Self::load_something(&name, "a-")
.or_else(|e| {
- if let ServerFailure::Anyhow(ae) = &e {
+ if let InternalError::Anyhow(ae) = &e {
if let Some(ioe) = ae.downcast_ref::<io::Error>() {
if ioe.kind() == io::ErrorKind::NotFound {
return Ok(Default::default())
use rocket::http::Status;
use OnlineError::*;
let status = match self {
- GameCorrupted | JSONSerializeFailed(_) | SVGProcessingFailed(_)
- | ServerFailure(_)
- => Status::InternalServerError,
+ ServerFailure(_) => Status::InternalServerError,
NoClient | NoPlayer | GameBeingDestroyed => Status::NotFound,
- InvalidZCoord | OnlineError::GameError(_) => Status::BadRequest,
+ InvalidZCoord | BadJSON(_) | OnlineError::PieceHeld
+ => Status::BadRequest,
};
let mut resp = Responder::respond_to(msg,req).unwrap();
resp.set_status(status);
BadNumber,
WriteFail,
NegativeDragraise,
- ImproperSizeSpec,
- UnsupportedColourSpec,
}
display_as_debug!{SVGProcessingError}
error_from_losedetails!{SVGProcessingError,WriteFail,fmt::Error}
error_from_losedetails!{SVGProcessingError,BadNumber,std::num::ParseFloatError}
+impl From<SVGProcessingError> for MgmtError {
+ fn from(se: SVGProcessingError) -> MgmtError { se.into() }
+}
+
+type IE = InternalError;
type SE = SVGProcessingError;
#[throws(SE)]
#[typetag::serde]
impl Piece for SimpleShape {
- #[throws(SE)]
+ #[throws(IE)]
fn svg_piece(&self, f: &mut String, pri: &PieceRenderInstructions) {
write!(f, r##"<path fill="{}" d="{}"/>"##,
self.colours[pri.face],
&self.path)?;
}
- #[throws(SE)]
+ #[throws(IE)]
fn surround_path(&self, _pri : &PieceRenderInstructions) -> String {
self.scaled_path.clone()
}
- #[throws(SE)]
+ #[throws(IE)]
fn thresh_dragraise(&self, _pri : &PieceRenderInstructions)
-> Option<Coord> {
Some(self.approx_dia / 2)
}
- #[throws(SE)]
+ #[throws(IE)]
fn svg_x_defs(&self, _f: &mut String, _pri : &PieceRenderInstructions) {
}
fn describe_html(&self, face : Option<FaceId>) -> String {
impl SimpleShape {
fn new_from_path(desc: String, path: String, approx_dia: Coord,
faces: &IndexVec<FaceId,ColourSpec>)
- -> Result<Box<dyn Piece>,SE> {
+ -> Result<Box<dyn Piece>,SpecError> {
let scaled_path = svg_rescale_path(&path, SELECT_SCALE)?;
let colours = faces
.iter()
.map(|s| s.try_into())
- .collect::<Result<_,SE>>()?;
+ .collect::<Result<_,SpecError>>()?;
Ok(Box::new(SimpleShape {
scaled_path, desc, approx_dia, path, colours,
}))
}
}
-#[throws(GameError)]
+#[throws(SpecError)]
fn simple_resolve_spec_face(faces: &IndexSlice<FaceId,[ColourSpec]>,
face: Option<FaceId>)
-> FaceId {
let face = face.unwrap_or_default();
- faces.get(face).ok_or(GameError::FaceNotFound)?;
+ faces.get(face).ok_or(SpecError::FaceNotFound)?;
face
}
#[typetag::serde]
impl PieceSpec for piece_specs::Disc {
- #[throws(SE)]
+ #[throws(SpecError)]
fn load(&self) -> Box<dyn Piece> {
let unit_path =
"M 0 1 a 1 1 0 1 0 0 -2 \
SimpleShape::new_from_path("circle".to_owned(), path, self.diam,
&self.faces)?
}
- #[throws(GameError)]
+ #[throws(SpecError)]
fn resolve_spec_face(&self, face: Option<FaceId>) -> FaceId {
simple_resolve_spec_face(&self.faces, face)?
}
#[typetag::serde]
impl PieceSpec for piece_specs::Square {
- #[throws(SE)]
+ #[throws(SpecError)]
fn load(&self) -> Box<dyn Piece> {
let (x, y) = match *self.size.as_slice() {
[s,] => (s,s),
[x, y] => (x,y),
- _ => throw!(SE::ImproperSizeSpec),
+ _ => throw!(SpecError::ImproperSizeSpec),
};
let path = format!("M {} {} h {} v {} h {} z",
-(x as f64)*0.5, -(y as f64)*0.5, x, y, -x);
SimpleShape::new_from_path("square".to_owned(), path, (x+y+1)/2,
&self.faces)?
}
- #[throws(GameError)]
+ #[throws(SpecError)]
fn resolve_spec_face(&self, face: Option<FaceId>) -> FaceId {
simple_resolve_spec_face(&self.faces, face)?
}
let for_piece = SessionPieceContext {
id: pri.id,
pos : pr.pos,
- info : serde_json::to_string(&for_info)?,
+ info : serde_json::to_string(&for_info)
+ .map_err(|e| InternalError::JSONEncode(e))?,
};
uses.push(for_piece);
}
nick : pl.nick.clone(),
load : serde_json::to_string(&DataLoad {
players : load_players,
- })?,
+ }).map_err(|e| InternalError::JSONEncode(e))?,
};
eprintln!("SRC {:?}", &src);
src
use crate::gamestate::PieceSpec;
use std::fmt::Debug;
use implementation::PlayerAccessSpec;
+use thiserror::Error;
+use crate::error::display_as_debug;
//---------- common types ----------
#[repr(transparent)]
pub struct ColourSpec(String);
+#[derive(Error,Clone,Serialize,Deserialize,Debug)]
+pub enum SpecError {
+ ImproperSizeSpec,
+ UnsupportedColourSpec,
+ FaceNotFound,
+ InternalError(String),
+}
+display_as_debug!{SpecError}
+
//---------- Table TOML file ----------
#[derive(Debug,Serialize,Deserialize)]
use super::*;
use crate::imports::*;
type Insn = crate::commands::MgmtGameInstruction;
- type SE = SVGProcessingError;
#[typetag::serde(tag="access")]
pub trait PlayerAccessSpec : Debug {
}
impl TryFrom<&ColourSpec> for Colour {
- type Error = SE;
- #[throws(SE)]
+ type Error = SpecError;
+ #[throws(SpecError)]
fn try_from(spec: &ColourSpec) -> Colour {
lazy_static! {
static ref RE: Regex = Regex::new(concat!(
}
let s = &spec.0;
if !RE.is_match(s) {
- throw!(SVGProcessingError::UnsupportedColourSpec);
+ throw!(SpecError::UnsupportedColourSpec);
}
spec.0.clone()
}
},
SetTableSize(Pos),
Log (Arc<LogEntry>),
- Error (ErrorSignaledViaUpdate),
+ Error (Option<ClientId> /* none: all */, ErrorSignaledViaUpdate),
}
#[derive(Debug,Serialize)]
logent.html.as_bytes().len() * 3
},
SetTableSize(_) |
- Error(_) => {
+ Error(_,_) => {
100
},
}
}
}
- #[throws(SVGProcessingError)]
+ #[throws(InternalError)]
fn piece_update_fallible(&mut self, piece: PieceId,
update: PieceUpdateOp<()>,
lens: &dyn Lens) -> PreparedUpdateEntry {
|_|{
let mut ns = pc.prep_piecestate(&pri_for_all)?;
lens.massage_prep_piecestate(&mut ns);
- <Result<_,SVGProcessingError>>::Ok(ns)
+ <Result<_,InternalError>>::Ok(ns)
},
)?;
(update, pri_for_all.id)
},
- Err(GameError::PieceGone) => {
+ Err(PieceOpError::Gone) => {
(PieceUpdateOp::Delete(), lens.pieceid2visible(piece))
}
Err(e) => {
.unwrap_or_else(|e| {
eprintln!("piece update error! piece={:?} lens={:?} error={:?}",
piece, &lens, &e);
- PreparedUpdateEntry::Error(ErrorSignaledViaUpdate::RenderingError)
+ PreparedUpdateEntry::Error(None,
+ ErrorSignaledViaUpdate::InternalError)
});
self.us.push(update);
}
&PreparedUpdateEntry::SetTableSize(size) => {
TransmitUpdateEntry::SetTableSize(size)
},
- &PreparedUpdateEntry::Error(e) => {
+ &PreparedUpdateEntry::Error(c, e) => {
+ if let Some(c) = c { if c != dest { continue } }
TransmitUpdateEntry::Error(e)
}
};