rahti_native/error.rs
1//! What a native host is told when startup cannot continue.
2//!
3//! Every failure in this crate is a value, never an exit. A packaged
4//! application has no terminal: a process that calls `std::process::exit`
5//! during startup is, from the user's side, a program that did not open. The
6//! host is expected to show [`NativeError`] in a dialog and stop, which is the
7//! difference between "the database could not be created" and nothing at all.
8
9use std::fmt;
10use std::path::{Path, PathBuf};
11
12/// A native startup or packaging failure.
13///
14/// `step` names the part of startup that failed so a host can say *where*
15/// without parsing the message, and `Display` is the sentence to show.
16#[derive(Debug)]
17pub struct NativeError {
18 /// Which part of startup failed: `"paths"`, `"listener"`, `"secret"`,
19 /// `"assets"`, `"config"`, `"server"`.
20 pub step: &'static str,
21 /// The failure, phrased for a person.
22 pub message: String,
23 /// The path involved, when there was one.
24 pub path: Option<PathBuf>,
25}
26
27impl NativeError {
28 pub fn new(step: &'static str, message: impl Into<String>) -> Self {
29 NativeError {
30 step,
31 message: message.into(),
32 path: None,
33 }
34 }
35
36 pub fn at(step: &'static str, path: impl AsRef<Path>, message: impl Into<String>) -> Self {
37 NativeError {
38 step,
39 message: message.into(),
40 path: Some(path.as_ref().to_path_buf()),
41 }
42 }
43
44 /// An `io::Error` with the path it happened to, which the standard error
45 /// does not carry and which is the only part a user can act on.
46 pub fn io(step: &'static str, path: impl AsRef<Path>, error: std::io::Error) -> Self {
47 NativeError::at(step, path, error.to_string())
48 }
49}
50
51impl fmt::Display for NativeError {
52 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53 match &self.path {
54 Some(path) => write!(f, "{} ({})", self.message, path.display()),
55 None => write!(f, "{}", self.message),
56 }
57 }
58}
59
60impl std::error::Error for NativeError {}