# ERR-002: Error From Conversions

## Summary

`BlockStoreError` MUST implement automatic conversions from dependency error types to enable ergonomic use of the `?` operator. Specifically: `From<rocksdb::Error>` for the `RocksDb` variant, and manual mappings from bincode and zstd errors to `Serialization` and `Compression` variants respectively.

## Specification

### RocksDB Error Conversion

The `From<rocksdb::Error>` implementation is generated automatically by `thiserror` via the `#[from]` attribute:

```rust
#[derive(Debug, Error)]
pub enum BlockStoreError {
    #[error("rocksdb error: {0}")]
    RocksDb(#[from] rocksdb::Error),
    // ...
}

// Generated by thiserror:
// impl From<rocksdb::Error> for BlockStoreError {
//     fn from(err: rocksdb::Error) -> Self {
//         BlockStoreError::RocksDb(err)
//     }
// }
```

### Bincode Serialization Error Mapping

Bincode errors MUST be mapped to `Serialization(String)` using `.map_err()`:

```rust
/// Example usage in serialization code:
fn serialize_block_record(record: &BlockRecord) -> Result<Vec<u8>, BlockStoreError> {
    bincode::serialize(record)
        .map_err(|e| BlockStoreError::Serialization(e.to_string()))
}

fn deserialize_block_record(bytes: &[u8]) -> Result<BlockRecord, BlockStoreError> {
    bincode::deserialize(bytes)
        .map_err(|e| BlockStoreError::Serialization(e.to_string()))
}
```

### Zstd Compression Error Mapping

Zstd errors MUST be mapped to `Compression(String)` using `.map_err()`:

```rust
/// Example usage in compression code:
fn compress_block(data: &[u8], dict: Option<&[u8]>) -> Result<Vec<u8>, BlockStoreError> {
    zstd::bulk::compress(data, 3)
        .map_err(|e| BlockStoreError::Compression(e.to_string()))
}

fn decompress_block(data: &[u8]) -> Result<Vec<u8>, BlockStoreError> {
    zstd::stream::decode_all(data)
        .map_err(|e| BlockStoreError::Compression(e.to_string()))
}
```

## Acceptance Criteria

1. [x] `From<rocksdb::Error>` is implemented: `let e: BlockStoreError = rocksdb_err.into();` compiles and produces `RocksDb` variant.
2. [x] Bincode errors can be mapped to `Serialization`: the string message from the original error is preserved.
3. [x] Zstd `io::Error` values can be mapped to `Compression`: the string message from the original error is preserved.
4. [x] The `?` operator works in functions returning `Result<T, BlockStoreError>` for all three error sources.

## Implementation Notes

- `rocksdb::Error` uses `#[from]` (thiserror). `bincode::Error` uses [`From<bincode::Error>`](https://doc.rust-lang.org/std/convert/trait.From.html) so `?` works without boilerplate. Zstd helpers return [`std::io::Error`]; we use [`BlockStoreError::compression_from_io`] instead of `From<std::io::Error>` so filesystem I/O paths stay unambiguous ([`ERR-001`](ERR-001_blockstoreerror_enum.md) interim `Serialization` for `create_dir_all`, etc.).
- Converting to `String` loses the original error chain but simplifies the error type (no generic parameters or trait objects needed).
- The `Serialization` variant covers both bincode serialization and deserialization failures.
- The `Compression` variant covers both zstd compression and decompression failures.

## Test Plan

1. **RocksDB From conversion**: Create a mock `rocksdb::Error`, convert via `.into()`, verify `matches!(err, BlockStoreError::RocksDb(_))`.
2. **Bincode mapping**: Attempt to deserialize invalid bytes with bincode, map the error, verify `matches!(err, BlockStoreError::Serialization(msg))` and `msg` is non-empty.
3. **Zstd mapping**: Attempt to decompress invalid bytes with zstd, map the error, verify `matches!(err, BlockStoreError::Compression(msg))` and `msg` is non-empty.
4. **Question mark operator**: Write a function returning `Result<(), BlockStoreError>` that uses `?` with each error type, verify it compiles and propagates correctly.
5. **Error message preservation**: Verify the string in `Serialization` and `Compression` variants contains the original error's message text.

## Expected Test Files

- `tests/err_002_tests.rs`
