sokoban-elements 0.2.1

Parsing and writing Sokoban, Hexoban, Trioban and Octoban file formats.
Documentation
use std::error::Error;
use std::fmt::{Display, Formatter};

use peginator::ParseError;

use super::Step;
use crate::Tessellation;

/// Variants of [`SavedGameSyntaxError`].
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum SavedGameSyntaxErrorKind {
    RleDecodingFailed,
    ParsingFailed,
    InvalidStepCharacter {
        input:        char,
        tessellation: Tessellation,
    },
    InvalidStepDirection {
        input:        Step,
        tessellation: Tessellation,
        position:     usize,
    },
}

#[derive(Debug)]
struct ErrorPayload {
    kind:         SavedGameSyntaxErrorKind,
    source:       Option<Box<dyn Error>>,
    parser_input: Option<String>,
}

/// [`std::Error`](std::error::Error) when parsing [`SavedGame`](crate::SavedGame).
#[derive(Debug)]
pub struct SavedGameSyntaxError(Box<ErrorPayload>);

impl SavedGameSyntaxError {
    pub fn kind(&self) -> &SavedGameSyntaxErrorKind { &self.0.kind }

    pub fn new(kind: SavedGameSyntaxErrorKind) -> Self {
        Self(Box::new(ErrorPayload {
            kind,
            source: None,
            parser_input: None,
        }))
    }

    pub fn new_with_source<E>(
        kind: SavedGameSyntaxErrorKind, source: Option<E>, input: Option<&str>,
    ) -> Self
    where
        E: Into<Box<dyn Error + Send + Sync>>,
    {
        Self(Box::new(ErrorPayload {
            kind,
            source: match source {
                Some(e) => Some(e.into()),
                None => None,
            },
            parser_input: input.map(std::convert::Into::into),
        }))
    }
}

impl Display for SavedGameSyntaxError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match &self.0.kind {
            SavedGameSyntaxErrorKind::RleDecodingFailed => {
                write!(f, "failed to expand run length encoded SavedGame string")
            }

            SavedGameSyntaxErrorKind::ParsingFailed => {
                let at_str = match &self.0.source {
                    Some(ref_box_dyn) => {
                        match ref_box_dyn.downcast_ref::<ParseError>() {
                            Some(e) => format!("at {}", e.position),
                            None => String::new(),
                        }
                    }
                    None => String::new(),
                };

                let input_str = match &self.0.parser_input {
                    Some(s) => format!("\"{s}\""),
                    None => String::new(),
                };

                write!(
                    f,
                    "{}",
                    ["failed parsing SavedGame string", &input_str, &at_str].join(" ")
                )
            }

            SavedGameSyntaxErrorKind::InvalidStepCharacter {
                input,
                tessellation,
            } => {
                write!(
                    f,
                    "{tessellation:?} tessellation doesn't recognize '{input}' as valid SavedGame step character"
                )
            }

            SavedGameSyntaxErrorKind::InvalidStepDirection {
                input,
                tessellation,
                position,
            } => {
                write!(
                    f,
                    "pusher Step at {} with direction {:?} is invalid in context of {:?} game tessellation",
                    position,
                    input.direction(),
                    tessellation
                )
            }
        }
    }
}

impl Error for SavedGameSyntaxError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match &self.0.source {
            Some(ref_box_dyn) => Some(ref_box_dyn.as_ref()),
            None => None,
        }
    }
}