Skip to main content

adze_bdd_scenario_core/
lib.rs

1//! Core BDD scenario status and ledger-row contracts.
2//!
3//! This crate isolates the scenario representation from broader grid concerns so
4//! policy and reporting crates can depend on a smaller SRP-focused surface.
5
6#![forbid(unsafe_op_in_unsafe_fn)]
7#![deny(missing_docs)]
8#![cfg_attr(feature = "strict_api", deny(unreachable_pub))]
9#![cfg_attr(not(feature = "strict_api"), warn(unreachable_pub))]
10#![cfg_attr(feature = "strict_docs", deny(missing_docs))]
11#![cfg_attr(not(feature = "strict_docs"), allow(missing_docs))]
12
13use core::fmt;
14
15/// BDD status phase for a scenario.
16///
17/// # Examples
18///
19/// ```
20/// use adze_bdd_scenario_core::BddPhase;
21///
22/// let phase = BddPhase::Core;
23/// assert_eq!(phase, BddPhase::Core);
24/// ```
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum BddPhase {
27    /// Parser-core validation phase (glr-core).
28    Core,
29    /// Runtime integration phase (runtime/runtime2).
30    Runtime,
31}
32
33impl fmt::Display for BddPhase {
34    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35        write!(f, "{self:?}")
36    }
37}
38
39/// Scenario status for a feature matrix row.
40///
41/// # Examples
42///
43/// ```
44/// use adze_bdd_scenario_core::BddScenarioStatus;
45///
46/// let done = BddScenarioStatus::Implemented;
47/// assert!(done.implemented());
48/// assert_eq!(done.label(), "IMPLEMENTED");
49///
50/// let pending = BddScenarioStatus::Deferred { reason: "wip" };
51/// assert!(!pending.implemented());
52/// assert_eq!(pending.detail(), "wip");
53/// ```
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
55pub enum BddScenarioStatus {
56    /// Completed in a given phase.
57    Implemented,
58    /// Deferred with reason text.
59    Deferred {
60        /// Explanation for why the scenario is deferred.
61        reason: &'static str,
62    },
63}
64
65impl fmt::Display for BddScenarioStatus {
66    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67        match self {
68            Self::Implemented => write!(f, "Implemented"),
69            Self::Deferred { reason } => write!(f, "Deferred: {reason}"),
70        }
71    }
72}
73
74impl BddScenarioStatus {
75    /// Whether this scenario is complete.
76    pub const fn implemented(self) -> bool {
77        matches!(self, Self::Implemented)
78    }
79
80    /// Status icon used in logs and summaries.
81    pub const fn icon(self) -> &'static str {
82        match self {
83            Self::Implemented => "✅",
84            Self::Deferred { .. } => "⏳",
85        }
86    }
87
88    /// Short status label for printouts.
89    pub const fn label(self) -> &'static str {
90        match self {
91            Self::Implemented => "IMPLEMENTED",
92            Self::Deferred { .. } => "DEFERRED",
93        }
94    }
95
96    /// Optional detail text for deferred scenarios.
97    pub const fn detail(self) -> &'static str {
98        match self {
99            Self::Implemented => "",
100            Self::Deferred { reason } => reason,
101        }
102    }
103}
104
105/// Shared BDD scenario ledger entry.
106#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107pub struct BddScenario {
108    /// Scenario identifier.
109    pub id: u8,
110    /// Scenario title/description.
111    pub title: &'static str,
112    /// Reference document for this scenario.
113    pub reference: &'static str,
114    /// Core phase status (glr-core).
115    pub core_status: BddScenarioStatus,
116    /// Runtime phase status (runtime/runtime2 integration).
117    pub runtime_status: BddScenarioStatus,
118}
119
120impl BddScenario {
121    /// Return status for a given phase.
122    pub const fn status(self, phase: BddPhase) -> BddScenarioStatus {
123        match phase {
124            BddPhase::Core => self.core_status,
125            BddPhase::Runtime => self.runtime_status,
126        }
127    }
128}
129
130impl fmt::Display for BddScenario {
131    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132        write!(f, "Scenario {}: {}", self.id, self.title)
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139
140    #[test]
141    fn status_helpers_work() {
142        let done = BddScenarioStatus::Implemented;
143        assert!(done.implemented());
144        assert_eq!(done.icon(), "✅");
145        assert_eq!(done.label(), "IMPLEMENTED");
146        assert_eq!(done.detail(), "");
147
148        let deferred = BddScenarioStatus::Deferred { reason: "wip" };
149        assert!(!deferred.implemented());
150        assert_eq!(deferred.icon(), "⏳");
151        assert_eq!(deferred.label(), "DEFERRED");
152        assert_eq!(deferred.detail(), "wip");
153    }
154
155    #[test]
156    fn scenario_returns_status_for_phase() {
157        let scenario = BddScenario {
158            id: 7,
159            title: "GLR runtime explores both paths",
160            reference: "docs/archive/plans/BDD_GLR_CONFLICT_PRESERVATION.md",
161            core_status: BddScenarioStatus::Deferred { reason: "pending" },
162            runtime_status: BddScenarioStatus::Implemented,
163        };
164
165        assert_eq!(scenario.status(BddPhase::Core), scenario.core_status);
166        assert_eq!(scenario.status(BddPhase::Runtime), scenario.runtime_status);
167    }
168}