use std::cmp::Ordering;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Severity {
Error,
#[default]
Warning,
Info,
Style,
}
impl Severity {
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"error" | "critical" | "major" => Some(Self::Error),
"warning" | "minor" => Some(Self::Warning),
"info" => Some(Self::Info),
"style" => Some(Self::Style),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::Error => "error",
Self::Warning => "warning",
Self::Info => "info",
Self::Style => "style",
}
}
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
impl Ord for Severity {
fn cmp(&self, other: &Self) -> Ordering {
let self_val = match self {
Self::Error => 0,
Self::Warning => 1,
Self::Info => 2,
Self::Style => 3,
};
let other_val = match other {
Self::Error => 0,
Self::Warning => 1,
Self::Info => 2,
Self::Style => 3,
};
other_val.cmp(&self_val)
}
}
impl PartialOrd for Severity {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RuleCategory {
Style,
Security,
BestPractice,
Performance,
}
impl RuleCategory {
pub fn as_str(&self) -> &'static str {
match self {
Self::Style => "style",
Self::Security => "security",
Self::BestPractice => "best-practice",
Self::Performance => "performance",
}
}
}
impl fmt::Display for RuleCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RuleCode(pub String);
impl RuleCode {
pub fn new(code: impl Into<String>) -> Self {
Self(code.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn is_dcl_rule(&self) -> bool {
self.0.starts_with("DCL")
}
pub fn number(&self) -> Option<u32> {
if self.0.starts_with("DCL") {
self.0[3..].parse().ok()
} else {
None
}
}
}
impl fmt::Display for RuleCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<&str> for RuleCode {
fn from(s: &str) -> Self {
Self::new(s)
}
}
impl From<String> for RuleCode {
fn from(s: String) -> Self {
Self(s)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CheckFailure {
pub code: RuleCode,
pub rule_name: String,
pub severity: Severity,
pub category: RuleCategory,
pub message: String,
pub line: u32,
pub column: u32,
pub end_line: Option<u32>,
pub end_column: Option<u32>,
pub fixable: bool,
pub data: std::collections::HashMap<String, String>,
}
impl CheckFailure {
pub fn new(
code: impl Into<RuleCode>,
rule_name: impl Into<String>,
severity: Severity,
category: RuleCategory,
message: impl Into<String>,
line: u32,
column: u32,
) -> Self {
Self {
code: code.into(),
rule_name: rule_name.into(),
severity,
category,
message: message.into(),
line,
column,
end_line: None,
end_column: None,
fixable: false,
data: std::collections::HashMap::new(),
}
}
pub fn with_end(mut self, end_line: u32, end_column: u32) -> Self {
self.end_line = Some(end_line);
self.end_column = Some(end_column);
self
}
pub fn with_fixable(mut self, fixable: bool) -> Self {
self.fixable = fixable;
self
}
pub fn with_data(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.data.insert(key.into(), value.into());
self
}
}
impl Ord for CheckFailure {
fn cmp(&self, other: &Self) -> Ordering {
match self.line.cmp(&other.line) {
Ordering::Equal => self.column.cmp(&other.column),
other => other,
}
}
}
impl PartialOrd for CheckFailure {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Debug, Clone)]
pub struct RuleMeta {
pub description: String,
pub url: String,
}
impl RuleMeta {
pub fn new(description: impl Into<String>, url: impl Into<String>) -> Self {
Self {
description: description.into(),
url: url.into(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ConfigLevel {
Off = 0,
Warn = 1,
#[default]
Error = 2,
}
impl ConfigLevel {
pub fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::Off),
1 => Some(Self::Warn),
2 => Some(Self::Error),
_ => None,
}
}
pub fn to_severity(&self) -> Option<Severity> {
match self {
Self::Off => None,
Self::Warn => Some(Severity::Warning),
Self::Error => Some(Severity::Error),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_severity_ordering() {
assert!(Severity::Error > Severity::Warning);
assert!(Severity::Warning > Severity::Info);
assert!(Severity::Info > Severity::Style);
}
#[test]
fn test_severity_from_str() {
assert_eq!(Severity::parse("error"), Some(Severity::Error));
assert_eq!(Severity::parse("WARNING"), Some(Severity::Warning));
assert_eq!(Severity::parse("Info"), Some(Severity::Info));
assert_eq!(Severity::parse("style"), Some(Severity::Style));
assert_eq!(Severity::parse("critical"), Some(Severity::Error));
assert_eq!(Severity::parse("major"), Some(Severity::Error));
assert_eq!(Severity::parse("minor"), Some(Severity::Warning));
assert_eq!(Severity::parse("invalid"), None);
}
#[test]
fn test_rule_code() {
let code = RuleCode::new("DCL001");
assert!(code.is_dcl_rule());
assert_eq!(code.number(), Some(1));
assert_eq!(code.as_str(), "DCL001");
let invalid = RuleCode::new("OTHER");
assert!(!invalid.is_dcl_rule());
assert_eq!(invalid.number(), None);
}
#[test]
fn test_check_failure_ordering() {
let f1 = CheckFailure::new(
"DCL001",
"test",
Severity::Warning,
RuleCategory::Style,
"msg1",
5,
1,
);
let f2 = CheckFailure::new(
"DCL002",
"test",
Severity::Info,
RuleCategory::Style,
"msg2",
10,
1,
);
let f3 = CheckFailure::new(
"DCL003",
"test",
Severity::Error,
RuleCategory::Style,
"msg3",
3,
1,
);
let f4 = CheckFailure::new(
"DCL004",
"test",
Severity::Error,
RuleCategory::Style,
"msg4",
3,
5,
);
let mut failures = vec![f1.clone(), f2.clone(), f3.clone(), f4.clone()];
failures.sort();
assert_eq!(failures[0].line, 3);
assert_eq!(failures[0].column, 1);
assert_eq!(failures[1].line, 3);
assert_eq!(failures[1].column, 5);
assert_eq!(failures[2].line, 5);
assert_eq!(failures[3].line, 10);
}
#[test]
fn test_config_level() {
assert_eq!(ConfigLevel::from_u8(0), Some(ConfigLevel::Off));
assert_eq!(ConfigLevel::from_u8(1), Some(ConfigLevel::Warn));
assert_eq!(ConfigLevel::from_u8(2), Some(ConfigLevel::Error));
assert_eq!(ConfigLevel::from_u8(3), None);
assert_eq!(ConfigLevel::Off.to_severity(), None);
assert_eq!(ConfigLevel::Warn.to_severity(), Some(Severity::Warning));
assert_eq!(ConfigLevel::Error.to_severity(), Some(Severity::Error));
}
#[test]
fn test_rule_category() {
assert_eq!(RuleCategory::Style.as_str(), "style");
assert_eq!(RuleCategory::Security.as_str(), "security");
assert_eq!(RuleCategory::BestPractice.as_str(), "best-practice");
assert_eq!(RuleCategory::Performance.as_str(), "performance");
}
}