backhopper_core/model/summary.rs
1// Copyright (C) 2026 Michael S. Klishin and Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// See LICENSE-APACHE and LICENSE-MIT for details.
4
5//! Compact per-result projection emitted by `--formatter summary`
6//! and `--formatter text-summary`.
7
8use std::num::NonZeroU32;
9
10use serde::{Deserialize, Serialize};
11
12use crate::model::names::{CommitSha, SeriesName};
13use crate::model::verdict::{TouchedKinds, Verdict};
14
15/// One row in the summary output: a compact projection of a single
16/// `SeriesEvaluation` result. JSON and text formatters share the type.
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
19pub struct SummaryRow {
20 pub sha: CommitSha,
21 pub verdict: VerdictKind,
22 pub touched: TouchedKinds,
23 /// Total tracked-dep symbol references summed over every evaluated
24 /// pin. Zero means the patch references no tracked-dep API; the
25 /// row is safe to skip at a glance.
26 pub tracked: u32,
27 pub subject: String,
28 /// The series the row was evaluated against. `None` for
29 /// pin-targeted single evaluations and pre-v7 envelopes.
30 #[serde(default)]
31 pub series: Option<SeriesName>,
32 /// Parent count of the evaluated commit; `None` for patch and PR
33 /// inputs (no commit identity) and pre-v7 envelopes.
34 #[serde(default)]
35 pub parent_count: Option<NonZeroU32>,
36 /// Paths the apply forecast predicts will conflict on the target.
37 /// Zero when the forecast is clean or was not evaluated; the
38 /// verdict kind never reflects it, so the row states it itself.
39 #[serde(default)]
40 pub apply_conflicts: u32,
41
42 /// Symbol-axis findings on the target. Zero when the axis found
43 /// nothing or was not evaluated; the verdict kind on an
44 /// all-inapplicable row never reflects it, so the row states it.
45 #[serde(default)]
46 pub target_findings: u32,
47}
48
49/// Kind-only projection of `Verdict`: drops the variant payloads so a
50/// summary row stays single-line.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
52#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
53#[serde(rename_all = "snake_case")]
54pub enum VerdictKind {
55 Compatible,
56 RequiresAdaptation,
57 Incompatible,
58 Inapplicable,
59}
60
61impl From<&Verdict> for VerdictKind {
62 fn from(v: &Verdict) -> Self {
63 match v {
64 Verdict::Compatible => Self::Compatible,
65 Verdict::RequiresAdaptation { .. } => Self::RequiresAdaptation,
66 Verdict::Incompatible { .. } => Self::Incompatible,
67 Verdict::Inapplicable { .. } => Self::Inapplicable,
68 }
69 }
70}
71
72impl VerdictKind {
73 /// Snake-case wire form. Matches the `#[serde(rename_all)]` projection.
74 #[must_use]
75 pub fn as_str(self) -> &'static str {
76 match self {
77 Self::Compatible => "compatible",
78 Self::RequiresAdaptation => "requires_adaptation",
79 Self::Incompatible => "incompatible",
80 Self::Inapplicable => "inapplicable",
81 }
82 }
83}