use crate::ir::DamlModule;
use serde::Serialize;
use std::path::PathBuf;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DetectError {
detector: String,
message: String,
}
impl DetectError {
pub fn new(detector: impl Into<String>, message: impl Into<String>) -> Self {
Self {
detector: detector.into(),
message: message.into(),
}
}
pub fn detector(&self) -> &str {
&self.detector
}
pub fn message(&self) -> &str {
&self.message
}
}
impl std::fmt::Display for DetectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "detector '{}': {}", self.detector, self.message)
}
}
impl std::error::Error for DetectError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)]
#[non_exhaustive]
pub enum Severity {
Critical,
High,
Medium,
Low,
Info,
}
impl std::fmt::Display for Severity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Critical => write!(f, "CRITICAL"),
Self::High => write!(f, "HIGH"),
Self::Medium => write!(f, "MEDIUM"),
Self::Low => write!(f, "LOW"),
Self::Info => write!(f, "INFO"),
}
}
}
impl std::str::FromStr for Severity {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"critical" => Ok(Self::Critical),
"high" => Ok(Self::High),
"medium" => Ok(Self::Medium),
"low" => Ok(Self::Low),
"info" => Ok(Self::Info),
_ => Err(()),
}
}
}
#[derive(Debug, Clone, Serialize)]
#[non_exhaustive]
pub struct Finding {
pub detector: String,
pub severity: Severity,
pub file: PathBuf,
pub line: usize,
pub column: usize,
pub message: String,
pub evidence: String,
}
pub fn parse_severity(s: &str) -> Option<Severity> {
s.parse().ok()
}
pub trait Detector {
fn name(&self) -> &str;
fn severity(&self) -> Severity;
fn description(&self) -> &str;
fn detect(&self, module: &DamlModule) -> Vec<Finding>;
fn try_detect(&self, module: &DamlModule) -> Result<Vec<Finding>, DetectError> {
Ok(self.detect(module))
}
}
use crate::detectors::archive_before_execute::ArchiveBeforeExecute;
use crate::detectors::ensure_decimal::MissingEnsureDecimal;
use crate::detectors::head_of_list::HeadOfListQuery;
use crate::detectors::positive_amount::MissingPositiveAmount;
use crate::detectors::unbounded_fields::UnboundedFields;
use crate::detectors::unguarded_division::UnguardedDivision;
pub fn find_duplicate_name(detectors: &[Box<dyn Detector>]) -> Option<String> {
let mut seen = std::collections::HashSet::new();
for det in detectors {
if !seen.insert(det.name()) {
return Some(det.name().to_string());
}
}
None
}
pub fn all_detectors() -> Vec<Box<dyn Detector>> {
vec![
Box::new(MissingEnsureDecimal),
Box::new(UnguardedDivision),
Box::new(HeadOfListQuery),
Box::new(UnboundedFields),
Box::new(MissingPositiveAmount),
Box::new(ArchiveBeforeExecute),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn returns_none_when_detector_names_are_unique() {
assert_eq!(find_duplicate_name(&all_detectors()), None);
}
#[test]
fn returns_duplicate_detector_name() {
let mut doubled = all_detectors();
doubled.extend(all_detectors());
assert!(find_duplicate_name(&doubled).is_some());
}
}