Skip to main content

fallow_output/
pr_decision.rs

1use serde::{Deserialize, Serialize};
2
3/// Schema discriminator serialized into [`PrDecisionSurface::schema`].
4pub const PR_DECISION_SCHEMA: &str = "fallow-pr-decision/v1";
5
6/// Provider-neutral PR gate decision, consumed by CI integrations to publish
7/// a check run or equivalent status surface.
8#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
9pub struct PrDecisionSurface {
10    /// Schema discriminator; always [`PR_DECISION_SCHEMA`].
11    pub schema: String,
12    /// Display title for the check surface.
13    pub title: String,
14    /// Overall conclusion aggregated across `gates`.
15    pub conclusion: PrDecisionConclusion,
16    /// Individual quality-gate outcomes.
17    pub gates: Vec<PrDecisionGate>,
18    /// File-anchored annotations for inline display.
19    pub annotations: Vec<PrDecisionAnnotation>,
20    /// Longer-form report content and links.
21    pub details: PrDecisionDetails,
22}
23
24/// Outcome of the overall decision or a single gate; values mirror GitHub
25/// check-run conclusions.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "snake_case")]
28pub enum PrDecisionConclusion {
29    /// Gate passed.
30    Success,
31    /// Gate breached its threshold.
32    Failure,
33    /// Gate produced findings without failing.
34    Neutral,
35    /// Gate did not run.
36    Skipped,
37}
38
39/// One quality-gate outcome inside [`PrDecisionSurface::gates`].
40#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub struct PrDecisionGate {
42    /// Stable gate identifier, e.g. `duplication`.
43    pub id: String,
44    /// Display label for the gate.
45    pub label: String,
46    /// Gate outcome.
47    pub status: PrDecisionConclusion,
48    /// Observed value text, e.g. "9.1% on changed code".
49    pub observed: String,
50    /// Configured threshold text, e.g. "<= 3%", when the gate has one.
51    pub threshold: Option<String>,
52    /// Scope the gate evaluated, e.g. "new code".
53    pub scope: String,
54}
55
56/// File-anchored annotation inside [`PrDecisionSurface::annotations`].
57#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
58pub struct PrDecisionAnnotation {
59    /// File path relative to the repository root.
60    pub path: String,
61    /// 1-based line the annotation points at.
62    pub line: u32,
63    /// Annotation display level.
64    pub level: PrDecisionAnnotationLevel,
65    /// Short annotation title.
66    pub title: String,
67    /// Annotation body text.
68    pub message: String,
69    /// Extra unrendered context, when available.
70    pub raw_details: Option<String>,
71}
72
73/// Display level for a [`PrDecisionAnnotation`]; values mirror GitHub
74/// check-run annotation levels.
75#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum PrDecisionAnnotationLevel {
78    /// Informational annotation.
79    Notice,
80    /// Advisory annotation.
81    Warning,
82    /// Gating annotation.
83    Failure,
84}
85
86/// Report content and links inside [`PrDecisionSurface::details`].
87#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
88pub struct PrDecisionDetails {
89    /// Markdown body summarizing the run.
90    pub summary_markdown: String,
91    /// Local path to the full report artifact, when one was written.
92    pub full_report_path: Option<String>,
93    /// Link to hosted report output, when available.
94    pub details_url: Option<String>,
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100
101    #[test]
102    fn decision_surface_serializes_stable_schema() {
103        let surface = PrDecisionSurface {
104            schema: PR_DECISION_SCHEMA.to_owned(),
105            title: "Fallow".to_owned(),
106            conclusion: PrDecisionConclusion::Failure,
107            gates: vec![PrDecisionGate {
108                id: "duplication".to_owned(),
109                label: "Duplication".to_owned(),
110                status: PrDecisionConclusion::Failure,
111                observed: "9.1% on changed code".to_owned(),
112                threshold: Some("<= 3%".to_owned()),
113                scope: "new code".to_owned(),
114            }],
115            annotations: vec![PrDecisionAnnotation {
116                path: "src/app.ts".to_owned(),
117                line: 42,
118                level: PrDecisionAnnotationLevel::Warning,
119                title: "Duplication".to_owned(),
120                message: "Clone group found".to_owned(),
121                raw_details: Some("fallow/code-duplication".to_owned()),
122            }],
123            details: PrDecisionDetails {
124                summary_markdown: "Quality gate failed".to_owned(),
125                full_report_path: None,
126                details_url: None,
127            },
128        };
129
130        let json = serde_json::to_value(surface).expect("serializes");
131        assert_eq!(json["schema"], PR_DECISION_SCHEMA);
132        assert_eq!(json["conclusion"], "failure");
133        assert_eq!(json["annotations"][0]["level"], "warning");
134    }
135}