fallow_types/output_format.rs
1/// Output format for fallow results.
2///
3/// This is command-line and integration metadata, not stored in config files.
4/// Keeping it in `fallow-types` lets config, output, CLI, MCP, and API layers
5/// agree on the same contract without creating a config-to-output dependency.
6#[derive(Debug, Default, Clone, Copy)]
7pub enum OutputFormat {
8 /// Human-readable terminal output with source context.
9 #[default]
10 Human,
11 /// Machine-readable JSON.
12 Json,
13 /// SARIF format for GitHub Code Scanning.
14 Sarif,
15 /// One issue per line (grep-friendly).
16 Compact,
17 /// Markdown for PR comments.
18 Markdown,
19 /// `CodeClimate` JSON for GitLab Code Quality.
20 ///
21 /// CLI aliases: `codeclimate`, `gitlab-codequality`, `gitlab-code-quality`.
22 CodeClimate,
23 /// GitHub-flavored sticky PR comment markdown.
24 PrCommentGithub,
25 /// GitLab-flavored sticky MR comment markdown.
26 PrCommentGitlab,
27 /// GitHub PR review JSON envelope.
28 ReviewGithub,
29 /// GitLab MR review JSON envelope.
30 ReviewGitlab,
31 /// Shields.io-compatible SVG badge (health command only).
32 Badge,
33 /// GitHub Actions workflow-command annotations (`::error` / `::warning` /
34 /// `::notice` lines). Provider-prefixed name because workflow-command
35 /// annotations are a GitHub-only concept with no GitLab twin.
36 GithubAnnotations,
37 /// GitHub Actions job-summary markdown (for `>> $GITHUB_STEP_SUMMARY`).
38 /// Provider-prefixed for the same reason as `GithubAnnotations`.
39 GithubSummary,
40}
41
42impl OutputFormat {
43 /// The `--format` spelling of this variant.
44 ///
45 /// Notes that name the format a run degraded on have to name it the way the
46 /// user would pass it, so the sentence doubles as the fix. `codeclimate` is
47 /// the canonical spelling of the aliased GitLab Code Quality format.
48 #[must_use]
49 pub const fn flag_label(self) -> &'static str {
50 match self {
51 Self::Human => "human",
52 Self::Json => "json",
53 Self::Sarif => "sarif",
54 Self::Compact => "compact",
55 Self::Markdown => "markdown",
56 Self::CodeClimate => "codeclimate",
57 Self::PrCommentGithub => "pr-comment-github",
58 Self::PrCommentGitlab => "pr-comment-gitlab",
59 Self::ReviewGithub => "review-github",
60 Self::ReviewGitlab => "review-gitlab",
61 Self::Badge => "badge",
62 Self::GithubAnnotations => "github-annotations",
63 Self::GithubSummary => "github-summary",
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 const VARIANTS: [OutputFormat; 13] = [
73 OutputFormat::Human,
74 OutputFormat::Json,
75 OutputFormat::Sarif,
76 OutputFormat::Compact,
77 OutputFormat::Markdown,
78 OutputFormat::CodeClimate,
79 OutputFormat::PrCommentGithub,
80 OutputFormat::PrCommentGitlab,
81 OutputFormat::ReviewGithub,
82 OutputFormat::ReviewGitlab,
83 OutputFormat::Badge,
84 OutputFormat::GithubAnnotations,
85 OutputFormat::GithubSummary,
86 ];
87
88 /// The labels are what a note tells a user to pass, so they must be the
89 /// `--format` values clap accepts rather than prettier prose.
90 #[test]
91 fn flag_labels_are_the_format_values() {
92 let labels: Vec<&str> = VARIANTS
93 .iter()
94 .map(|variant| variant.flag_label())
95 .collect();
96 assert_eq!(
97 labels,
98 vec![
99 "human",
100 "json",
101 "sarif",
102 "compact",
103 "markdown",
104 "codeclimate",
105 "pr-comment-github",
106 "pr-comment-gitlab",
107 "review-github",
108 "review-gitlab",
109 "badge",
110 "github-annotations",
111 "github-summary",
112 ]
113 );
114 }
115
116 #[test]
117 fn default_is_human() {
118 assert!(matches!(OutputFormat::default(), OutputFormat::Human));
119 }
120
121 #[test]
122 fn debug_names_remain_stable() {
123 let names: Vec<String> = VARIANTS
124 .iter()
125 .map(|variant| format!("{variant:?}"))
126 .collect();
127 assert_eq!(
128 names,
129 vec![
130 "Human",
131 "Json",
132 "Sarif",
133 "Compact",
134 "Markdown",
135 "CodeClimate",
136 "PrCommentGithub",
137 "PrCommentGitlab",
138 "ReviewGithub",
139 "ReviewGitlab",
140 "Badge",
141 "GithubAnnotations",
142 "GithubSummary",
143 ]
144 );
145 }
146}