@@ -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" ]
607645pub 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/// ```
10681127pub enum DriverError {
0 commit comments