agent_first_data/cli_spec/
error.rs1use serde::Serialize;
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
5#[serde(rename_all = "snake_case")]
6pub enum CliErrorRule {
7 InvalidUtf8,
8 UnknownCommand,
9 UnknownArgument,
10 MissingArgumentValue,
11 InvalidArgumentValue,
12 DuplicateArgument,
13 UnexpectedPositional,
14 UnregisteredCombination,
15}
16
17impl CliErrorRule {
18 pub const fn as_str(self) -> &'static str {
19 match self {
20 Self::InvalidUtf8 => "invalid_utf8",
21 Self::UnknownCommand => "unknown_command",
22 Self::UnknownArgument => "unknown_argument",
23 Self::MissingArgumentValue => "missing_argument_value",
24 Self::InvalidArgumentValue => "invalid_argument_value",
25 Self::DuplicateArgument => "duplicate_argument",
26 Self::UnexpectedPositional => "unexpected_positional",
27 Self::UnregisteredCombination => "unregistered_combination",
28 }
29 }
30
31 pub const fn code(self) -> &'static str {
38 match self {
39 Self::InvalidUtf8 => "cli_invalid_utf8",
40 Self::UnknownCommand => "cli_unknown_command",
41 Self::UnknownArgument => "cli_unknown_argument",
42 Self::MissingArgumentValue => "cli_missing_argument_value",
43 Self::InvalidArgumentValue => "cli_invalid_argument_value",
44 Self::DuplicateArgument => "cli_duplicate_argument",
45 Self::UnexpectedPositional => "cli_unexpected_positional",
46 Self::UnregisteredCombination => "cli_unregistered_combination",
47 }
48 }
49}
50
51#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct CliError {
59 pub rule: CliErrorRule,
60 pub message: String,
61 pub hint: String,
62}
63
64impl CliError {
65 pub(super) fn new(
66 rule: CliErrorRule,
67 command_path: String,
68 message: impl Into<String>,
69 ) -> Self {
70 let hint = match rule {
76 CliErrorRule::UnregisteredCombination => {
77 format!("run `{command_path} --help` and choose one registered combination")
78 }
79 CliErrorRule::UnknownCommand => {
80 format!("run `{command_path} --help` to list the registered commands")
81 }
82 CliErrorRule::UnknownArgument | CliErrorRule::UnexpectedPositional => {
83 format!("run `{command_path} --help` to list the arguments each shape accepts")
84 }
85 CliErrorRule::InvalidArgumentValue => format!(
86 "pass a value of the type and range `{command_path} --help` lists for that argument"
87 ),
88 CliErrorRule::MissingArgumentValue => format!(
89 "pass a value right after the argument; `{command_path} --help` shows its value name"
90 ),
91 CliErrorRule::DuplicateArgument => format!(
92 "pass the argument once; `{command_path} --help` shows which arguments repeat"
93 ),
94 CliErrorRule::InvalidUtf8 => {
95 format!("pass every argument as valid UTF-8; `{command_path} --help` lists them")
96 }
97 };
98 Self {
99 rule,
100 message: message.into(),
101 hint,
102 }
103 }
104
105 pub(super) fn unregistered(command_path: String) -> Self {
106 Self::new(
107 CliErrorRule::UnregisteredCombination,
108 command_path,
109 "arguments do not match a registered CLI combination",
110 )
111 }
112
113 pub const fn exit_code(&self) -> u8 {
118 2
119 }
120}
121
122impl std::fmt::Display for CliError {
123 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124 f.write_str(&self.message)
125 }
126}
127
128impl std::error::Error for CliError {}
129
130pub(super) fn invalid_value(command_path: &str, name: String, message: &str) -> CliError {
131 CliError::new(
132 CliErrorRule::InvalidArgumentValue,
133 command_path.to_string(),
134 format!("invalid value for `{name}`: {message}"),
135 )
136}
137
138pub(super) fn missing_value(command_path: &str, name: &str) -> CliError {
139 CliError::new(
140 CliErrorRule::MissingArgumentValue,
141 command_path.to_string(),
142 format!("argument `{name}` requires a value"),
143 )
144}
145
146pub(super) fn duplicate_error(command_path: &str, name: &str) -> CliError {
147 CliError::new(
148 CliErrorRule::DuplicateArgument,
149 command_path.to_string(),
150 format!("argument `{name}` may not be repeated"),
151 )
152}