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, vocabulary};
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
49vocabulary!(
50 /// Kind-only projection of `Verdict`: drops the variant payloads so a
51 /// summary row stays single-line.
52 pub enum VerdictKind: "verdict kind" {
53 Compatible => "compatible",
54 RequiresAdaptation => "requires_adaptation",
55 Incompatible => "incompatible",
56 Inapplicable => "inapplicable",
57 }
58);
59
60impl From<&Verdict> for VerdictKind {
61 fn from(v: &Verdict) -> Self {
62 match v {
63 Verdict::Compatible => Self::Compatible,
64 Verdict::RequiresAdaptation { .. } => Self::RequiresAdaptation,
65 Verdict::Incompatible { .. } => Self::Incompatible,
66 Verdict::Inapplicable { .. } => Self::Inapplicable,
67 }
68 }
69}