Skip to main content

sokoban_elements/saved_game/
errors.rs

1use std::error::Error;
2use std::fmt::{Display, Formatter};
3
4use peginator::ParseError;
5
6use super::Step;
7use crate::Tessellation;
8
9/// Variants of [`SavedGameSyntaxError`].
10#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
11pub enum SavedGameSyntaxErrorKind {
12    RleDecodingFailed,
13    ParsingFailed,
14    InvalidStepCharacter {
15        input:        char,
16        tessellation: Tessellation,
17    },
18    InvalidStepDirection {
19        input:        Step,
20        tessellation: Tessellation,
21        position:     usize,
22    },
23}
24
25#[derive(Debug)]
26struct ErrorPayload {
27    kind:         SavedGameSyntaxErrorKind,
28    source:       Option<Box<dyn Error>>,
29    parser_input: Option<String>,
30}
31
32/// [`std::Error`](std::error::Error) when parsing [`SavedGame`](crate::SavedGame).
33#[derive(Debug)]
34pub struct SavedGameSyntaxError(Box<ErrorPayload>);
35
36impl SavedGameSyntaxError {
37    pub fn kind(&self) -> &SavedGameSyntaxErrorKind { &self.0.kind }
38
39    pub fn new(kind: SavedGameSyntaxErrorKind) -> Self {
40        Self(Box::new(ErrorPayload {
41            kind,
42            source: None,
43            parser_input: None,
44        }))
45    }
46
47    pub fn new_with_source<E>(
48        kind: SavedGameSyntaxErrorKind, source: Option<E>, input: Option<&str>,
49    ) -> Self
50    where
51        E: Into<Box<dyn Error + Send + Sync>>,
52    {
53        Self(Box::new(ErrorPayload {
54            kind,
55            source: match source {
56                Some(e) => Some(e.into()),
57                None => None,
58            },
59            parser_input: input.map(std::convert::Into::into),
60        }))
61    }
62}
63
64impl Display for SavedGameSyntaxError {
65    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
66        match &self.0.kind {
67            SavedGameSyntaxErrorKind::RleDecodingFailed => {
68                write!(f, "failed to expand run length encoded SavedGame string")
69            }
70
71            SavedGameSyntaxErrorKind::ParsingFailed => {
72                let at_str = match &self.0.source {
73                    Some(ref_box_dyn) => {
74                        match ref_box_dyn.downcast_ref::<ParseError>() {
75                            Some(e) => format!("at {}", e.position),
76                            None => String::new(),
77                        }
78                    }
79                    None => String::new(),
80                };
81
82                let input_str = match &self.0.parser_input {
83                    Some(s) => format!("\"{s}\""),
84                    None => String::new(),
85                };
86
87                write!(
88                    f,
89                    "{}",
90                    ["failed parsing SavedGame string", &input_str, &at_str].join(" ")
91                )
92            }
93
94            SavedGameSyntaxErrorKind::InvalidStepCharacter {
95                input,
96                tessellation,
97            } => {
98                write!(
99                    f,
100                    "{tessellation:?} tessellation doesn't recognize '{input}' as valid SavedGame step character"
101                )
102            }
103
104            SavedGameSyntaxErrorKind::InvalidStepDirection {
105                input,
106                tessellation,
107                position,
108            } => {
109                write!(
110                    f,
111                    "pusher Step at {} with direction {:?} is invalid in context of {:?} game tessellation",
112                    position,
113                    input.direction(),
114                    tessellation
115                )
116            }
117        }
118    }
119}
120
121impl Error for SavedGameSyntaxError {
122    fn source(&self) -> Option<&(dyn Error + 'static)> {
123        match &self.0.source {
124            Some(ref_box_dyn) => Some(ref_box_dyn.as_ref()),
125            None => None,
126        }
127    }
128}