use std::fmt;
use std::io;
use crate::namespace::error::NamespaceError;
use crate::node::error::NodeError;
use crate::parser::error::ParseError;
use crate::schema::error::SchemaError;
use crate::schema::fetcher::error::FetchError;
use crate::schema::xsd::error::XsdParseError;
use crate::xpath::error::{XPathEvalError, XPathSyntaxError};
#[derive(Debug, Clone, Default)]
pub struct ErrorLocation {
pub line: Option<usize>,
pub column: Option<usize>,
pub byte_offset: Option<usize>,
pub xpath: Option<String>,
}
impl ErrorLocation {
pub fn new() -> Self {
Self::default()
}
pub fn from_offset(byte_offset: usize) -> Self {
Self {
byte_offset: Some(byte_offset),
..Default::default()
}
}
pub fn from_offset_with_input(byte_offset: usize, input: &str) -> Self {
let (line, column) = Self::calculate_line_column(input, byte_offset);
Self {
line: Some(line),
column: Some(column),
byte_offset: Some(byte_offset),
xpath: None,
}
}
pub fn from_line_column(line: usize, column: usize) -> Self {
Self {
line: Some(line),
column: Some(column),
byte_offset: None,
xpath: None,
}
}
pub fn with_xpath(mut self, xpath: String) -> Self {
self.xpath = Some(xpath);
self
}
pub fn with_offset(mut self, offset: usize) -> Self {
self.byte_offset = Some(offset);
self
}
pub fn has_position(&self) -> bool {
self.line.is_some() || self.byte_offset.is_some()
}
pub fn calculate_line_column(input: &str, byte_offset: usize) -> (usize, usize) {
let mut line = 1;
let mut column = 1;
for (pos, ch) in input.char_indices() {
if pos >= byte_offset {
break;
}
if ch == '\n' {
line += 1;
column = 1;
} else {
column += 1;
}
}
(line, column)
}
}
impl fmt::Display for ErrorLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
if let (Some(line), Some(col)) = (self.line, self.column) {
parts.push(format!("line {}:{}", line, col));
} else if let Some(offset) = self.byte_offset {
parts.push(format!("position {}", offset));
}
if let Some(xpath) = &self.xpath {
parts.push(format!("at {}", xpath));
}
write!(f, "{}", parts.join(", "))
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("parse error: {0}")]
Parse(#[from] ParseError),
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("xpath syntax error: {0}")]
XPathSyntax(#[from] XPathSyntaxError),
#[error("xpath evaluation error: {0}")]
XPathEval(#[from] XPathEvalError),
#[error("schema error: {0}")]
Schema(#[from] SchemaError),
#[error("validation error: {message}")]
Validation {
message: String,
line: Option<usize>,
column: Option<usize>,
},
#[error("namespace error: {0}")]
Namespace(#[from] NamespaceError),
#[error("node error: {0}")]
Node(#[from] NodeError),
#[error("invalid operation: {0}")]
InvalidOperation(String),
#[error("fetch error: {0}")]
Fetch(#[from] FetchError),
#[error("utf8 error: {0}")]
Utf8(#[from] std::str::Utf8Error),
#[error("string utf8 error: {0}")]
FromUtf8(#[from] std::string::FromUtf8Error),
#[error("xsd parse error: {0}")]
XsdParse(#[from] XsdParseError),
}
impl From<quick_xml::Error> for Error {
fn from(err: quick_xml::Error) -> Self {
ParseError::Generic {
message: err.to_string(),
}
.into()
}
}
impl From<quick_xml::events::attributes::AttrError> for Error {
fn from(err: quick_xml::events::attributes::AttrError) -> Self {
ParseError::AttributeError {
message: err.to_string(),
}
.into()
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ErrorLevel {
Warning,
#[default]
Error,
Fatal,
}
impl std::fmt::Display for ErrorLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ErrorLevel::Warning => write!(f, "warning"),
ErrorLevel::Error => write!(f, "error"),
ErrorLevel::Fatal => write!(f, "fatal"),
}
}
}
#[derive(Debug, Clone)]
pub struct StructuredError {
pub message: String,
pub location: ErrorLocation,
pub error_type: ValidationErrorType,
pub level: ErrorLevel,
pub node_name: Option<String>,
pub expected: Option<String>,
pub found: Option<String>,
}
impl Default for StructuredError {
fn default() -> Self {
Self {
message: String::new(),
location: ErrorLocation::default(),
error_type: ValidationErrorType::Other,
level: ErrorLevel::Error,
node_name: None,
expected: None,
found: None,
}
}
}
impl StructuredError {
pub fn new(message: impl Into<String>, error_type: ValidationErrorType) -> Self {
Self {
message: message.into(),
error_type,
..Default::default()
}
}
pub fn with_line(mut self, line: usize) -> Self {
self.location.line = Some(line);
self
}
pub fn with_column(mut self, column: usize) -> Self {
self.location.column = Some(column);
self
}
pub fn with_byte_offset(mut self, offset: usize) -> Self {
self.location.byte_offset = Some(offset);
self
}
pub fn with_level(mut self, level: ErrorLevel) -> Self {
self.level = level;
self
}
pub fn with_element_path(mut self, path: impl Into<String>) -> Self {
self.location.xpath = Some(path.into());
self
}
pub fn with_node_name(mut self, name: impl Into<String>) -> Self {
self.node_name = Some(name.into());
self
}
pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
self.expected = Some(expected.into());
self
}
pub fn with_found(mut self, found: impl Into<String>) -> Self {
self.found = Some(found.into());
self
}
pub fn is_warning(&self) -> bool {
self.level == ErrorLevel::Warning
}
pub fn is_error(&self) -> bool {
self.level >= ErrorLevel::Error
}
pub fn with_location(mut self, location: &ErrorLocation) -> Self {
if let Some(line) = location.line {
self.location.line = Some(line);
}
if let Some(column) = location.column {
self.location.column = Some(column);
}
if let Some(offset) = location.byte_offset {
self.location.byte_offset = Some(offset);
}
if let Some(ref xpath) = location.xpath {
self.location.xpath = Some(xpath.clone());
}
self
}
pub fn set_location(mut self, location: ErrorLocation) -> Self {
self.location = location;
self
}
pub fn line(&self) -> Option<usize> {
self.location.line
}
pub fn column(&self) -> Option<usize> {
self.location.column
}
pub fn byte_offset(&self) -> Option<usize> {
self.location.byte_offset
}
pub fn element_path(&self) -> Option<&str> {
self.location.xpath.as_deref()
}
pub fn calculate_line_column(mut self, input: &str) -> Self {
if let Some(offset) = self.location.byte_offset {
let (line, column) = ErrorLocation::calculate_line_column(input, offset);
self.location.line = Some(line);
self.location.column = Some(column);
}
self
}
}
impl From<&StructuredError> for ErrorLocation {
fn from(err: &StructuredError) -> Self {
err.location.clone()
}
}
impl std::fmt::Display for StructuredError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] ", self.level)?;
if let Some(ref path) = self.location.xpath {
write!(f, "{}", path)?;
if let Some(line) = self.location.line {
write!(f, " (line {})", line)?;
}
write!(f, ": ")?;
} else if let (Some(line), Some(col)) = (self.location.line, self.location.column) {
write!(f, "{}:{}: ", line, col)?;
} else if let Some(line) = self.location.line {
write!(f, "line {}: ", line)?;
} else if let Some(offset) = self.location.byte_offset {
write!(f, "offset {}: ", offset)?;
}
write!(f, "{}", self.message)?;
if let (Some(expected), Some(found)) = (&self.expected, &self.found) {
write!(f, " (expected: {}, found: {})", expected, found)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidationErrorType {
UnknownElement,
UnknownAttribute,
MissingRequiredElement,
MissingRequiredAttribute,
InvalidAttributeValue,
InvalidContent,
InvalidTextContent,
TooManyOccurrences,
TooFewOccurrences,
ElementOutOfOrder,
UnexpectedElement,
NamespaceMismatch,
SchemaNotFound,
IdentityConstraint,
TypeNotFound,
FacetViolation,
ContentModelViolation,
UnclosedElement,
Other,
}