1use serde::{Deserialize, Serialize};
4use std::fmt;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CheckReport {
9 pub commits: Vec<CommitCheckResult>,
11 pub summary: CheckSummary,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct CommitCheckResult {
18 pub hash: String,
20 pub message: String,
22 pub issues: Vec<CommitIssue>,
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub suggestion: Option<CommitSuggestion>,
27 pub passes: bool,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct CommitIssue {
34 pub severity: IssueSeverity,
36 pub section: String,
38 pub rule: String,
40 pub explanation: String,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct CommitSuggestion {
47 pub message: String,
49 pub explanation: String,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "lowercase")]
56pub enum IssueSeverity {
57 Error,
59 Warning,
61 Info,
63}
64
65impl fmt::Display for IssueSeverity {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 match self {
68 IssueSeverity::Error => write!(f, "ERROR"),
69 IssueSeverity::Warning => write!(f, "WARNING"),
70 IssueSeverity::Info => write!(f, "INFO"),
71 }
72 }
73}
74
75impl std::str::FromStr for IssueSeverity {
76 type Err = ();
77
78 fn from_str(s: &str) -> Result<Self, Self::Err> {
79 match s.to_lowercase().as_str() {
80 "error" => Ok(IssueSeverity::Error),
81 "warning" => Ok(IssueSeverity::Warning),
82 "info" => Ok(IssueSeverity::Info),
83 _ => Ok(IssueSeverity::Warning), }
85 }
86}
87
88impl IssueSeverity {
89 pub fn parse(s: &str) -> Self {
91 s.parse().unwrap_or(IssueSeverity::Warning)
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct CheckSummary {
98 pub total_commits: usize,
100 pub passing_commits: usize,
102 pub failing_commits: usize,
104 pub error_count: usize,
106 pub warning_count: usize,
108 pub info_count: usize,
110}
111
112impl CheckSummary {
113 pub fn from_results(results: &[CommitCheckResult]) -> Self {
115 let total_commits = results.len();
116 let passing_commits = results.iter().filter(|r| r.passes).count();
117 let failing_commits = total_commits - passing_commits;
118
119 let mut error_count = 0;
120 let mut warning_count = 0;
121 let mut info_count = 0;
122
123 for result in results {
124 for issue in &result.issues {
125 match issue.severity {
126 IssueSeverity::Error => error_count += 1,
127 IssueSeverity::Warning => warning_count += 1,
128 IssueSeverity::Info => info_count += 1,
129 }
130 }
131 }
132
133 Self {
134 total_commits,
135 passing_commits,
136 failing_commits,
137 error_count,
138 warning_count,
139 info_count,
140 }
141 }
142}
143
144impl CheckReport {
145 pub fn new(commits: Vec<CommitCheckResult>) -> Self {
147 let summary = CheckSummary::from_results(&commits);
148 Self { commits, summary }
149 }
150
151 pub fn has_errors(&self) -> bool {
153 self.summary.error_count > 0
154 }
155
156 pub fn has_warnings(&self) -> bool {
158 self.summary.warning_count > 0
159 }
160
161 pub fn exit_code(&self, strict: bool) -> i32 {
163 if self.has_errors() {
164 1
165 } else if strict && self.has_warnings() {
166 2
167 } else {
168 0
169 }
170 }
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
175pub enum OutputFormat {
176 #[default]
178 Text,
179 Json,
181 Yaml,
183}
184
185impl std::str::FromStr for OutputFormat {
186 type Err = ();
187
188 fn from_str(s: &str) -> Result<Self, Self::Err> {
189 match s.to_lowercase().as_str() {
190 "text" => Ok(OutputFormat::Text),
191 "json" => Ok(OutputFormat::Json),
192 "yaml" => Ok(OutputFormat::Yaml),
193 _ => Err(()),
194 }
195 }
196}
197
198impl fmt::Display for OutputFormat {
199 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200 match self {
201 OutputFormat::Text => write!(f, "text"),
202 OutputFormat::Json => write!(f, "json"),
203 OutputFormat::Yaml => write!(f, "yaml"),
204 }
205 }
206}
207
208#[derive(Debug, Clone, Deserialize)]
210pub struct AiCheckResponse {
211 pub checks: Vec<AiCommitCheck>,
213}
214
215#[derive(Debug, Clone, Deserialize)]
217pub struct AiCommitCheck {
218 pub commit: String,
220 pub passes: bool,
222 #[serde(default)]
224 pub issues: Vec<AiIssue>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub suggestion: Option<AiSuggestion>,
228}
229
230#[derive(Debug, Clone, Deserialize)]
232pub struct AiIssue {
233 pub severity: String,
235 pub section: String,
237 pub rule: String,
239 pub explanation: String,
241}
242
243#[derive(Debug, Clone, Deserialize)]
245pub struct AiSuggestion {
246 pub message: String,
248 pub explanation: String,
250}
251
252impl From<AiCommitCheck> for CommitCheckResult {
253 fn from(ai: AiCommitCheck) -> Self {
254 let issues: Vec<CommitIssue> = ai
255 .issues
256 .into_iter()
257 .map(|i| CommitIssue {
258 severity: IssueSeverity::parse(&i.severity),
259 section: i.section,
260 rule: i.rule,
261 explanation: i.explanation,
262 })
263 .collect();
264
265 let suggestion = ai.suggestion.map(|s| CommitSuggestion {
266 message: s.message,
267 explanation: s.explanation,
268 });
269
270 Self {
271 hash: ai.commit,
272 message: String::new(), issues,
274 suggestion,
275 passes: ai.passes,
276 }
277 }
278}