Derive Macro AsApiError

Source
#[derive(AsApiError)]
{
    // Attributes available to this derive:
    #[api_error]
}
Expand description

Derives the AsApiErrorTrait for an enum, allowing it to be converted into an ApiError suitable for Actix-Web responses. It also conditionally implements std::fmt::Display.

§Attributes

Attributes are placed on enum variants using #[api_error(...)]:

  • code = <u16>: Specifies a raw HTTP status code (e.g., code = 404). If both code and status are provided, code takes precedence.

  • status = "<StatusCodeString>": Specifies the HTTP status using a predefined string. (e.g., status = "NotFound"). See below for a list of supported strings. If neither code nor status is provided, defaults to 500 (Internal Server Error).

  • kind = "<string>": Sets the kind field in the ApiError. Defaults to the snake_case version of the variant name (e.g., MyVariant becomes "my_variant").

  • msg = "<string>": Provides a custom error message.

    • For variants with named fields: msg = "Error for {field_name}".
    • For variants with unnamed (tuple) fields: msg = "Error with value {0} and {1}".
    • If msg is not provided, the message is generated based on the Display trait:
      • If this macro generates Display (see “Conditional std::fmt::Display Implementation” below), it will be the variant name or a simple format derived from it.
      • If the user provides Display (e.g., via thiserror), that implementation is used (self.to_string()).
  • ignore = <bool>: (Default: false)

    • If true, msg is not provided, and the macro does not generate Display, the message will be the variant name, and fields will not be automatically formatted into the message.
    • This attribute does not prevent field interpolation if a msg attribute is provided (e.g., #[api_error(msg = "Value: {0}", ignore)] MyVariant(i32) will still print the value).
    • Its primary use is to simplify the message to just the variant name when no msg is given and Display is not generated by this macro, overriding default field formatting.
  • group = <bool>: (Default: false)

    • If true, the variant is expected to hold a single field that itself implements AsApiErrorTrait. The as_api_error() method of this inner error will be called. Other attributes like code, status, msg, kind on the group variant are ignored.

§Automatic details Field Population

If a variant is not a group and contains a single field of type serde_json::Value or Option<serde_json::Value>, this field’s value will automatically populate the details field of the generated ApiError.

§Conditional std::fmt::Display Implementation

The std::fmt::Display trait is implemented for the enum by this macro if and only if at least one variant has an explicit #[api_error(msg = "...")] attribute.

  • If implemented by the macro:
    • Variants with msg will use that formatted message for their Display output.
    • Variants without msg will display as their variant name (e.g., MyEnum::VariantName displays as “VariantName”).

If no variants use #[api_error(msg = "...")], you are expected to provide your own Display implementation (e.g., using the thiserror crate or manually). The as_api_error method will then use self.to_string() for the ApiError message if msg is not set on the variant.

§Supported status Strings and Their Codes

// "BadRequest" => 400
// "Unauthorized" => 401
// "Forbidden" => 403
// "NotFound" => 404
// "MethodNotAllowed" => 405
// "Conflict" => 409
// "Gone" => 410
// "PayloadTooLarge" => 413
// "UnsupportedMediaType" => 415
// "UnprocessableEntity" => 422
// "TooManyRequests" => 429
// "InternalServerError" => 500 (Default if no code/status is specified)
// "NotImplemented" => 501
// "BadGateway" => 502
// "ServiceUnavailable" => 503
// "GatewayTimeout" => 504

Using an unsupported string in status will result in a compile-time error.

§Example

use actix_error_derive::AsApiError;
// Ensure ApiError and AsApiErrorTrait are in scope, typically via:
// use actix_error::{ApiError, AsApiErrorTrait}; 
use serde_json::json;

// Dummy AnotherErrorType for the group example
#[derive(Debug)]
pub struct AnotherErrorType;
impl actix_error::AsApiErrorTrait for AnotherErrorType {
    fn as_api_error(&self) -> actix_error::ApiError {
        actix_error::ApiError::new(401, "auth_failure", "Authentication failed".to_string(), None)
    }
}
impl std::fmt::Display for AnotherErrorType { 
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "AnotherErrorType: Authentication Failed")
    }
}

#[derive(Debug, AsApiError)]
pub enum MyError {
    #[api_error(status = "NotFound", msg = "Resource not found.")]
    NotFound, // Display will be "Resource not found."

    // No msg, so if Display is macro-generated, it's "InvalidInput".
    // If user provides Display (e.g. with thiserror), that's used for ApiError.message.
    #[api_error(code = 400, kind = "input_validation")]
    InvalidInput { field: String, reason: String }, 

    #[api_error(status = "UnprocessableEntity", msg = "Cannot process item: {0}")]
    Unprocessable(String), // Display will be "Cannot process item: <value>"

    // 'details' will be auto-populated from the serde_json::Value field.
    // msg is present, so Display is "Detailed error occurred."
    #[api_error(status = "BadRequest", msg = "Detailed error occurred.")] 
    DetailedError(serde_json::Value),

    #[api_error(group)]
    AuthError(AnotherErrorType), // Delegates to AnotherErrorType's AsApiErrorTrait
}

// Since MyError has variants with `msg`, `Display` is generated by AsApiError.
// If no variants had `msg`, you would need to implement `Display` manually or with `thiserror`:
//
// #[derive(Debug, AsApiError, thiserror::Error)] // Example with thiserror
// pub enum MyErrorWithoutMacroDisplay {
//     #[error("Item {0} was not found")] // thiserror message
//     #[api_error(status = "NotFound")]
//     NotFound(String),
//
//     #[error("Input is invalid: {reason}")]
//     #[api_error(code = 400, kind = "bad_input")]
//     InvalidInput { reason: String }
// }