rust_args_parser/
error.rs

1/// Top-level error type.
2#[derive(Debug)]
3pub enum Error {
4    /// Invalid CLI input (unknown flag, bad value, missing value, etc.).
5    User(String),
6    /// Arbitrary user error bubbled from callbacks.
7    UserAny(Box<dyn std::error::Error + Send + Sync + 'static>),
8    /// Internal/structural error (should be rare).
9    Parse(String),
10    /// Exit with a specific code (e.g., `--help`). Optional message payload.
11    ExitMsg {
12        code: i32,
13        message: Option<String>,
14    },
15    /// Rich diagnostics
16    UnknownOption {
17        token: String,
18        suggestions: Vec<String>,
19    },
20    UnknownCommand {
21        token: String,
22        suggestions: Vec<String>,
23    },
24    MissingValue {
25        opt: String,
26    },
27    UnexpectedPositional {
28        token: String,
29    },
30}
31
32fn format_alternates(items: &[String]) -> String {
33    match items.len() {
34        0 => String::new(),
35        1 => format!("'{}'", items[0]),
36        2 => format!("'{}' or '{}'", items[0], items[1]),
37        _ => {
38            // 'a', 'b', or 'c'
39            let mut s = String::new();
40            for (i, it) in items.iter().enumerate() {
41                if i > 0 {
42                    s.push_str(if i + 1 == items.len() { ", or " } else { ", " });
43                }
44                s.push('\'');
45                s.push_str(it);
46                s.push('\'');
47            }
48            s
49        }
50    }
51}
52
53impl core::fmt::Display for Error {
54    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
55        match self {
56            Self::UserAny(e) => write!(f, "{e}"),
57            Self::Parse(s) | Self::User(s) => write!(f, "{s}"),
58            Self::ExitMsg { code, message } => {
59                if let Some(m) = message {
60                    write!(f, "{m}")?;
61                }
62                write!(f, " (exit {code})")
63            }
64            Self::UnknownOption { token, suggestions } => {
65                write!(f, "unknown option: '{token}'")?;
66                if !suggestions.is_empty() {
67                    write!(f, ". Did you mean {}?", format_alternates(suggestions))?;
68                }
69                Ok(())
70            },
71            Self::UnknownCommand { token, suggestions } => {
72                write!(f, "unknown command: '{token}'")?;
73                if !suggestions.is_empty() {
74                    write!(f, ". Did you mean {}?", format_alternates(suggestions))?;
75                }
76                Ok(())
77            }
78            Self::MissingValue { opt } => write!(f, "missing value for option: '{opt}'"),
79            Self::UnexpectedPositional { token } => write!(f, "unexpected positional: '{token}'"),
80        }
81    }
82}
83impl std::error::Error for Error {}
84
85impl From<Box<dyn std::error::Error + Send + Sync + 'static>> for Error {
86    fn from(e: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
87        Self::UserAny(e)
88    }
89}
90
91impl Error {
92    pub fn user<E>(e: E) -> Self
93    where
94        E: std::error::Error + Send + Sync + 'static,
95    {
96        Self::UserAny(Box::new(e))
97    }
98}
99
100/// Result alias.
101pub type Result<T, E = Error> = core::result::Result<T, E>;