# Error Handling

PlestyLib centralizes most runtime error behavior through `handle_error` in `plestylib.utils.error_utils`.

This utility standardizes logging and exception raising across device, traffic, and solver layers.

## Core Utility

Signature:

```python
handle_error(err, error_msg=None, silent=False, func_name="Unknown Function")
```

Parameters:

1. `err`: expected to be an Exception instance.
2. `error_msg`: optional high-level message to expose to callers.
3. `silent`: when `True`, only logs a warning and does not raise.
4. `func_name`: currently unused in implementation.

## Behavior Matrix

### Case 1: `silent=True`

1. Logs warning with `error_msg`.
2. Returns without raising.

### Case 2: `silent=False`, `error_msg is None`, `err` is Exception

1. Logs the original exception type and message.
2. Re-raises the same exception object.

### Case 3: `silent=False`, `error_msg is None`, `err` is not Exception

1. Logs invalid usage.
2. Raises `RuntimeError`.

### Case 4: `silent=False`, `error_msg` provided, `err` is Exception

1. Logs wrapped context plus cause.
2. Raises `RuntimeError(error_msg)` with chained cause (`raise ... from err`).

### Case 5: `silent=False`, `error_msg` provided, `err` is not Exception

1. Logs invalid usage.
2. Raises `RuntimeError` with combined message.

## Why This Pattern Is Used

This project uses `handle_error` to:

1. Keep error messages consistent across modules.
2. Preserve root cause while exposing cleaner user-facing messages.
3. Avoid silent failures in most code paths.

You can see usage in:

1. Traffic managers (`plestylib/traffic`).
2. Base device classes (`plestylib/device/base_*`).
3. Solvers (`plestylib/solver`).
4. Parameter system (`plestylib/device/params.py`).

## Usage Examples

### Re-raise the original exception

```python
from plestylib.utils.error_utils import handle_error

try:
	risky_call()
except Exception as e:
	handle_error(e)
```

### Raise a contextual runtime error with chained cause

```python
from plestylib.utils.error_utils import handle_error

try:
	device.connect()
except Exception as e:
	handle_error(e, "Failed to connect to instrument")
```

### Warning-only mode (rare)

```python
from plestylib.utils.error_utils import handle_error

try:
	optional_background_task()
except Exception as e:
	handle_error(e, "Background task failed", silent=True)
```

## Recommended Practices

1. Pass real Exception instances as `err`.
2. Provide `error_msg` when you want a stable caller-facing message.
3. Use warning-only mode (`silent=True`) only for explicitly non-critical flows.
4. Prefer fail-fast behavior in device I/O, protocol parsing, and parameter validation.
5. Preserve context by raising with chained causes when wrapping low-level errors.

## Common Pitfalls

1. Passing strings instead of Exception objects as `err`.
2. Using `silent=True` too broadly and hiding critical failures.
3. Expecting `func_name` to appear in output (it is currently unused).
4. Assuming wrapped errors keep original exception type when `error_msg` is provided.

## Integration Guidance

Use this pattern by layer:

1. Traffic manager: wrap connection/read/write failures with transport-specific context.
2. Solver: wrap parse/serialization failures with operation context.
3. Device class: expose operation-level context such as command key and device identity.
4. Param/func system: raise validation errors early, before sending traffic.
