use crate::parser::location::SourceLocation;
use thiserror::Error;
pub type SchemaResult<T> = Result<T, SchemaError>;
pub type FacetResult<T> = Result<T, FacetError>;
#[derive(Error, Debug, Clone)]
pub enum FacetError {
#[error("length constraint violation: {message}")]
LengthViolation { message: String },
#[error("minLength constraint violation: value length {actual} is less than minimum {min}")]
MinLengthViolation { actual: u64, min: u64 },
#[error("maxLength constraint violation: value length {actual} exceeds maximum {max}")]
MaxLengthViolation { actual: u64, max: u64 },
#[error("pattern constraint violation: value '{value}' does not match pattern '{pattern}'")]
PatternViolation { value: String, pattern: String },
#[error("enumeration constraint violation: value '{value}' is not in the allowed set")]
EnumerationViolation { value: String },
#[error("minInclusive constraint violation: value '{value}' is less than minimum '{min}'")]
MinInclusiveViolation { value: String, min: String },
#[error("maxInclusive constraint violation: value '{value}' is greater than maximum '{max}'")]
MaxInclusiveViolation { value: String, max: String },
#[error("minExclusive constraint violation: value '{value}' is not greater than '{min}'")]
MinExclusiveViolation { value: String, min: String },
#[error("maxExclusive constraint violation: value '{value}' is not less than '{max}'")]
MaxExclusiveViolation { value: String, max: String },
#[error("totalDigits constraint violation: value has {actual} digits, maximum is {max}")]
TotalDigitsViolation { actual: u32, max: u32 },
#[error(
"fractionDigits constraint violation: value has {actual} fraction digits, maximum is {max}"
)]
FractionDigitsViolation { actual: u32, max: u32 },
#[error("explicitTimezone constraint violation: {message}")]
ExplicitTimezoneViolation { message: String },
#[error("invalid pattern regex '{pattern}': {message}")]
InvalidPattern { pattern: String, message: String },
#[error("derivation restriction violation: {message}")]
DerivationRestriction { message: String },
#[error("fixed facet violation: cannot override fixed {facet_name} value '{base_value}' with '{derived_value}'")]
FixedFacetViolation {
facet_name: String,
base_value: String,
derived_value: String,
},
#[error("conflicting facets: {message}")]
ConflictingFacets { message: String },
#[error("facet '{facet}' is not applicable to type '{type_name}'")]
NotApplicable { facet: String, type_name: String },
}
impl FacetError {
pub fn length(message: impl Into<String>) -> Self {
FacetError::LengthViolation {
message: message.into(),
}
}
pub fn pattern(value: impl Into<String>, pattern: impl Into<String>) -> Self {
FacetError::PatternViolation {
value: value.into(),
pattern: pattern.into(),
}
}
pub fn enumeration(value: impl Into<String>) -> Self {
FacetError::EnumerationViolation {
value: value.into(),
}
}
pub fn derivation(message: impl Into<String>) -> Self {
FacetError::DerivationRestriction {
message: message.into(),
}
}
pub fn fixed_violation(
facet_name: impl Into<String>,
base_value: impl Into<String>,
derived_value: impl Into<String>,
) -> Self {
FacetError::FixedFacetViolation {
facet_name: facet_name.into(),
base_value: base_value.into(),
derived_value: derived_value.into(),
}
}
pub fn conflicting(message: impl Into<String>) -> Self {
FacetError::ConflictingFacets {
message: message.into(),
}
}
}
#[derive(Error, Debug)]
pub enum SchemaError {
#[error("XML parse error{}: {message}", location_str(.location))]
XmlError {
message: String,
location: Option<SourceLocation>,
},
#[error("Schema structural error{}: {message} (constraint: {constraint})", location_str(.location))]
StructuralError {
constraint: &'static str,
message: String,
location: Option<SourceLocation>,
},
#[error("Namespace error{}: {message}", location_str(.location))]
NamespaceError {
message: String,
location: Option<SourceLocation>,
},
#[error("Feature not supported{}: {message}", location_str(.location))]
FeatureError {
message: String,
location: Option<SourceLocation>,
},
#[error("Schema resolution error: {message}")]
ResolutionError { message: String },
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("Internal error: {0}")]
Internal(String),
}
impl SchemaError {
pub fn structural(
constraint: &'static str,
message: impl Into<String>,
location: Option<SourceLocation>,
) -> Self {
SchemaError::StructuralError {
constraint,
message: message.into(),
location,
}
}
pub fn namespace(message: impl Into<String>, location: Option<SourceLocation>) -> Self {
SchemaError::NamespaceError {
message: message.into(),
location,
}
}
pub fn feature(message: impl Into<String>, location: Option<SourceLocation>) -> Self {
SchemaError::FeatureError {
message: message.into(),
location,
}
}
pub fn resolution(message: impl Into<String>) -> Self {
SchemaError::ResolutionError {
message: message.into(),
}
}
pub fn xml(message: impl Into<String>, location: Option<SourceLocation>) -> Self {
SchemaError::XmlError {
message: message.into(),
location,
}
}
pub fn internal(message: impl Into<String>) -> Self {
SchemaError::Internal(message.into())
}
pub fn with_location(self, location: SourceLocation) -> Self {
match self {
SchemaError::XmlError {
message,
location: None,
} => SchemaError::XmlError {
message,
location: Some(location),
},
SchemaError::StructuralError {
constraint,
message,
location: None,
} => SchemaError::StructuralError {
constraint,
message,
location: Some(location),
},
SchemaError::NamespaceError {
message,
location: None,
} => SchemaError::NamespaceError {
message,
location: Some(location),
},
SchemaError::FeatureError {
message,
location: None,
} => SchemaError::FeatureError {
message,
location: Some(location),
},
other => other,
}
}
pub fn is_schema_content_error(&self) -> bool {
matches!(
self,
SchemaError::StructuralError { .. }
| SchemaError::NamespaceError { .. }
| SchemaError::XmlError { .. }
| SchemaError::FeatureError { .. }
)
}
}
impl From<quick_xml::Error> for SchemaError {
fn from(err: quick_xml::Error) -> Self {
SchemaError::XmlError {
message: err.to_string(),
location: None,
}
}
}
fn location_str(loc: &Option<SourceLocation>) -> String {
match loc {
Some(l) => format!(" at {}", l),
None => String::new(),
}
}