use crate::manifest::Manifest;
use colored::Colorize;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq)]
pub enum LintSeverity {
Error,
Warning,
}
#[derive(Debug, Clone)]
pub struct LintIssue {
pub severity: LintSeverity,
pub message: String,
}
#[derive(Debug, Default)]
pub struct LintResult {
pub issues: Vec<LintIssue>,
}
impl LintResult {
pub fn error_count(&self) -> usize {
self.issues
.iter()
.filter(|i| i.severity == LintSeverity::Error)
.count()
}
pub fn warning_count(&self) -> usize {
self.issues
.iter()
.filter(|i| i.severity == LintSeverity::Warning)
.count()
}
}
pub fn run(manifest: &Manifest) -> LintResult {
let mut issues = Vec::new();
let mut name_counts: HashMap<&str, usize> = HashMap::new();
for svc in &manifest.services {
*name_counts.entry(svc.name.as_str()).or_insert(0) += 1;
}
let mut dupes: Vec<(&str, usize)> = name_counts
.iter()
.filter(|(_, &c)| c > 1)
.map(|(&n, &c)| (n, c))
.collect();
dupes.sort_unstable_by_key(|(n, _)| *n);
for (name, count) in dupes {
issues.push(LintIssue {
severity: LintSeverity::Error,
message: format!("duplicate service name '{}' appears {} times", name, count),
});
}
for (i, svc) in manifest.services.iter().enumerate() {
if svc.name.trim().is_empty() {
issues.push(LintIssue {
severity: LintSeverity::Error,
message: format!("service at index {} has a blank or whitespace-only name", i),
});
}
}
for svc in &manifest.services {
if svc.depends_on.iter().any(|d| d == &svc.name) {
issues.push(LintIssue {
severity: LintSeverity::Error,
message: format!("'{}' lists itself in depends_on", svc.name),
});
}
}
for svc in &manifest.services {
if svc.depends_on.iter().any(|d| d.trim().is_empty()) {
issues.push(LintIssue {
severity: LintSeverity::Error,
message: format!("'{}' has a blank entry in its depends_on list", svc.name),
});
}
}
for svc in &manifest.services {
let mut seen: HashMap<&str, usize> = HashMap::new();
for dep in &svc.depends_on {
*seen.entry(dep.as_str()).or_insert(0) += 1;
}
let mut dup_deps: Vec<&str> = seen
.iter()
.filter(|(_, &c)| c > 1)
.map(|(&d, _)| d)
.collect();
dup_deps.sort_unstable();
for dep in dup_deps {
issues.push(LintIssue {
severity: LintSeverity::Warning,
message: format!(
"'{}' lists '{}' more than once in depends_on",
svc.name, dep
),
});
}
}
if !["1", "2"].contains(&manifest.version.as_str()) {
issues.push(LintIssue {
severity: LintSeverity::Warning,
message: format!(
"manifest version '{}' is unrecognised (expected '1' or '2')",
manifest.version
),
});
}
LintResult { issues }
}
pub fn render(result: &LintResult) {
if result.issues.is_empty() {
println!("{}", "✓ No lint issues found.".green().bold());
return;
}
for issue in &result.issues {
match issue.severity {
LintSeverity::Error => {
println!("{} {}", "✗ [error]".red().bold(), issue.message);
}
LintSeverity::Warning => {
println!("{} {}", "⚠ [warn] ".yellow().bold(), issue.message);
}
}
}
println!();
println!(
"found {} error(s), {} warning(s)",
result.error_count(),
result.warning_count()
);
}