use std::{error::Error as StdError, fmt, io};
#[derive(Debug, Clone, PartialEq)]
pub enum PuzWarning {
SkippedExtension { section: String, reason: String },
EncodingIssue { context: String, recovered: bool },
DataRecovery { field: String, issue: String },
ScrambledPuzzle { version: String },
}
#[derive(Debug)]
pub struct ParseResult<T> {
pub result: T,
pub warnings: Vec<PuzWarning>,
}
impl<T> ParseResult<T> {
pub fn new(result: T) -> Self {
Self {
result,
warnings: Vec::new(),
}
}
pub fn with_warnings(result: T, warnings: Vec<PuzWarning>) -> Self {
Self { result, warnings }
}
pub fn add_warning(&mut self, warning: PuzWarning) {
self.warnings.push(warning);
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PuzError {
InvalidMagic { found: Vec<u8> },
InvalidChecksum {
expected: u16,
found: u16,
context: String,
},
InvalidDimensions { width: u8, height: u8 },
InvalidClueCount { expected: u16, found: usize },
SectionSizeMismatch {
section: String,
expected: usize,
found: usize,
},
ParseError {
message: String,
position: Option<u64>,
context: String,
},
IoError {
message: String,
kind: io::ErrorKind,
position: Option<u64>,
},
InvalidUtf8 {
message: String,
position: Option<u64>,
},
MissingData {
field: String,
position: Option<u64>,
},
UnsupportedVersion { version: String },
InvalidGrid { reason: String },
InvalidClues { reason: String },
}
impl fmt::Display for PuzError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PuzError::InvalidMagic { found } => {
write!(f, "Invalid .puz file magic header. Expected 'ACROSS&DOWN\\0', found: {found:?}. This file may be corrupted or not a .puz file.")
}
PuzError::InvalidChecksum {
expected,
found,
context,
} => {
write!(f, "Checksum validation failed in {context}: expected 0x{expected:04X}, found 0x{found:04X}. The file may be corrupted.")
}
PuzError::InvalidDimensions { width, height } => {
write!(
f,
"Invalid puzzle dimensions: {width}x{height}. Dimensions must be between 1 and 255."
)
}
PuzError::InvalidClueCount { expected, found } => {
write!(
f,
"Clue count mismatch: expected {expected} clues, found {found}. The file may be corrupted."
)
}
PuzError::SectionSizeMismatch {
section,
expected,
found,
} => {
write!(f, "Extension section '{section}' size mismatch: expected {expected} bytes, found {found}. The section may be corrupted.")
}
PuzError::ParseError {
message,
position,
context,
} => match position {
Some(pos) => write!(f, "Parse error at position {pos}: {message} ({context})"),
None => write!(f, "Parse error: {message} ({context})"),
},
PuzError::IoError {
message,
kind,
position,
} => match position {
Some(pos) => write!(f, "I/O error at position {pos}: {message} ({kind:?})"),
None => write!(f, "I/O error: {message} ({kind:?})"),
},
PuzError::InvalidUtf8 { message, position } => match position {
Some(pos) => write!(f, "Invalid UTF-8 data at position {pos}: {message}"),
None => write!(f, "Invalid UTF-8 data: {message}"),
},
PuzError::MissingData { field, position } => match position {
Some(pos) => write!(f, "Missing required data '{field}' at position {pos}"),
None => write!(f, "Missing required data: {field}"),
},
PuzError::UnsupportedVersion { version } => {
write!(
f,
"Unsupported .puz file version: '{version}'. Only standard versions are supported."
)
}
PuzError::InvalidGrid { reason } => {
write!(f, "Invalid puzzle grid: {reason}")
}
PuzError::InvalidClues { reason } => {
write!(f, "Invalid clues: {reason}")
}
}
}
}
impl StdError for PuzError {}
impl From<io::Error> for PuzError {
fn from(error: io::Error) -> Self {
PuzError::IoError {
message: format!("I/O operation failed: {error}"),
kind: error.kind(),
position: None,
}
}
}
impl From<std::str::Utf8Error> for PuzError {
fn from(error: std::str::Utf8Error) -> Self {
PuzError::InvalidUtf8 {
message: format!("UTF-8 decoding failed: {error}"),
position: None,
}
}
}
impl PuzError {
pub fn with_position(mut self, position: u64) -> Self {
match &mut self {
PuzError::IoError { position: pos, .. } => *pos = Some(position),
PuzError::InvalidUtf8 { position: pos, .. } => *pos = Some(position),
PuzError::MissingData { position: pos, .. } => *pos = Some(position),
PuzError::ParseError { position: pos, .. } => *pos = Some(position),
_ => {} }
self
}
pub fn with_context(self, context: &str) -> Self {
match self {
PuzError::IoError {
message,
kind,
position,
} => PuzError::IoError {
message: format!("{context}: {message}"),
kind,
position,
},
PuzError::InvalidUtf8 { message, position } => PuzError::InvalidUtf8 {
message: format!("{context}: {message}"),
position,
},
PuzError::ParseError {
message,
position,
context: existing_context,
} => PuzError::ParseError {
message,
position,
context: format!("{context}: {existing_context}"),
},
other => other, }
}
}
impl fmt::Display for PuzWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PuzWarning::SkippedExtension { section, reason } => {
write!(f, "Skipped extension section '{section}': {reason}")
}
PuzWarning::EncodingIssue { context, recovered } => {
write!(
f,
"Encoding issue in {}: {}",
context,
if *recovered {
"recovered using fallback"
} else {
"could not recover"
}
)
}
PuzWarning::DataRecovery { field, issue } => {
write!(f, "Data recovery for '{field}': {issue}")
}
PuzWarning::ScrambledPuzzle { version } => {
write!(f, "Puzzle is scrambled (version {version}). Solution may not be readable without descrambling.")
}
}
}
}