Skip to main content

ntfs_mac_core/
error.rs

1//! Typed errors + POSIX-style exit codes used by the CLI/GUI layer.
2
3use std::path::PathBuf;
4
5use thiserror::Error;
6
7/// Exit code surface for the CLI. Kept small and stable so scripts can
8/// branch on it without parsing stderr.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[repr(u8)]
11pub enum ExitCode {
12    /// Command succeeded.
13    Ok = 0,
14    /// Generic failure (I/O, unexpected stderr).
15    Failure = 1,
16    /// Missing or unsatisfiable dependency (`ntfs-3g`, macFUSE/FUSE-T).
17    MissingDependency = 2,
18    /// User declined an interactive prompt (Ctrl-C, "no" to confirm).
19    UserCancelled = 3,
20    /// Confirmation phrase did not match.
21    ConfirmationMismatch = 4,
22    /// Input/argument validation error (unknown device, invalid label).
23    InvalidArgument = 5,
24}
25
26impl From<ExitCode> for u8 {
27    fn from(e: ExitCode) -> Self {
28        e as u8
29    }
30}
31
32/// Library error type. Every variant carries enough context that a
33/// user-facing message can be derived without re-running the failing
34/// command.
35#[derive(Debug, Error)]
36pub enum Error {
37    /// A subprocess exited non-zero. `stderr` is preserved verbatim
38    /// so the caller can show the underlying tool's diagnosis.
39    #[error("command `{cmd}` exited with status {status}: {stderr}")]
40    CommandFailed {
41        cmd: String,
42        status: i32,
43        stderr: String,
44        #[source]
45        io: Option<std::io::Error>,
46    },
47
48    /// The target binary is not on PATH. `hint` is a copy-pastable
49    /// fix instruction.
50    #[error("missing dependency `{binary}`: {detail}")]
51    MissingDependency {
52        binary: String,
53        detail: String,
54        hint: Option<String>,
55        #[source]
56        io: Option<std::io::Error>,
57    },
58
59    /// Config file could not be read or written.
60    #[error("config error: {0}")]
61    Config(#[from] ConfigError),
62
63    /// The parsed `diskutil` output did not contain an NTFS volume
64    /// matching the user's request.
65    #[error("no matching NTFS volume for `{pattern}`")]
66    NoMatch { pattern: String },
67
68    /// Confirmation phrase did not match.
69    #[error("confirmation mismatch: expected `{expected}`, got `{actual}`")]
70    ConfirmationMismatch { expected: String, actual: String },
71
72    /// User pressed Ctrl-C or answered "no" to a confirmation prompt.
73    #[error("user cancelled")]
74    Cancelled,
75
76    /// Argument validation failure (empty label, unknown device, ...).
77    #[error("invalid argument: {0}")]
78    InvalidArgument(String),
79
80    /// Underlying I/O error that does not fit into a more specific
81    /// variant. Kept narrow so `?` from `std::fs::read_to_string` and
82    /// the like still works.
83    #[error("i/o error: {0}")]
84    Io(#[from] std::io::Error),
85
86    /// Serialization / parsing error (plist, toml, json).
87    #[error("serialization error: {0}")]
88    Serde(String),
89}
90
91#[derive(Debug, Error)]
92pub enum ConfigError {
93    #[error("failed to read config at {path}: {reason}")]
94    Read { path: PathBuf, reason: String },
95    #[error("failed to write config to {path}: {reason}")]
96    Write { path: PathBuf, reason: String },
97    #[error("config parse error at {path}: {reason}")]
98    Parse { path: PathBuf, reason: String },
99}
100
101impl From<ConfigError> for std::io::Error {
102    fn from(e: ConfigError) -> Self {
103        std::io::Error::other(e.to_string())
104    }
105}
106
107/// Library result alias.
108pub type Result<T> = std::result::Result<T, Error>;