use std::fmt;
use std::path::Path;
use ito_config::types::ItoConfig;
use crate::errors::CoreError;
use crate::process::ProcessRunner;
use crate::validate::ValidationIssue;
use super::staged::StagedFiles;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RuleId(&'static str);
impl RuleId {
#[must_use]
pub const fn new(id: &'static str) -> Self {
Self(id)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
self.0
}
}
impl fmt::Display for RuleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.0)
}
}
impl AsRef<str> for RuleId {
fn as_ref(&self) -> &str {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RuleSeverity {
Error,
Warning,
Info,
}
impl RuleSeverity {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Error => "ERROR",
Self::Warning => "WARNING",
Self::Info => "INFO",
}
}
}
impl fmt::Display for RuleSeverity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[non_exhaustive]
pub struct RuleContext<'a> {
pub config: &'a ItoConfig,
pub project_root: &'a Path,
pub staged: &'a StagedFiles,
pub runner: &'a dyn ProcessRunner,
}
impl<'a> RuleContext<'a> {
#[must_use]
pub fn new(
config: &'a ItoConfig,
project_root: &'a Path,
staged: &'a StagedFiles,
runner: &'a dyn ProcessRunner,
) -> Self {
Self {
config,
project_root,
staged,
runner,
}
}
}
pub trait Rule: Send + Sync {
fn id(&self) -> RuleId;
fn severity(&self) -> RuleSeverity;
fn description(&self) -> &'static str;
fn gate(&self) -> Option<&'static str> {
None
}
fn is_active(&self, config: &ItoConfig) -> bool;
fn check(&self, ctx: &RuleContext<'_>) -> Result<Vec<ValidationIssue>, CoreError>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rule_id_round_trips_through_as_str() {
let id = RuleId::new("coordination/symlinks-wired");
assert_eq!(id.as_str(), "coordination/symlinks-wired");
assert_eq!(format!("{id}"), "coordination/symlinks-wired");
assert_eq!(id.as_ref(), "coordination/symlinks-wired");
}
#[test]
fn rule_id_is_orderable_for_deterministic_output() {
let mut ids = [
RuleId::new("coordination/staged-symlinked-paths"),
RuleId::new("coordination/symlinks-wired"),
RuleId::new("coordination/branch-name-set"),
];
ids.sort();
assert_eq!(
ids.iter().map(|id| id.as_str()).collect::<Vec<_>>(),
vec![
"coordination/branch-name-set",
"coordination/staged-symlinked-paths",
"coordination/symlinks-wired",
]
);
}
#[test]
fn rule_severity_string_matches_validation_levels() {
assert_eq!(RuleSeverity::Error.as_str(), "ERROR");
assert_eq!(RuleSeverity::Warning.as_str(), "WARNING");
assert_eq!(RuleSeverity::Info.as_str(), "INFO");
}
}