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