Skip to main content

usage/
error.rs

1use miette::{Diagnostic, NamedSource, SourceSpan};
2use thiserror::Error;
3
4/// Everything that can go wrong reading a spec or a command line against one.
5///
6/// `#[non_exhaustive]`, so a caller matching on it needs a `_` arm. That is the point:
7/// this enum grows every time the spec learns to say something new — `MissingGroup`
8/// arrived with groups, `ArgRequiresDoubleDash` with `double_dash` — and without this
9/// each one is a major release for everyone downstream.
10#[derive(Error, Diagnostic, Debug)]
11#[non_exhaustive]
12pub enum UsageErr {
13    #[error("Invalid flag `{token}`: {reason}")]
14    InvalidFlag {
15        token: String,
16        reason: String,
17        #[label("{reason}")]
18        span: SourceSpan,
19        #[source_code]
20        input: String,
21    },
22
23    #[error("Missing required flag: --{0} <{0}>")]
24    MissingFlag(String),
25
26    #[error("Flag --{0} cannot be used multiple times")]
27    DuplicateFlag(String),
28
29    /// A required group had none of its members given.
30    ///
31    /// Its own variant rather than a [`UsageErr::MissingFlag`] holding a sentence,
32    /// because there is no one flag to name: the group is the thing that was not
33    /// satisfied, and a caller that renders errors itself needs the members as members.
34    #[error("Missing one of the required flags in group {group}: {members}")]
35    MissingGroup { group: String, members: String },
36
37    #[error("Invalid usage config")]
38    InvalidInput(
39        String,
40        #[label = "{0}"] SourceSpan,
41        #[source_code] NamedSource<String>,
42    ),
43
44    #[error("Missing required arg: <{0}>")]
45    MissingArg(String),
46
47    /// A command that declares `subcommand_required` was given none.
48    ///
49    /// The spec could say this and the parser did not read it, so `mise generate` — which
50    /// declares it — parsed as though it were a complete invocation. usage-argv and clap both
51    /// refuse it.
52    #[error("`{0}` needs a subcommand: one of {1}")]
53    MissingSubcommand(String, String),
54
55    #[error("Argument <{0}> can only be set after a `--` separator")]
56    ArgRequiresDoubleDash(String),
57
58    #[error("{0}")]
59    Help(String),
60
61    #[error("{0}")]
62    Version(String),
63
64    #[error("Invalid usage config")]
65    #[diagnostic(transparent)]
66    Miette(#[from] miette::MietteError),
67
68    #[error(transparent)]
69    IO(#[from] std::io::Error),
70
71    #[error(transparent)]
72    Strum(#[from] strum::ParseError),
73
74    #[error(transparent)]
75    FromUtf8Error(#[from] std::string::FromUtf8Error),
76
77    #[cfg(feature = "tera")]
78    #[error(transparent)]
79    TeraError(#[from] tera::Error),
80
81    #[error(transparent)]
82    #[diagnostic(transparent)]
83    KdlError(#[from] kdl::KdlError),
84
85    /// A file the spec model was asked to read could not be read.
86    ///
87    /// Carries the path as well as the io error: "No such file or directory" on its own
88    /// names nothing, and this is reported for spec files given on a command line.
89    #[error("{0}\nFile: {1}")]
90    #[diagnostic(code(usage::file))]
91    FileError(std::io::Error, std::path::PathBuf),
92
93    /// A `run=` script could not be run, exited non-zero, or produced output usage
94    /// could not read. The message names the shell and the script.
95    #[error("{0}")]
96    #[diagnostic(code(usage::shell))]
97    ShellError(String),
98
99    #[error("Variadic argument <{name}> requires at least {min} value(s), got {got}")]
100    VarArgTooFew {
101        name: String,
102        min: usize,
103        got: usize,
104    },
105
106    #[error("Variadic argument <{name}> accepts at most {max} value(s), got {got}")]
107    VarArgTooMany {
108        name: String,
109        max: usize,
110        got: usize,
111    },
112
113    #[error("Variadic flag --{name} requires at least {min} value(s), got {got}")]
114    VarFlagTooFew {
115        name: String,
116        min: usize,
117        got: usize,
118    },
119
120    #[error("Variadic flag --{name} accepts at most {max} value(s), got {got}")]
121    VarFlagTooMany {
122        name: String,
123        max: usize,
124        got: usize,
125    },
126
127    #[error("Invalid file path: {0}")]
128    InvalidPath(String),
129
130    #[error("Invalid spec view: {0}")]
131    InvalidView(String),
132
133    #[error("Invalid value for {name}: {value}: {reason}")]
134    InvalidValue {
135        name: String,
136        value: String,
137        reason: String,
138    },
139
140    #[error("Unsupported shell: {0}")]
141    UnsupportedShell(String),
142
143    #[error("No injected output was provided for mount command: {0}")]
144    MissingMountOutput(String),
145}
146pub type Result<T> = std::result::Result<T, UsageErr>;
147
148#[macro_export]
149macro_rules! bail_parse {
150    ($ctx:expr, $span:expr, $fmt:literal) => {{
151        let span: miette::SourceSpan = ($span.offset(), $span.len()).into();
152        let msg = format!($fmt);
153        let err = $ctx.build_err(msg, span);
154        return std::result::Result::Err(err);
155    }};
156    ($ctx:expr, $span:expr, $fmt:literal, $($arg:tt)*) => {{
157        let span: miette::SourceSpan = ($span.offset(), $span.len()).into();
158        let msg = format!($fmt, $($arg)*);
159        let err = $ctx.build_err(msg, span);
160        return std::result::Result::Err(err);
161    }};
162}