Skip to main content

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}