blob: dc8a6324b7148417c893514e20038987cc5bbb1f [file] [view] [edit]
<!-- 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.