1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6#[non_exhaustive]
7pub enum Severity {
8 Hint,
10 Warn,
12 Block,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Signal {
19 pub severity: Severity,
20 pub origin: String,
21 pub message: String,
23 pub agent_hint: Option<String>,
25 pub auto_fix: Option<FixPatch>,
27 pub location: Option<CodeSpan>,
28}
29
30impl Signal {
31 pub fn is_blocking(&self) -> bool {
32 matches!(self.severity, Severity::Block)
33 }
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct CodeSpan {
38 pub path: PathBuf,
39 pub line: u32,
40 pub column: u32,
41 pub length: u32,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46#[non_exhaustive]
47pub enum FixPatch {
48 ReplaceFile { path: PathBuf, content: String },
50 UnifiedDiff { diff: String },
52 RunCommand {
54 program: String,
55 args: Vec<String>,
56 cwd: Option<PathBuf>,
57 },
58}
59
60#[derive(Debug, Default)]
62pub struct SignalSet {
63 pub signals: Vec<Signal>,
64}
65
66impl SignalSet {
67 pub fn new(signals: Vec<Signal>) -> Self {
68 Self { signals }
69 }
70
71 pub fn has_blocking(&self) -> bool {
72 self.signals.iter().any(Signal::is_blocking)
73 }
74
75 pub fn partition_auto_fix(self) -> (Vec<FixPatch>, SignalSet) {
77 let mut patches = Vec::new();
78 let mut remaining = Vec::new();
79 for s in self.signals {
80 if let Some(p) = s.auto_fix.clone() {
81 patches.push(p);
82 } else {
83 remaining.push(s);
84 }
85 }
86 (patches, SignalSet { signals: remaining })
87 }
88
89 pub fn is_clean(&self) -> bool {
90 self.signals.is_empty()
91 }
92}