# Errors

Rust Error enum variants, MFA auth codes, and queue overflow.

All fallible SDK APIs use `polyester::Result<T>` = `Result<T, Error>`.

For usage patterns see the [Error handling guide](https://testnet.polyester.com/docs/sdk/rust/guides/error-handling).

Use `err.is_retryable()` for backoff classification, `err.retry_after()` for a server-requested delay, and `err.mutation_outcome_unknown()` to identify mutations that require reconciliation before retrying. These helpers never prove that a mutation was unapplied.

## Variants

| Variant                                                                | When                                               |
| ---------------------------------------------------------------------- | -------------------------------------------------- |
| `Error::Auth`                                                          | Unary `Unauthenticated` / `PermissionDenied`       |
| `Error::PermissionDenied { message, status, code, context, endpoint }` | Realtime token HTTP 403 with structured context    |
| `Error::Validation`                                                    | Bad input (`post_only`, ids, enums, shapes, scale) |
| `Error::Transport`                                                     | Network / deadline, usually retryable with backoff |
| `Error::ResponseContract { context, message }`                         | Successful RPC returned an invalid response shape  |
| `Error::RateLimit { message, retry_after }`                            | Server rate limit or local signing-capacity limit  |
| `Error::Server`                                                        | Backend 5xx-class                                  |
| `Error::Api { message, code, metadata }`                               | Structured Connect/API error                       |
| `Error::RouteNotFound { procedure }`                                   | RPC not exposed                                    |
| `Error::Realtime`                                                      | Realtime connect / decode                          |
| `Error::QueueOverflow`                                                 | Subscription queue full (fail-closed)              |

```rust
use polyester::models::{CreateOrderParams, CreateOrderType, CreateSide};
use polyester::{Error, Quantity};

let quantity_scale = client
    .catalogs
    .base_quantity_scale_for_symbol("BTC-USDT")
    .ok_or_else(|| Error::validation("BTC-USDT quantity scale is unavailable"))?;
let params = CreateOrderParams {
    symbol: "BTC-USDT".into(),
    side: CreateSide::Buy,
    order_type: CreateOrderType::Limit,
    quantity: Some(Quantity::from_decimal_str("0.01", quantity_scale, Some("BTC-USDT".into()), None)?),
    max_quote_debit_scaled: None,
    price: None,
    time_in_force: None,
    client_order_id: Some("mm-bot-001".into()),
    subaccount_id: None,
    post_only: None,
    market_client_ref_price: None,
    fee_asset: None,
    self_trade_prevention: None,
    market_max_slippage: None,
    attached_risk: None,
};
match client.orders.create(params).await {
    Ok(r) => println!("{}", r.order_id),
    Err(err) if err.mutation_outcome_unknown() => {
        // reconcile by client_order_id before deciding whether to resubmit
        return Err(err);
    }
    Err(err) if err.is_retryable() => {
        let _ = err.retry_after(); // sleep when present; reconcile this create
    }
    Err(Error::Validation(msg)) => eprintln!("fix input: {msg}"),
    Err(Error::QueueOverflow(_)) => { /* resubscribe, not a unary retry */ }
    Err(err) => return Err(err),
}
```

There is no separate catalog-conversion error type like TypeScript. Catalog / precision failures typically surface as `Error::Validation` or `Error::Api`.

`Error::ResponseContract` is deliberately **not retryable**: the server returned success, but the SDK could not safely interpret the response. For an order mutation, `mutation_outcome_unknown()` is still `true` because the mutation may have been accepted. Reconcile by the original stable identifier; do not turn this error into a blind retry loop.

Unary Connect order APIs map both `Unauthenticated` and `PermissionDenied` to `Error::Auth`. `Error::PermissionDenied` is not their unary mapping; it carries structured context specifically for an HTTP 403 while acquiring a private realtime token.

## MFA helpers

On `Error`: `auth_error_code()`, `is_mfa_enrollment_required()`, `is_step_up_required()`, `is_mfa_elevation_required()`, `is_mfa_last_factor_required()`, for shared auth codes. API-key create-key / wallet MFA UX is **not** part of this SDK (JWT/session only).

## Retry sketch

Use `is_retryable()` for backoff. When `mutation_outcome_unknown()` is true, reconcile first and reuse the same `client_order_id` / `request_id` / `client_trigger_id`. Queue overflow is fail-closed for that subscription.

High-level async calls apply signing backpressure with Tokio-aware waits; they do not block an executor worker. Direct synchronous `Credentials::sign_request` returns a retryable rate-limit error immediately when the bounded timestamp window is full. Async low-level integrations should use `Credentials::sign_request_async`.

See [Requests & idempotency](https://testnet.polyester.com/docs/sdk/rust/concepts/requests-and-idempotency).

## Class notes

| Concern                | Guidance                                      |
| ---------------------- | --------------------------------------------- |
| Auth                   | Fix credentials / Account ID before retrying  |
| Validation             | Includes `post_only` on non-limit-GTC         |
| Transport / rate limit | Backoff; reconcile when outcome is unknown    |
| Response contract      | Reconcile; non-retryable without new evidence |
| Queue overflow         | Resubscribe; do not treat as unary retry      |
| Route not found        | Wrong host / incomplete env                   |

## Related

- [Error handling guide](https://testnet.polyester.com/docs/sdk/rust/guides/error-handling)
- [Streaming](https://testnet.polyester.com/docs/sdk/rust/guides/streaming)
- [Requests & idempotency](https://testnet.polyester.com/docs/sdk/rust/concepts/requests-and-idempotency)
