# miden-node-grpc-error-macro

Procedural macro for deriving the `GrpcError` trait on error enums, reducing boilerplate in gRPC error handling.

## Overview

This macro automatically generates:
1. A companion `*GrpcError` enum with `#[repr(u8)]` for wire serialization
2. An `api_error()` method that maps error variants to their gRPC representations
3. Implementation of the `GrpcError` trait for the companion enum

## Usage

### Basic Example

```rust
use miden_node_grpc_error_macro::GrpcError;
use thiserror::Error;

#[derive(Debug, Error, GrpcError)]
pub enum GetNoteScriptByRootError {
    #[error("database error")]
    #[grpc(internal)]
    DatabaseError(#[from] DatabaseError),
    
    #[error("malformed script root")]
    DeserializationFailed(#[from] ConversionError),
    
    #[error("script with given root doesn't exist")]
    ScriptNotFound,
}
```

### Generated Code

The above example generates:

```rust
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub enum GetNoteScriptByRootGrpcError {
    /// Internal server error
    Internal = 0,
    /// Malformed script root
    DeserializationFailed = 1,
    /// Script with given root doesn't exist
    ScriptNotFound = 2,
}

impl GrpcError for GetNoteScriptByRootGrpcError {
    fn api_code(self) -> u8 {
        self as u8
    }
    
    fn is_internal(&self) -> bool {
        matches!(self, Self::Internal)
    }
}

impl GetNoteScriptByRootError {
    pub fn api_error(&self) -> GetNoteScriptByRootGrpcError {
        match self {
            GetNoteScriptByRootError::DatabaseError(_) => 
                GetNoteScriptByRootGrpcError::Internal,
            GetNoteScriptByRootError::DeserializationFailed(_) => 
                GetNoteScriptByRootGrpcError::DeserializationFailed,
            GetNoteScriptByRootError::ScriptNotFound => 
                GetNoteScriptByRootGrpcError::ScriptNotFound,
        }
    }
}

impl From<GetNoteScriptByRootError> for tonic::Status {
    fn from(value: GetNoteScriptByRootError) -> Self {
        let api_error = value.api_error();
        let message = if api_error.is_internal() {
            "Internal error".to_owned()
        } else {
            value.as_report()
        };
        tonic::Status::with_details(
            api_error.tonic_code(),
            message,
            vec![api_error.api_code()].into(),
        )
    }
}
```

## Attributes

### `#[grpc(internal)]`

Mark a variant as an internal error. This will:
- Map the variant to `*GrpcError::Internal` instead of creating a separate variant
- Result in the error being serialized with `tonic::Code::Internal` instead of `tonic::Code::InvalidArgument`

```rust
#[derive(Debug, Error, GrpcError)]
pub enum MyError {
    #[error("database connection failed")]
    #[grpc(internal)]  // Maps to MyGrpcError::Internal
    DatabaseError(#[from] DatabaseError),
    
    #[error("invalid input")]
    InvalidInput,  // Creates MyGrpcError::InvalidInput variant
}
```

## License
This project is [MIT licensed](../../LICENSE).
