Skip to main content

horto_os_ui_shared/
step.rs

1//! Versioned setup step trait implemented by modules under [`crate::steps`].
2
3use crate::context::HostContext;
4use crate::error::Result;
5
6/// One setup / ops step. Implemented by isolated modules under `steps/`.
7///
8/// Steps are versioned (`step_version`) and may declare dependencies (`depends_on`).
9/// Surfaces call [`crate::setup_run`] / [`crate::setup_step`] rather than steps directly.
10pub trait Step: Send + Sync {
11    /// Stable short id (`s1`, `m1`, `d1`, …).
12    fn id(&self) -> &'static str;
13    /// Human-readable title for TUI / status.
14    fn title(&self) -> &'static str;
15    /// Legacy shell script this step absorbs (documentation / tip-sync).
16    fn reference_script(&self) -> &'static str;
17    /// Schema of the step trait itself (rarely bumped).
18    fn schema_version(&self) -> u32 {
19        1
20    }
21    /// Implementation version; resume marks stale when this increases.
22    fn step_version(&self) -> u32;
23    /// Step ids that must be `Done` before this step may run.
24    fn depends_on(&self) -> &'static [&'static str] {
25        &[]
26    }
27    /// Heuristic: already completed on this host layout.
28    fn is_done(&self, ctx: &HostContext) -> bool;
29    /// Record planned actions without mutating the host (also used in dry-run apply).
30    ///
31    /// # Errors
32    ///
33    /// Returns [`crate::HortoError`] when planning cannot inspect host state or build the action list.
34    fn plan(&self, ctx: &mut HostContext) -> Result<Vec<crate::context::PlannedAction>>;
35    /// Execute or plan according to [`HostContext::mode`](crate::ApplyMode).
36    ///
37    /// # Errors
38    ///
39    /// Returns [`crate::HortoError`] when apply fails (I/O, missing deps, cancelled confirms, …).
40    fn apply(&self, ctx: &mut HostContext) -> Result<()>;
41    /// Advise a reboot after a successful apply.
42    fn needs_reboot_after(&self) -> bool {
43        false
44    }
45    /// True when apply can overwrite live host config.
46    fn destructive(&self) -> bool {
47        false
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crate::context::{ApplyMode, HostContext};
55    use crate::pipeline::SetupKind;
56
57    /// Minimal `Step` that leaves trait defaults in place.
58    struct DefaultsOnly;
59
60    impl Step for DefaultsOnly {
61        fn id(&self) -> &'static str {
62            "t0"
63        }
64        fn title(&self) -> &'static str {
65            "defaults-only"
66        }
67        fn reference_script(&self) -> &'static str {
68            "none"
69        }
70        fn step_version(&self) -> u32 {
71            1
72        }
73        fn is_done(&self, _ctx: &HostContext) -> bool {
74            false
75        }
76        fn plan(&self, _ctx: &mut HostContext) -> Result<Vec<crate::context::PlannedAction>> {
77            Ok(Vec::new())
78        }
79        fn apply(&self, _ctx: &mut HostContext) -> Result<()> {
80            Ok(())
81        }
82    }
83
84    #[test]
85    fn trait_defaults_are_stable() {
86        let concrete = DefaultsOnly;
87        let step: &dyn Step = &concrete;
88        assert_eq!(step.schema_version(), 1);
89        assert_eq!(step.depends_on(), &[] as &[&str]);
90        assert!(!step.needs_reboot_after());
91        assert!(!step.destructive());
92        let mut ctx = HostContext::new(ApplyMode::DryRun, SetupKind::Minimal);
93        assert!(!step.is_done(&ctx));
94        assert_eq!(step.plan(&mut ctx).unwrap(), Vec::new());
95        step.apply(&mut ctx).unwrap();
96        assert_eq!(step.id(), "t0");
97        assert_eq!(step.title(), "defaults-only");
98        assert_eq!(step.reference_script(), "none");
99        assert_eq!(step.step_version(), 1);
100    }
101}