ontocore_diagnostics/
config.rs1use ontocore_core::{DiagnosticCode, DiagnosticSeverity};
4use serde::Deserialize;
5use std::collections::HashMap;
6use std::path::Path;
7
8#[derive(Debug, Clone, Default, Deserialize)]
9pub struct DiagnosticConfig {
10 #[serde(default)]
11 pub rules: HashMap<String, RuleConfig>,
12}
13
14#[derive(Debug, Clone, Deserialize)]
15pub struct RuleConfig {
16 #[serde(default = "default_enabled")]
17 pub enabled: bool,
18 pub severity: Option<String>,
19}
20
21fn default_enabled() -> bool {
22 true
23}
24
25impl DiagnosticConfig {
26 pub fn load(path: &Path) -> Result<Self, String> {
27 let text = std::fs::read_to_string(path)
28 .map_err(|e| format!("failed to read {}: {e}", path.display()))?;
29 toml::from_str(&text).map_err(|e| format!("invalid diagnostics config: {e}"))
30 }
31
32 pub fn is_rule_enabled(&self, code: DiagnosticCode) -> bool {
33 let key = code.as_str();
34 self.rules.get(key).map(|r| r.enabled).unwrap_or(true)
35 }
36
37 pub fn severity_override(&self, code: DiagnosticCode) -> Option<DiagnosticSeverity> {
38 let key = code.as_str();
39 let sev = self.rules.get(key)?.severity.as_deref()?;
40 match sev.to_ascii_lowercase().as_str() {
41 "error" => Some(DiagnosticSeverity::Error),
42 "warning" => Some(DiagnosticSeverity::Warning),
43 "info" | "hint" => Some(DiagnosticSeverity::Info),
44 _ => None,
45 }
46 }
47}
48
49pub fn find_config(workspace: &Path) -> Option<DiagnosticConfig> {
50 let path = workspace.join(".ontocore").join("diagnostics.toml");
51 DiagnosticConfig::load(&path).ok()
52}
53
54#[cfg(test)]
55mod tests {
56 use super::*;
57
58 #[test]
59 fn parses_rule_toggles() {
60 let cfg: DiagnosticConfig = toml::from_str(
61 r#"
62[rules.missing_label]
63enabled = false
64[rules.broken_import]
65severity = "error"
66"#,
67 )
68 .unwrap();
69 assert!(!cfg.is_rule_enabled(DiagnosticCode::MissingLabel));
70 assert_eq!(
71 cfg.severity_override(DiagnosticCode::BrokenImport),
72 Some(DiagnosticSeverity::Error)
73 );
74 }
75
76 #[test]
77 fn find_config_loads_from_workspace_dot_ontocore() {
78 let dir = tempfile::tempdir().unwrap();
79 std::fs::create_dir_all(dir.path().join(".ontocore")).unwrap();
80 std::fs::write(
81 dir.path().join(".ontocore/diagnostics.toml"),
82 "[rules.orphan_class]\nenabled = false\n",
83 )
84 .unwrap();
85 let cfg = find_config(dir.path()).expect("config");
86 assert!(!cfg.is_rule_enabled(DiagnosticCode::OrphanClass));
87 }
88
89 #[test]
90 fn hint_severity_maps_to_info() {
91 let cfg: DiagnosticConfig = toml::from_str(
92 r#"
93[rules.duplicate_label]
94severity = "hint"
95"#,
96 )
97 .unwrap();
98 assert_eq!(
99 cfg.severity_override(DiagnosticCode::DuplicateLabel),
100 Some(DiagnosticSeverity::Info)
101 );
102 }
103}