use std::fmt;
use crate::io::smiles::chem::ast::Span;
use molrs::error::MolRsError;
#[derive(Debug, Clone, PartialEq)]
pub struct SmilesError {
pub kind: SmilesErrorKind,
pub span: Span,
pub input: String,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SmilesErrorKind {
UnexpectedChar(char),
UnexpectedEnd,
UnclosedBracket,
UnclosedBranch,
UnmatchedRingClosure(u16),
InvalidElement(String),
InvalidCharge,
EmptyInput,
TrailingCharacters,
InvalidQueryPrimitive(String),
UnclosedRecursive,
RecursionLimit,
RingBondConflict { rnum: u16 },
}
impl SmilesError {
pub fn new(kind: SmilesErrorKind, span: Span, input: &str) -> Self {
Self {
kind,
span,
input: input.to_owned(),
}
}
}
impl fmt::Display for SmilesError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pos = self.span.start;
let msg = match &self.kind {
SmilesErrorKind::UnexpectedChar(c) => format!("unexpected character '{c}'"),
SmilesErrorKind::UnexpectedEnd => "unexpected end of input".to_owned(),
SmilesErrorKind::UnclosedBracket => "unclosed bracket '['".to_owned(),
SmilesErrorKind::UnclosedBranch => "unclosed branch '('".to_owned(),
SmilesErrorKind::UnmatchedRingClosure(n) => {
format!("unmatched ring closure {n}")
}
SmilesErrorKind::InvalidElement(s) => format!("invalid element '{s}'"),
SmilesErrorKind::InvalidCharge => "invalid charge specification".to_owned(),
SmilesErrorKind::EmptyInput => "empty input".to_owned(),
SmilesErrorKind::TrailingCharacters => "trailing characters after molecule".to_owned(),
SmilesErrorKind::InvalidQueryPrimitive(s) => {
format!("invalid SMARTS query primitive '{s}'")
}
SmilesErrorKind::UnclosedRecursive => "unclosed recursive SMARTS '$('".to_owned(),
SmilesErrorKind::RecursionLimit => "recursive SMARTS nesting limit exceeded".to_owned(),
SmilesErrorKind::RingBondConflict { rnum } => {
format!("conflicting bond types on ring closure {rnum}")
}
};
write!(f, "SMILES parse error at position {pos}: {msg}")?;
if self.input.len() <= 120 {
write!(f, "\n {}\n ", self.input)?;
for _ in 0..pos.min(self.input.len()) {
write!(f, " ")?;
}
write!(f, "^")?;
}
Ok(())
}
}
impl std::error::Error for SmilesError {}
impl From<SmilesError> for MolRsError {
fn from(e: SmilesError) -> Self {
MolRsError::Parse {
line: None,
message: e.to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_display_with_caret() {
let err = SmilesError::new(
SmilesErrorKind::UnexpectedChar('X'),
Span::new(3, 4),
"CC(X)O",
);
let s = err.to_string();
assert!(s.contains("position 3"));
assert!(s.contains("unexpected character 'X'"));
assert!(s.contains("CC(X)O"));
assert!(s.contains(" ^"));
}
#[test]
fn test_into_molrs_error() {
let err = SmilesError::new(SmilesErrorKind::EmptyInput, Span::new(0, 0), "");
let molrs: MolRsError = err.into();
let msg = format!("{molrs}");
assert!(msg.contains("empty input"));
}
}