# API-006: thiserror Implementation

> **Authoritative requirement:** [API-006](../NORMATIVE.md#API-006)
> **Verification:** [VERIFICATION.md](../VERIFICATION.md)
> **Tracking:** [TRACKING.yaml](../TRACKING.yaml)
> **Spec reference:** [SPEC.md](../../../../resources/SPEC.md) — Section 5.1

---

## Summary

`ValidationError` MUST implement `std::error::Error` and `std::fmt::Display` using the `thiserror` crate (version 2). This provides idiomatic Rust error handling: callers can use `?` for propagation, `{}` formatting for human-readable messages, and `source()` for error chaining. The `thiserror` derive macro ensures Display messages are consistent and the Error source chain is correctly wired.

Two upstream error types are wrapped: `chia_consensus::validation_error::ValidationErr` (for CLVM execution failures) and `chia_sdk_driver::DriverError` (for spend construction failures). The `Driver` variant uses `#[from]` for automatic conversion, enabling the `?` operator to convert `DriverError` into `ValidationError` transparently. The `Clvm` variant wraps `ValidationErr` but may require manual conversion in some code paths due to the structure of the upstream error type.

Using `thiserror` instead of manual `impl Error` and `impl Display` blocks eliminates boilerplate, reduces the risk of mismatched error messages, and ensures the `source()` method correctly returns the wrapped inner error for variants that carry upstream errors. The `thiserror = "2"` dependency is already part of the approved dependency set in [SPEC.md Section 4.2](../../../../resources/SPEC.md).

---

## Specification

### Derive Macro Usage

The `ValidationError` enum MUST use the `thiserror::Error` derive macro:

```rust
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
    #[error("CLVM execution failed: {0}")]
    Clvm(chia_consensus::validation_error::ValidationErr),

    #[error("Coin not found: {0}")]
    CoinNotFound(Bytes32),

    #[error("Coin already spent: {0}")]
    AlreadySpent(Bytes32),

    #[error("Double spend in bundle: {0}")]
    DoubleSpend(Bytes32),

    #[error("Puzzle hash mismatch: {0}")]
    PuzzleHashMismatch(Bytes32),

    #[error("Signature verification failed")]
    SignatureFailed,

    #[error("Conservation violation: input={input}, output={output}")]
    ConservationViolation { input: u64, output: u64 },

    #[error("Cost exceeded: limit={limit}, consumed={consumed}")]
    CostExceeded { limit: Cost, consumed: Cost },

    #[error("Driver error: {0}")]
    Driver(#[from] DriverError),
}
```

### Trait Implementations

The derive macro MUST produce the following trait implementations:

| Trait | Behavior |
|---|---|
| `std::fmt::Debug` | Derived via `#[derive(Debug)]` |
| `std::fmt::Display` | Generated by `#[error("...")]` attributes |
| `std::error::Error` | Generated by `thiserror::Error` derive |
| `From<DriverError>` | Generated by `#[from]` on the `Driver` variant |

### Error Source Chain

The `Error::source()` method MUST return:

| Variant | `source()` Returns |
|---|---|
| `Clvm(ValidationErr)` | `Some(&ValidationErr)` (if `ValidationErr` implements `Error`) |
| `Driver(DriverError)` | `Some(&DriverError)` |
| All other variants | `None` |

### Display Messages

Each variant's `Display` output MUST be human-readable and include all context data:

| Variant | Display Output |
|---|---|
| `Clvm(err)` | `"CLVM execution failed: {err}"` |
| `CoinNotFound(id)` | `"Coin not found: {id}"` (hex-formatted Bytes32) |
| `AlreadySpent(id)` | `"Coin already spent: {id}"` |
| `DoubleSpend(id)` | `"Double spend in bundle: {id}"` |
| `PuzzleHashMismatch(id)` | `"Puzzle hash mismatch: {id}"` |
| `SignatureFailed` | `"Signature verification failed"` |
| `ConservationViolation { input, output }` | `"Conservation violation: input={input}, output={output}"` |
| `CostExceeded { limit, consumed }` | `"Cost exceeded: limit={limit}, consumed={consumed}"` |
| `Driver(err)` | `"Driver error: {err}"` |

### From Conversions

```rust
// MUST compile: automatic conversion from DriverError
fn example() -> Result<(), ValidationError> {
    let spend = some_driver_operation()?;  // DriverError -> ValidationError::Driver
    Ok(())
}
```

---

## Acceptance Criteria

- [ ] `ValidationError` derives `thiserror::Error` and `Debug`
- [ ] `ValidationError` implements `std::error::Error` (verified by trait bound check)
- [ ] `ValidationError` implements `std::fmt::Display` with messages matching the table above
- [ ] `From<DriverError> for ValidationError` is implemented (via `#[from]`)
- [ ] `error.source()` returns the wrapped inner error for `Clvm` and `Driver` variants
- [ ] `thiserror = "2"` is listed in `Cargo.toml` dependencies

---

## Implementation Notes

- `thiserror` version 2.x is required (not 1.x). Version 2 has improved proc-macro diagnostics and supports more formatting options.
- If `ValidationErr` does not implement `std::error::Error`, the `source()` chain for the `Clvm` variant will return `None`. This is acceptable -- the Display message will still include the error details via `{0}` formatting.
- The `#[from]` attribute on `Driver(DriverError)` generates both `From<DriverError>` and sets `source()` to return the inner error. Only one variant per source type can use `#[from]`.
- The `Clvm` variant does NOT use `#[from]` because `ValidationErr` may need contextual wrapping (e.g., adding a coin ID or additional diagnostic info) in some code paths.
- The `Display` formatting for `Bytes32` depends on the upstream `Display` impl. If `Bytes32` displays as hex, the error messages will show hex coin IDs, which is the expected behavior.

---

## Verification

### Test Plan

1. **Error trait verification**:
   ```rust
   #[test]
   fn validation_error_is_std_error() {
       fn assert_error<T: std::error::Error>() {}
       assert_error::<ValidationError>();
   }
   ```

2. **Display output verification**:
   ```rust
   #[test]
   fn validation_error_display_messages() {
       let id = Bytes32::from([0xAB; 32]);
       assert!(format!("{}", ValidationError::CoinNotFound(id)).contains("Coin not found"));
       assert!(format!("{}", ValidationError::SignatureFailed).contains("Signature verification failed"));
       assert!(format!("{}", ValidationError::ConservationViolation { input: 100, output: 200 })
           .contains("input=100"));
       assert!(format!("{}", ValidationError::CostExceeded { limit: 1000, consumed: 2000 })
           .contains("limit=1000"));
   }
   ```

3. **From<DriverError> conversion test**:
   ```rust
   #[test]
   fn driver_error_converts_to_validation_error() {
       let driver_err: DriverError = /* construct a DriverError */;
       let val_err: ValidationError = driver_err.into();
       assert!(matches!(val_err, ValidationError::Driver(_)));
   }
   ```

4. **Error source chain test**:
   ```rust
   #[test]
   fn driver_variant_has_source() {
       let driver_err: DriverError = /* construct */;
       let val_err: ValidationError = driver_err.into();
       assert!(val_err.source().is_some());
   }
   ```

---

## Source Citations

- [SPEC.md Section 5.1](../../../../resources/SPEC.md) — `ValidationError` definition with `#[derive(Debug, thiserror::Error)]`
- [SPEC.md Section 4.2](../../../../resources/SPEC.md) — `thiserror = "2"` in dependencies

---

## References

- [API-005](API-005.md) — ValidationError variants (defines the enum this spec implements traits for)
- [API-002](API-002.md) — `thiserror` is in the approved dependency list
- [API-001](API-001.md) — `DriverError` is re-exported (callers may need to handle it directly)
