use microsandbox_runtime::boot_error::{BootError, BootErrorStage};
use crate::ui::{self, ErrorLine};
pub fn render(name: &str, err: &BootError) {
let header = format!("failed to start {:?}", name);
let stage = stage_label(err.stage);
let cause = format!("{}: {}", stage, err.message);
let hint_text = stage_hint(err);
let log_pointer = format!("run `msb logs --source system {name}` for full diagnostics");
let mut lines: Vec<ErrorLine<'_>> = Vec::with_capacity(3);
lines.push(ErrorLine::Cause(&cause));
if let Some(ref h) = hint_text {
lines.push(ErrorLine::Hint(h));
}
lines.push(ErrorLine::Hint(&log_pointer));
ui::error_with_lines(&header, &lines);
}
fn stage_label(stage: BootErrorStage) -> &'static str {
match stage {
BootErrorStage::Mount => "mount",
BootErrorStage::BuildVm => "build_vm",
BootErrorStage::Config => "config",
BootErrorStage::Network => "network",
BootErrorStage::Image => "image",
BootErrorStage::Other => "other",
}
}
fn stage_hint(err: &BootError) -> Option<String> {
match (err.stage, err.errno) {
(BootErrorStage::Mount, Some(2)) => Some(extract_mount_hint(&err.message)),
(BootErrorStage::Mount, Some(13)) => {
Some("the host path is not readable by msb (check permissions)".into())
}
(BootErrorStage::Image, Some(2)) => {
Some("rootfs not found — try `msb pull <image>` first".into())
}
(BootErrorStage::Network, Some(48)) | (BootErrorStage::Network, Some(98)) => Some(
"a port is already bound — try a different host port or stop the other process".into(),
),
_ => None,
}
}
fn extract_mount_hint(_message: &str) -> String {
"the host path for one of the mounts does not exist".into()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mount_enoent_has_hint() {
let err = BootError {
t: "2026-04-30T20:32:59.690Z".into(),
stage: BootErrorStage::Mount,
errno: Some(2),
message: "mount foo: No such file or directory (os error 2)".into(),
};
assert!(stage_hint(&err).is_some());
}
#[test]
fn other_with_no_errno_has_no_hint() {
let err = BootError {
t: "2026-04-30T20:32:59.690Z".into(),
stage: BootErrorStage::Other,
errno: None,
message: "weird".into(),
};
assert!(stage_hint(&err).is_none());
}
#[test]
fn network_eaddrinuse_macos_and_linux() {
let err = BootError {
t: "x".into(),
stage: BootErrorStage::Network,
errno: Some(48), message: "bind: in use".into(),
};
assert!(stage_hint(&err).is_some());
let err2 = BootError {
t: "x".into(),
stage: BootErrorStage::Network,
errno: Some(98), message: "bind: in use".into(),
};
assert!(stage_hint(&err2).is_some());
}
}