| <!-- Copyright 2026 The Fuchsia Authors. All rights reserved. --> | |
| <!-- Use of this source code is governed by a BSD-style license that can be --> | |
| <!-- found in the LICENSE file. --> | |
| # FFX Plugin Error Construction Guide | |
| This document provides a comprehensive manual for `ffx` plugin authors on how to construct, format, and propagate errors within the `ffx` multi-tool architecture framework. | |
| ## Core Philosophy: User-Facing Errors vs. Internal Bugs | |
| The `ffx` front-end main execution loop processes all errors returned from plugins using `anyhow::Error` objects. The runtime framework evaluates the error object payload using downcasting: | |
| 1. **Actionable User Errors (`FfxError`)**: If the error downcasts successfully to `FfxError::Error`, the framework treats it as an expected operational failure. The message is printed directly to the user terminal `stderr` cleanly, free of engineer stack traces. | |
| 2. **Unexpected Bugs**: All other unmapped error types (e.g., raw network errors, filesystem errors, un-downcast `anyhow::Error`) are treated as internal tool **BUGS**. The framework automatically attaches a `BUG:` stack trace prefix and instructs the user to file a Buganizer ticket at `go/ffx-bug`. | |
| **Rule of Thumb**: If the failure is caused by bad user input, missing local file configurations, target unavailability, or anything actionable by the end-user, it **MUST** be returned as an explicit `FfxError` using the macro utilities below. | |
| ## Error Macro Utilities | |
| The errors library crate (`ffx_error`) provides four main macro entries optimized for plugin development: | |
| ### 1. `ffx_error!` | |
| Use this to construct a standalone `FfxError` instance with a simple text string. By default, it associates the failure with an exit status code of `1`. | |
| ```rust | |
| use errors::ffx_error; | |
| // Plain string message error | |
| let err = ffx_error!("Target device socket connection refused."); | |
| // Formatted template message error | |
| let err_fmt = ffx_error!("Failed to open file: {}", path.display()); | |
| ``` | |
| ### 2. `ffx_error_with_code!` | |
| Use this when the subcommand needs to return a specific non-zero exit status code back to the host shell wrapper script layer. | |
| ```rust | |
| use errors::ffx_error_with_code; | |
| // Returns a custom exit code 2 indicating entry mapping target missing | |
| let err = ffx_error_with_code!(2, "Configuration target key not found."); | |
| ``` | |
| ### 3. `ffx_bail!` | |
| A highly convenient control flow macro that constructs an `ffx_error!`, wraps it in an `Err(...)` enum variant, and instantly triggers an early return (`return Err(...)`) from the active function context block. | |
| ```rust | |
| use errors::ffx_bail; | |
| if !manifest_path.exists() { | |
| ffx_bail!("Staged flashing manifest path '{}' does not exist.", manifest_path.display()); | |
| } | |
| ``` | |
| ### 4. `ffx_bail_with_code!` | |
| Combines custom exit status status codes with immediate control flow bailing early termination. | |
| ```rust | |
| use errors::ffx_bail_with_code; | |
| if value.is_null() { | |
| ffx_bail_with_code!(2, "Configuration target key contains no value data mapping."); | |
| } | |
| ``` | |
| ## Structuring Descriptive Error Strings | |
| To guarantee optimal user ergonomics, all error text blocks should follow the imperative style guide rules: | |
| * **Context First**: Explicitly detail *what* operation failed before naming the low-level trigger. | |
| * **Actionable Hints**: Provide explicit instructions or alternative parameters commands if possible (e.g., `"Run ffx doctor --restart-daemon to reset connection state."`). | |
| * **No Engineering Traces**: Keep raw logs, stack dumps, and module path identifiers inside the background file logging layers (`ffx.log`), preserving clean streams for the user console. |