Skip to main content

cu_profiler_core/budget/
result.rs

1//! Structured outcomes of evaluating a [`super::BudgetPolicy`].
2
3use serde::{Deserialize, Serialize};
4
5/// Tri-state outcome of a single policy check.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum PolicyStatus {
9    /// Within limits.
10    Pass,
11    /// Within hard limits but past a soft/warning threshold.
12    Warn,
13    /// A hard limit was exceeded.
14    Fail,
15}
16
17impl PolicyStatus {
18    /// Uppercase label (`PASS` / `WARN` / `FAIL`).
19    #[must_use]
20    pub fn label(self) -> &'static str {
21        match self {
22            Self::Pass => "PASS",
23            Self::Warn => "WARN",
24            Self::Fail => "FAIL",
25        }
26    }
27
28    /// The more severe of two statuses (`Fail` > `Warn` > `Pass`).
29    #[must_use]
30    pub fn max(self, other: Self) -> Self {
31        self.max_rank(other)
32    }
33
34    fn rank(self) -> u8 {
35        match self {
36            Self::Pass => 0,
37            Self::Warn => 1,
38            Self::Fail => 2,
39        }
40    }
41
42    fn max_rank(self, other: Self) -> Self {
43        if self.rank() >= other.rank() {
44            self
45        } else {
46            other
47        }
48    }
49}
50
51/// Severity classification, independent of pass/fail (a `Warn` can still be
52/// `Error`-severity for a critical scenario).
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "lowercase")]
55pub enum Severity {
56    /// Informational.
57    Info,
58    /// Worth attention.
59    Warning,
60    /// Blocking.
61    Error,
62}
63
64/// The result of evaluating one policy clause.
65#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66pub struct PolicyResult {
67    /// Stable identifier of the clause, e.g. `"absolute_max_cu"`.
68    pub policy_id: String,
69    /// Pass/warn/fail outcome.
70    pub status: PolicyStatus,
71    /// Severity classification.
72    pub severity: Severity,
73    /// The measured value, if numeric.
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub actual: Option<f64>,
76    /// The threshold compared against, if numeric.
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub expected: Option<f64>,
79    /// Human-readable explanation.
80    pub message: String,
81    /// Optional Solana-specific remediation hint.
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub remediation: Option<String>,
84}
85
86impl PolicyResult {
87    /// Convenience constructor for a passing result.
88    #[must_use]
89    pub fn pass(policy_id: impl Into<String>, message: impl Into<String>) -> Self {
90        Self {
91            policy_id: policy_id.into(),
92            status: PolicyStatus::Pass,
93            severity: Severity::Info,
94            actual: None,
95            expected: None,
96            message: message.into(),
97            remediation: None,
98        }
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn status_max_picks_worst() {
108        assert_eq!(
109            PolicyStatus::Pass.max(PolicyStatus::Fail),
110            PolicyStatus::Fail
111        );
112        assert_eq!(
113            PolicyStatus::Warn.max(PolicyStatus::Pass),
114            PolicyStatus::Warn
115        );
116    }
117}