The Error Pipeline: From Parsing Failures to Compiler-Style Diagnostics
When a command-line user inputs an incorrect argument, a missing parameter, or an invalid value, clish does not just crash or output a generic usage string. Instead, it generates a beautiful, color-coded, compiler-style error report pointing directly to the problematic token.
This page explains how errors are represented, passed across the runtime boundary, and rendered to the terminal.
The Error Registry (ErrorKind)
Every parsing and validation failure maps to a specific variant of the ErrorKind enum:
pub enum ErrorKind {
UnknownCommand { name: String },
UnknownOption { token: String },
MissingValue { name: String },
MissingArgument { name: String },
InvalidValue { token: String, expected: String },
InvalidChoice { token: String, expected: String },
Conflict { name: String, other: String },
Requires { name: String, requires: String },
OneshotWithOtherCommands,
}
Each variant is responsible for capturing the context of the error:
* Token-based errors (UnknownOption, InvalidValue, InvalidChoice) capture the exact string the user typed.
* Structural errors (Conflict, Requires) capture the long names of the parameters whose constraints were violated.
The Boundary Round-Trip Design
An interesting architectural aspect of clish is the string round-trip boundary between the generated macro closure and the runner:
graph TD
subgraph closure ["Run Closure (macro-expanded)"]
ErrEnum[ErrorKind] -->|"impl From#60;ErrorKind#62; for String"| ErrStr[String Representation]
end
ErrStr -->|Result::Err| Boundary((Closure Boundary))
subgraph apprun ["App::run (clish-core)"]
Boundary --> Catch[Catch Err#40;String#41;]
Catch -->|parse_error_string| Recover[Reconstruct ErrorKind]
Recover -->|print_error| StdErr[Format and print to stderr]
end
Why use a string boundary?
- Crate Decoupling: The runtime closure pointer registered in the static command registry uses a clean, generic Rust signature:
run: fn(&[String]) -> Result<(), String>. Using basic types prevents unnecessary exposure of the internalErrorKindenum in the function pointer type. - User Errors Compatibility: Returning a
Result<(), String>leaves the door open to support custom errors returned from user functions, allowing them to propagate through the same error handling loop.
Reconstructing the Error
Inside App::run(), if the command execution closure returns an Err(String), clish-core runs a parsing routine called parse_error_string to reconstruct the original ErrorKind. It does this by checking prefixes like "unknown option: ", "missing value for option: ", or "invalid value '". Once reconstructed, the typed error is passed to the diagnostic printer.
Diagnostic Output Rendering (print_error)
Once the ErrorKind is recovered, the print_error function creates a diagnostic display that mimics the Rust compiler's error output. It prints three distinct sections:
1. Error Label & Message: e.g., error: unknown argument --verbose.
2. Source Code Context (print_source_line): Shows the exact CLI invocation with an underline pointing at the bad argument.
3. Actionable Hint: e.g., = hint: run 'myapp deploy --help' for more information.
How Pointing works (print_source_line)
To underline the exact error location, clish reconstructs the user's input line using the application name and the argument slice:
let line = format!("{} {}", app.name, args.join(" "));
|).
* It pads the line with spaces up to the column offset and prints a caret highlight sequence (^^^^) matching the length of the invalid token.
Example Outputs
1. Unknown Command
If the user runs myapp deplooy --force, clish detects deplooy as an invalid command:
error: unknown command 'deplooy'
|
1 | myapp deplooy --force
| ^^^^^^^
|
= hint: run 'myapp --help' for available commands
2. Missing Named Option Value
If the user passes --port without a value:
error: missing value for --port
|
1 | myapp deploy --port
| ^^^^^^
|
= note: --port expects a value
3. Prerequisite Constraint Violated
If --port requires --host to be configured, but the user only supplied --port 80:
error: missing required argument: --port requires --host
|
1 | myapp deploy --port 80
| ^^^^^^
|
= hint: run 'myapp deploy --help' for more information
Next, let's explore Help Rendering to see how parameter metadata is formatted into aligned CLI reference screens.