apollo-errors-derive 0.5.0

Proc macro for deriving apollo-errors::Error trait
Documentation
//! Struct error parsing

use syn::{Attribute, DataStruct, Fields, Ident, Lit, Result};

use crate::ir::StructDefinition;

use super::diagnostic::parse_diagnostic_attrs;
use super::field::parse_field;
use super::http_status::parse_http_status;
use super::jsonrpc_code::parse_jsonrpc_code;

/// Parse a struct into a StructDefinition
pub(crate) fn parse_struct(
    name: Ident,
    generics: syn::Generics,
    attrs: &[Attribute],
    data_struct: DataStruct,
) -> Result<StructDefinition> {
    // Parse #[error("...")] attribute
    let error_message = parse_error_message(attrs)?.ok_or_else(|| {
        syn::Error::new_spanned(&name, "struct must have #[error(\"message\")] attribute")
    })?;

    // Parse #[diagnostic(...)] attributes
    let diagnostic_attrs = parse_diagnostic_attrs(attrs)?;

    let diagnostic_code = diagnostic_attrs.code.ok_or_else(|| {
        syn::Error::new_spanned(&name, "struct must have #[diagnostic(code(...))] attribute")
    })?;

    let help_text = diagnostic_attrs.help;
    let url = diagnostic_attrs.url;
    let severity = diagnostic_attrs.severity;

    // Parse #[http_status(...)] attribute
    let http_status = parse_http_status(attrs)?;

    // Parse #[jsonrpc_code(...)] attribute
    let jsonrpc_code = parse_jsonrpc_code(attrs)?;

    // Parse fields
    let fields = match data_struct.fields {
        Fields::Named(fields) => fields
            .named
            .into_iter()
            .map(parse_field)
            .collect::<Result<Vec<_>>>()?,
        Fields::Unnamed(_) => {
            return Err(syn::Error::new_spanned(
                name,
                "tuple structs are not supported, use named fields instead",
            ));
        }
        Fields::Unit => Vec::new(),
    };

    // Validate at most one #[from] field
    let from_count = fields.iter().filter(|f| f.is_from).count();
    if from_count > 1 {
        return Err(syn::Error::new_spanned(
            &name,
            "struct can have at most one #[from] field",
        ));
    }

    Ok(StructDefinition {
        name,
        generics,
        error_message,
        diagnostic_code,
        help_text,
        url,
        severity,
        http_status,
        jsonrpc_code,
        fields,
    })
}

/// Parse #[error("message")] attribute from struct-level attributes
fn parse_error_message(attrs: &[Attribute]) -> Result<Option<String>> {
    for attr in attrs {
        if attr.path().is_ident("error") {
            // Parse the string literal directly from the attribute
            let lit: Lit = attr.parse_args()?;
            if let Lit::Str(s) = lit {
                return Ok(Some(s.value()));
            }
        }
    }
    Ok(None)
}