use std::fmt;
pub type QcResult<T> = Result<T, QcError>;
#[derive(Debug, thiserror::Error)]
pub enum QcError {
#[error("Invalid configuration: {0}")]
InvalidConfiguration(String),
#[error("Invalid input data: {0}")]
InvalidInput(String),
#[error("Validation rule error: {0}")]
ValidationRule(String),
#[error("Topology error: {0}")]
TopologyError(String),
#[error("Attribute error: {0}")]
AttributeError(String),
#[error("Metadata error: {0}")]
MetadataError(String),
#[error("Raster error: {0}")]
RasterError(String),
#[error("Report generation error: {0}")]
ReportError(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("TOML error: {0}")]
Toml(#[from] toml::de::Error),
#[error("Core error: {0}")]
Core(#[from] oxigdal_core::error::OxiGdalError),
#[error("Algorithm error: {0}")]
Algorithm(#[from] oxigdal_algorithms::error::AlgorithmError),
#[error("Numerical error: {0}")]
Numerical(String),
#[error("Fix error: {0}")]
FixError(String),
#[error("Threshold exceeded: {0}")]
ThresholdExceeded(String),
#[error("Unsupported operation: {0}")]
Unsupported(String),
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Info,
Warning,
Minor,
Major,
Critical,
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Info => write!(f, "INFO"),
Self::Warning => write!(f, "WARNING"),
Self::Minor => write!(f, "MINOR"),
Self::Major => write!(f, "MAJOR"),
Self::Critical => write!(f, "CRITICAL"),
}
}
}
impl Severity {
pub fn color(&self) -> &'static str {
match self {
Self::Info => "#17a2b8", Self::Warning => "#ffc107", Self::Minor => "#fd7e14", Self::Major => "#dc3545", Self::Critical => "#721c24", }
}
pub fn emoji(&self) -> &'static str {
match self {
Self::Info => "ℹ️",
Self::Warning => "⚠️",
Self::Minor => "⚡",
Self::Major => "❌",
Self::Critical => "🚨",
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct QcIssue {
pub severity: Severity,
pub category: String,
pub description: String,
pub message: String,
pub location: Option<String>,
pub suggestion: Option<String>,
pub rule_id: Option<String>,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
impl QcIssue {
pub fn new(
severity: Severity,
category: impl Into<String>,
description: impl Into<String>,
message: impl Into<String>,
) -> Self {
Self {
severity,
category: category.into(),
description: description.into(),
message: message.into(),
location: None,
suggestion: None,
rule_id: None,
timestamp: chrono::Utc::now(),
}
}
pub fn with_location(mut self, location: impl Into<String>) -> Self {
self.location = Some(location.into());
self
}
pub fn with_suggestion(mut self, suggestion: impl Into<String>) -> Self {
self.suggestion = Some(suggestion.into());
self
}
pub fn with_rule_id(mut self, rule_id: impl Into<String>) -> Self {
self.rule_id = Some(rule_id.into());
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_severity_ordering() {
assert!(Severity::Info < Severity::Warning);
assert!(Severity::Warning < Severity::Minor);
assert!(Severity::Minor < Severity::Major);
assert!(Severity::Major < Severity::Critical);
}
#[test]
fn test_severity_display() {
assert_eq!(Severity::Info.to_string(), "INFO");
assert_eq!(Severity::Warning.to_string(), "WARNING");
assert_eq!(Severity::Minor.to_string(), "MINOR");
assert_eq!(Severity::Major.to_string(), "MAJOR");
assert_eq!(Severity::Critical.to_string(), "CRITICAL");
}
#[test]
fn test_severity_color() {
assert_eq!(Severity::Info.color(), "#17a2b8");
assert_eq!(Severity::Critical.color(), "#721c24");
}
#[test]
fn test_qc_issue_creation() {
let issue = QcIssue::new(
Severity::Major,
"topology",
"Invalid geometry",
"Polygon has self-intersection",
)
.with_location("feature_123")
.with_suggestion("Use ST_MakeValid to repair")
.with_rule_id("TOPO-001");
assert_eq!(issue.severity, Severity::Major);
assert_eq!(issue.category, "topology");
assert_eq!(issue.description, "Invalid geometry");
assert_eq!(issue.message, "Polygon has self-intersection");
assert_eq!(issue.location, Some("feature_123".to_string()));
assert_eq!(
issue.suggestion,
Some("Use ST_MakeValid to repair".to_string())
);
assert_eq!(issue.rule_id, Some("TOPO-001".to_string()));
}
#[test]
fn test_qc_issue_serialization() {
let issue = QcIssue::new(
Severity::Warning,
"attribute",
"Missing field",
"Required field 'name' is missing",
);
let json = serde_json::to_string(&issue).ok();
assert!(json.is_some());
let deserialized: Result<QcIssue, _> = serde_json::from_str(&json.unwrap_or_default());
assert!(deserialized.is_ok());
}
}