Skip to content
This repository was archived by the owner on Jun 18, 2026. It is now read-only.

Commit e20a2f8

Browse files
docs: clarify why DriverOutcome exists and how to use it
Rewrite doc comments on DriverOutcome, DriverError, unwrap(), and into_result() to explain the motivation: --help/--version are early exits (code 0), not errors, and DriverOutcome.unwrap() handles them correctly where a bare Result would not. Add runnable doc tests showing both the simple (.unwrap()) and advanced (.into_result()) paths.
1 parent 21948ba commit e20a2f8

1 file changed

Lines changed: 107 additions & 48 deletions

File tree

crates/figue/src/driver.rs

Lines changed: 107 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -568,21 +568,31 @@ fn get_source_for_provenance(
568568
}
569569
}
570570

571-
/// Opaque result type for driver operations.
571+
/// The result of running the figue driver — either a parsed value or an early exit.
572572
///
573-
/// This type intentionally does NOT implement `Try`, so you cannot use `?` on it directly.
574-
/// This prevents accidentally propagating help/version/completions as errors (which would
575-
/// cause exit code 1 instead of 0).
573+
/// # Why this type exists
576574
///
577-
/// # Usage
575+
/// When a CLI user passes `--help` or `--version`, the program should print the
576+
/// relevant text and exit with code 0 (success). But these cases flow through the
577+
/// error path of the driver, since no config value `T` was produced. If `DriverOutcome`
578+
/// were just a `Result`, calling `.unwrap()` would panic on `--help`, and using `?`
579+
/// would propagate it as an error (exit code 1 instead of 0).
578580
///
579-
/// Use one of the following methods to extract the value:
580-
/// - [`.unwrap()`](Self::unwrap) - handles exits correctly, returns `T` (recommended for most cases)
581-
/// - [`.into_result()`](Self::into_result) - for advanced users who want to handle everything themselves
581+
/// `DriverOutcome` solves this by providing an [`.unwrap()`](Self::unwrap) method that
582+
/// does the right thing for every case:
582583
///
583-
/// # Example
584+
/// - Parsed successfully → returns `T`
585+
/// - `--help` / `--version` / `--completions` → prints to stdout, exits with code 0
586+
/// - Parse error → prints diagnostics to stderr, exits with code 1
584587
///
585-
/// ```rust
588+
/// This type intentionally does NOT implement `Try`, so you cannot use `?` on it
589+
/// accidentally.
590+
///
591+
/// # Usage
592+
///
593+
/// For most CLI programs, just call `.unwrap()`:
594+
///
595+
/// ```rust,no_run
586596
/// use facet::Facet;
587597
/// use figue::{self as args, FigueBuiltins};
588598
///
@@ -595,13 +605,41 @@ fn get_source_for_provenance(
595605
/// builtins: FigueBuiltins,
596606
/// }
597607
///
598-
/// // Using unwrap() - recommended for simple cases
599-
/// let args: Args = figue::from_slice(&["input.txt"]).unwrap();
600-
/// assert_eq!(args.file, "input.txt");
608+
/// fn main() {
609+
/// // If the user passes --help, this prints help and exits with code 0.
610+
/// // If the user passes invalid args, this prints an error and exits with code 1.
611+
/// // Otherwise, it returns the parsed Args.
612+
/// let args: Args = figue::from_std_args().unwrap();
613+
/// println!("Processing: {}", args.file);
614+
/// }
615+
/// ```
616+
///
617+
/// For tests or custom handling, use [`.into_result()`](Self::into_result) to get a
618+
/// `Result<DriverOutput<T>, DriverError>`:
619+
///
620+
/// ```rust
621+
/// use facet::Facet;
622+
/// use figue::{self as args, FigueBuiltins, DriverError};
623+
///
624+
/// #[derive(Facet)]
625+
/// struct Args {
626+
/// #[facet(args::positional, default)]
627+
/// file: Option<String>,
628+
///
629+
/// #[facet(flatten)]
630+
/// builtins: FigueBuiltins,
631+
/// }
632+
///
633+
/// // --help produces a DriverError::Help (exit code 0, not a "real" error)
634+
/// let outcome = figue::from_slice::<Args>(&["--help"]);
635+
/// let err = outcome.unwrap_err();
636+
/// assert!(err.is_help());
637+
/// assert_eq!(err.exit_code(), 0);
601638
///
602-
/// // Using into_result() - for custom error handling
603-
/// let result = figue::from_slice::<Args>(&["--help"]).into_result();
604-
/// assert!(result.is_err());
639+
/// // Successful parse returns DriverOutput containing the value
640+
/// let outcome = figue::from_slice::<Args>(&["input.txt"]);
641+
/// let output = outcome.into_result().unwrap();
642+
/// assert_eq!(output.value.file.as_deref(), Some("input.txt"));
605643
/// ```
606644
#[must_use = "this `DriverOutcome` may contain a help/version request that should be handled"]
607645
pub struct DriverOutcome<T>(Result<DriverOutput<T>, DriverError>);
@@ -631,9 +669,12 @@ impl<T> DriverOutcome<T> {
631669

632670
/// Convert to a standard `Result` for manual handling.
633671
///
634-
/// **Warning**: If you use `?` on this result and the error is `Help`, `Version`,
635-
/// or `Completions`, Rust's default error handling will exit with code 1 instead of 0.
636-
/// Consider using `.unwrap()` instead for correct exit behavior.
672+
/// Use this when you need to inspect the error yourself (e.g., in tests, or to
673+
/// implement custom exit behavior). For most CLI programs, prefer
674+
/// [`.unwrap()`](Self::unwrap) instead.
675+
///
676+
/// **Warning**: Don't blindly use `?` on this result — early exits like `Help` and
677+
/// `Version` will propagate as errors and cause exit code 1 instead of 0.
637678
pub fn into_result(self) -> Result<DriverOutput<T>, DriverError> {
638679
self.0
639680
}
@@ -648,14 +689,18 @@ impl<T> DriverOutcome<T> {
648689
self.0.is_err()
649690
}
650691

651-
/// Get the value, or print output and exit.
692+
/// Get the parsed value, handling all early-exit cases automatically.
652693
///
653-
/// This is the recommended way to handle `DriverOutcome` in most applications.
654-
/// It correctly handles all cases:
694+
/// This is the primary way to use figue. It does exactly what a well-behaved
695+
/// CLI should do:
655696
///
656-
/// - **On success**: prints warnings to stderr, returns the parsed value
657-
/// - **On help/completions/version**: prints to stdout, exits with code 0
658-
/// - **On error**: prints diagnostics to stderr, exits with code 1
697+
/// | Case | Behavior |
698+
/// |------|----------|
699+
/// | Parse succeeded | Prints warnings to stderr, returns `T` |
700+
/// | `--help` passed | Prints help to stdout, exits with code 0 |
701+
/// | `--version` passed | Prints version to stdout, exits with code 0 |
702+
/// | `--completions` passed | Prints shell script to stdout, exits with code 0 |
703+
/// | Parse failed | Prints diagnostics to stderr, exits with code 1 |
659704
///
660705
/// # Example
661706
///
@@ -672,12 +717,10 @@ impl<T> DriverOutcome<T> {
672717
/// builtins: FigueBuiltins,
673718
/// }
674719
///
675-
/// // This will:
676-
/// // - Print help and exit(0) if --help is passed
677-
/// // - Print version and exit(0) if --version is passed
678-
/// // - Print error and exit(1) if args are invalid
679-
/// // - Return the Args if everything is OK
720+
/// // In your main():
680721
/// let args: Args = figue::from_std_args().unwrap();
722+
/// // If we get here, args were parsed successfully.
723+
/// // --help and --version already exited before this line.
681724
/// println!("Processing: {}", args.file);
682725
/// ```
683726
pub fn unwrap(self) -> T {
@@ -1013,22 +1056,33 @@ fn extract_shell_from_value(value: &ConfigValue) -> Option<Shell> {
10131056
}
10141057
}
10151058

1016-
/// Error returned by the driver.
1059+
/// Reason the driver did not produce a parsed value.
1060+
///
1061+
/// This enum covers two distinct cases:
10171062
///
1018-
/// Not all variants are "errors" in the traditional sense - [`Help`](Self::Help),
1019-
/// [`Completions`](Self::Completions), and [`Version`](Self::Version) are successful
1020-
/// operations that just don't produce a config value.
1063+
/// - **Early exits** ([`Help`](Self::Help), [`Version`](Self::Version),
1064+
/// [`Completions`](Self::Completions)) — the user asked for something other than
1065+
/// running the program. These have exit code 0 and are "errors" only in the sense
1066+
/// that no `T` was produced.
1067+
///
1068+
/// - **Actual errors** ([`Failed`](Self::Failed), [`Builder`](Self::Builder),
1069+
/// [`EnvSubst`](Self::EnvSubst)) — something went wrong. These have exit code 1.
1070+
///
1071+
/// Most programs don't need to inspect this type at all — calling
1072+
/// [`DriverOutcome::unwrap()`] handles everything correctly. This type is useful
1073+
/// when you want to customize behavior, e.g. in tests or in programs that embed
1074+
/// figue's parsing in a larger framework.
10211075
///
10221076
/// # Exit Codes
10231077
///
1024-
/// | Variant | Exit Code | Meaning |
1025-
/// |---------|-----------|---------|
1026-
/// | `Help` | 0 | User requested help |
1027-
/// | `Version` | 0 | User requested version |
1028-
/// | `Completions` | 0 | User requested shell completions |
1029-
/// | `Failed` | 1 | Parsing or validation error |
1030-
/// | `Builder` | 1 | Schema or setup error |
1031-
/// | `EnvSubst` | 1 | Environment variable substitution error |
1078+
/// | Variant | Exit Code | Kind |
1079+
/// |---------|-----------|------|
1080+
/// | `Help` | 0 | Early exit |
1081+
/// | `Version` | 0 | Early exit |
1082+
/// | `Completions` | 0 | Early exit |
1083+
/// | `Failed` | 1 | Error |
1084+
/// | `Builder` | 1 | Error |
1085+
/// | `EnvSubst` | 1 | Error |
10321086
///
10331087
/// # Example
10341088
///
@@ -1045,11 +1099,19 @@ fn extract_shell_from_value(value: &ConfigValue) -> Option<Shell> {
10451099
/// builtins: FigueBuiltins,
10461100
/// }
10471101
///
1048-
/// let result = figue::from_slice::<Args>(&["--help"]).into_result();
1049-
/// match result {
1102+
/// // --help is an early exit, not an error
1103+
/// let err = figue::from_slice::<Args>(&["--help"]).unwrap_err();
1104+
/// assert!(err.is_success());
1105+
/// assert_eq!(err.exit_code(), 0);
1106+
///
1107+
/// // Pattern matching for custom handling:
1108+
/// match figue::from_slice::<Args>(&["--help"]).into_result() {
1109+
/// Ok(output) => {
1110+
/// // use output.value
1111+
/// }
10501112
/// Err(DriverError::Help { text }) => {
10511113
/// assert!(text.contains("--help"));
1052-
/// // In a real app: print text and exit(0)
1114+
/// // print text and exit(0)
10531115
/// }
10541116
/// Err(DriverError::Version { text }) => {
10551117
/// // print text and exit(0)
@@ -1060,9 +1122,6 @@ fn extract_shell_from_value(value: &ConfigValue) -> Option<Shell> {
10601122
/// Err(e) => {
10611123
/// // other error, exit(1)
10621124
/// }
1063-
/// Ok(output) => {
1064-
/// // success, use output.value
1065-
/// }
10661125
/// }
10671126
/// ```
10681127
pub enum DriverError {

0 commit comments

Comments
 (0)