use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::rle::RleSyntaxError;
use crate::utilities::is_blank;
use crate::{BoardSyntaxError, SavedGameSyntaxError, SokobanPlusSyntaxError, Tessellation};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IoReadErrorKind {
BoardSyntaxError { ln: usize },
SavedGameSyntaxError { ln: usize },
SokobanPlusSyntaxError { ln: usize },
InvalidFileExtension { ext: String },
UnknownTessellationName { ln: usize, name: String },
XMLSyntaxError { ln: usize },
JsonSyntaxError { ln: usize },
IoError,
}
#[derive(Debug)]
struct IoReadErrorPayload {
kind: IoReadErrorKind,
source: Option<Box<dyn Error>>,
msg: Option<String>,
}
#[derive(Debug)]
pub struct IoReadError(Box<IoReadErrorPayload>);
impl IoReadError {
pub fn kind(&self) -> &IoReadErrorKind { &self.0.kind }
pub(in crate::io) fn new_xml_syntax_error(
ln: usize, source: Option<Box<dyn Error>>, msg: String,
) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::XMLSyntaxError { ln },
source,
msg: Some(msg),
}))
}
pub(in crate::io) fn new_puzzle_syntax_error(ln: usize, source: BoardSyntaxError) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::BoardSyntaxError { ln },
source: Some(Box::new(source)),
msg: None,
}))
}
pub(in crate::io) fn new_saved_game_syntax_error(
ln: usize, source: SavedGameSyntaxError,
) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::SavedGameSyntaxError { ln },
source: Some(Box::new(source)),
msg: None,
}))
}
pub(in crate::io) fn new_sokoban_plus_syntax_error(
ln: usize, source: SokobanPlusSyntaxError,
) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::SokobanPlusSyntaxError { ln },
source: Some(Box::new(source)),
msg: None,
}))
}
#[cfg(feature = "json")]
pub(in crate::io) fn new_json_syntax_error(source: serde_json::Error) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::JsonSyntaxError { ln: source.line() },
msg: Some(source.to_string()),
source: Some(Box::new(source)),
}))
}
pub(in crate::io) fn new_invalid_extension_error(ext: &str) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::InvalidFileExtension { ext: ext.into() },
source: None,
msg: None,
}))
}
pub(in crate::io) fn new_unknown_tessellation_error(ln: usize, name: &str) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::UnknownTessellationName {
ln,
name: name.into(),
},
source: None,
msg: None,
}))
}
}
impl Display for IoReadError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.0.kind {
IoReadErrorKind::BoardSyntaxError { ln } => {
write!(f, "syntax error at line {ln} of Puzzle data")
}
IoReadErrorKind::SavedGameSyntaxError { ln } => {
write!(f, "syntax error at line {ln} of SavedGame data")
}
IoReadErrorKind::SokobanPlusSyntaxError { ln } => {
write!(f, "syntax error at line {ln} of SokobanPlus data")
}
IoReadErrorKind::InvalidFileExtension { ext } => {
write!(
f,
"file extension \".{ext}\" can't be matched to any known FileFormat"
)
}
IoReadErrorKind::UnknownTessellationName { ln, name } => {
write!(
f,
"at line {ln}, tessellation name: \"{name}\" can't be matched to any known Tessellation"
)
}
IoReadErrorKind::XMLSyntaxError { ln } => {
let msg = match &self.0.msg {
Some(v) => {
if is_blank(v) {
String::new()
} else {
format!(": {v}")
}
}
None => String::new(),
};
write!(f, "XML syntax error at line {ln} while reading Sokoban data{msg}")
}
IoReadErrorKind::JsonSyntaxError { .. } => {
let msg = match &self.0.msg {
Some(v) if !is_blank(v) => format!(": {v}"),
_ => String::new(),
};
write!(f, "JSON syntax error while reading Sokoban data{msg}")
}
IoReadErrorKind::IoError => {
write!(f, "I/O error while reading Sokoban data")
}
}
}
}
impl Error for IoReadError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.0.source {
Some(ref_box_dyn) => Some(ref_box_dyn.as_ref()),
None => None,
}
}
}
impl From<std::io::Error> for IoReadError {
fn from(value: std::io::Error) -> Self {
Self(Box::new(IoReadErrorPayload {
kind: IoReadErrorKind::IoError,
source: Some(Box::new(value)),
msg: None,
}))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IoWriteErrorKind {
RleEncodingFailed,
IoError,
VariantNotAllowedByFileFormat { variant: Tessellation },
InvalidFileExtension { ext: String },
}
#[derive(Debug)]
struct IoWriteErrorPayload {
kind: IoWriteErrorKind,
puzzle_idx: usize,
source: Option<Box<dyn Error>>,
}
#[derive(Debug)]
pub struct IoWriteError(Box<IoWriteErrorPayload>);
impl IoWriteError {
pub fn puzzle_idx(&self) -> usize { self.0.puzzle_idx }
pub(in crate::io) fn new_io_error(source: std::io::Error, puzzle_idx: usize) -> Self {
Self(Box::new(IoWriteErrorPayload {
kind: IoWriteErrorKind::IoError,
puzzle_idx,
source: Some(Box::new(source)),
}))
}
pub(in crate::io) fn new_rle_encoding_error(source: RleSyntaxError, puzzle_idx: usize) -> Self {
Self(Box::new(IoWriteErrorPayload {
kind: IoWriteErrorKind::RleEncodingFailed,
puzzle_idx,
source: Some(Box::new(source)),
}))
}
pub(in crate::io) fn new_variant_not_allowed_error(
puzzle_idx: usize, variant: Tessellation,
) -> Self {
Self(Box::new(IoWriteErrorPayload {
kind: IoWriteErrorKind::VariantNotAllowedByFileFormat { variant },
puzzle_idx,
source: None,
}))
}
pub(in crate::io) fn new_invalid_extension_error(ext: &str) -> Self {
Self(Box::new(IoWriteErrorPayload {
kind: IoWriteErrorKind::InvalidFileExtension { ext: ext.into() },
puzzle_idx: 0,
source: None,
}))
}
}
impl Display for IoWriteError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match &self.0.kind {
IoWriteErrorKind::IoError => {
write!(f, "I/O error while writing Sokoban data")
}
IoWriteErrorKind::RleEncodingFailed => {
write!(f, "failed to RLE encode Sokoban data before writing it")
}
IoWriteErrorKind::VariantNotAllowedByFileFormat { variant } => {
write!(
f,
"writing \"{variant:?}\" game variant into this file format is not allowed"
)
}
IoWriteErrorKind::InvalidFileExtension { ext } => {
write!(
f,
"writing Sokoban data to file with extension \".{ext}\" is not supported"
)
}
}
}
}
impl Error for IoWriteError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.0.source {
Some(ref_box_dyn) => Some(ref_box_dyn.as_ref()),
None => None,
}
}
}
impl From<std::io::Error> for IoWriteError {
fn from(value: std::io::Error) -> Self { Self::new_io_error(value, 0) }
}