miden-node-grpc-error-macro 0.12.5

Procedural macro for deriving GrpcError trait (internal implementation detail - use miden-node-proto)
Documentation

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

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:

#[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
#[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.