horto_os_ui_shared/error.rs
1//! Typed setup/ops errors for the Horto engine.
2//!
3//! # Layering (ITC habit)
4//!
5//! | Layer | Crate | Tool |
6//! | ----- | ----- | ---- |
7//! | Library / step API | `horto-os-ui-shared` | `thiserror` via [`HortoError`] |
8//! | Process binaries (CLI, TUI, API, KPI) | each binary | `anyhow` at `main`; `?` maps [`HortoError`] |
9//! | Diagnostics | shared emits; CLI/API/KPI/desktop call [`crate::init_tracing`] | `tracing` only (stderr via subscriber); TUI uses [`crate::HostContext::logs`] |
10//!
11//! Do **not** put `thiserror` on every surface crate. Surfaces that only talk HTTP
12//! (KPI, web) never see [`HortoError`]. Surfaces that call the engine convert with `?`.
13
14use thiserror::Error;
15
16/// Crate-wide result alias for the setup engine.
17pub type Result<T> = std::result::Result<T, HortoError>;
18
19/// Failures from steps, kits, resume, and host ops.
20#[derive(Debug, Error)]
21pub enum HortoError {
22 /// Free-form message when no tighter variant fits yet.
23 #[error("{0}")]
24 Message(String),
25
26 /// Apply mode needs root (sudo).
27 #[error("root required for apply mode; re-run with sudo")]
28 RootRequired,
29
30 /// Step id is not registered.
31 #[error("unknown step id: {0}")]
32 UnknownStep(String),
33
34 /// Step id exists but is not in the selected Full/Minimal pipeline.
35 #[error("step {step} is not in the {kind} pipeline")]
36 NotInPipeline {
37 /// Step id requested.
38 step: String,
39 /// Pipeline kind (`full` / `minimal`).
40 kind: String,
41 },
42
43 /// Required dependency steps are incomplete.
44 #[error("step {0} depends on incomplete step(s): {1}")]
45 MissingDependency(String, String),
46
47 /// Embedded asset path missing from the binary.
48 #[error("missing embedded asset: {0}")]
49 EmbedMissing(String),
50
51 /// External process failed (apt, systemctl, docker, iptables, …).
52 #[error("command failed: {program}: {detail}")]
53 CommandFailed {
54 /// Program name or short label.
55 program: String,
56 /// Status / stderr summary.
57 detail: String,
58 },
59
60 /// Filesystem IO.
61 #[error("IO error: {0}")]
62 Io(#[from] std::io::Error),
63
64 /// JSON encode/decode.
65 #[error("JSON error: {0}")]
66 Json(#[from] serde_json::Error),
67}
68
69impl HortoError {
70 /// Build a [`Message`](Self::Message) error.
71 pub fn msg(s: impl Into<String>) -> Self {
72 Self::Message(s.into())
73 }
74
75 /// External command failure helper.
76 pub fn command(program: impl Into<String>, detail: impl Into<String>) -> Self {
77 Self::CommandFailed {
78 program: program.into(),
79 detail: detail.into(),
80 }
81 }
82
83 /// Suggested process exit code for CLI surfaces.
84 #[must_use]
85 pub const fn exit_code(&self) -> i32 {
86 match self {
87 Self::RootRequired => 77,
88 Self::UnknownStep(_) | Self::NotInPipeline { .. } => 2,
89 _ => 1,
90 }
91 }
92}