fallow_output/format.rs
1/// Output format for fallow results.
2///
3/// This is command-line metadata, not stored in config files. Keeping it in
4/// `fallow-output` lets config, CLI, MCP, and future API layers agree on the
5/// same output contract without making `fallow-config` own presentation
6/// concepts.
7#[derive(Debug, Default, Clone, Copy)]
8pub enum OutputFormat {
9 /// Human-readable terminal output with source context.
10 #[default]
11 Human,
12 /// Machine-readable JSON.
13 Json,
14 /// SARIF format for GitHub Code Scanning.
15 Sarif,
16 /// One issue per line (grep-friendly).
17 Compact,
18 /// Markdown for PR comments.
19 Markdown,
20 /// `CodeClimate` JSON for GitLab Code Quality.
21 ///
22 /// CLI aliases: `codeclimate`, `gitlab-codequality`, `gitlab-code-quality`.
23 CodeClimate,
24 /// GitHub-flavored sticky PR comment markdown.
25 PrCommentGithub,
26 /// GitLab-flavored sticky MR comment markdown.
27 PrCommentGitlab,
28 /// GitHub PR review JSON envelope.
29 ReviewGithub,
30 /// GitLab MR review JSON envelope.
31 ReviewGitlab,
32 /// Shields.io-compatible SVG badge (health command only).
33 Badge,
34}
35
36#[cfg(test)]
37mod tests {
38 use super::*;
39
40 const VARIANTS: [OutputFormat; 11] = [
41 OutputFormat::Human,
42 OutputFormat::Json,
43 OutputFormat::Sarif,
44 OutputFormat::Compact,
45 OutputFormat::Markdown,
46 OutputFormat::CodeClimate,
47 OutputFormat::PrCommentGithub,
48 OutputFormat::PrCommentGitlab,
49 OutputFormat::ReviewGithub,
50 OutputFormat::ReviewGitlab,
51 OutputFormat::Badge,
52 ];
53
54 #[test]
55 fn default_is_human() {
56 assert!(matches!(OutputFormat::default(), OutputFormat::Human));
57 }
58
59 #[test]
60 fn debug_names_remain_stable() {
61 let names: Vec<String> = VARIANTS
62 .iter()
63 .map(|variant| format!("{variant:?}"))
64 .collect();
65 assert_eq!(
66 names,
67 vec![
68 "Human",
69 "Json",
70 "Sarif",
71 "Compact",
72 "Markdown",
73 "CodeClimate",
74 "PrCommentGithub",
75 "PrCommentGitlab",
76 "ReviewGithub",
77 "ReviewGitlab",
78 "Badge",
79 ]
80 );
81 }
82
83 #[test]
84 fn variants_are_distinct() {
85 let names: Vec<String> = VARIANTS
86 .iter()
87 .map(|variant| format!("{variant:?}"))
88 .collect();
89
90 for (i, a) in names.iter().enumerate() {
91 for (j, b) in names.iter().enumerate() {
92 if i != j {
93 assert_ne!(a, b);
94 }
95 }
96 }
97 }
98}