apollo-errors-derive 0.4.0

Proc macro for deriving apollo-errors::Error trait
Documentation
//! Registry entry generation for struct errors

use proc_macro2::TokenStream;
use quote::quote;

use crate::codegen::helpers::{
    DEFAULT_JSONRPC_CODE, generate_downcast_attempts, generate_field_metadata, quote_option_str,
};
use crate::ir::StructDefinition;

/// Generate the registry entry for a struct error
pub fn generate_registry_entry(ir: &StructDefinition) -> TokenStream {
    // References to external crates
    let private = quote!(::apollo_errors::private);
    let linkme_crate = quote!(#private::linkme);
    let serde_json_crate = quote!(#private::serde_json);

    let name = &ir.name;
    let name_str = name.to_string();
    let message = &ir.error_message;
    let code = &ir.diagnostic_code;
    let http_status = ir.http_status.unwrap_or(500);
    let jsonrpc_code = ir.jsonrpc_code.unwrap_or(DEFAULT_JSONRPC_CODE);

    // Create unique static names
    let static_prefix = format!("_APOLLO_ERROR_{}", name_str.to_uppercase());
    let static_name = syn::Ident::new(&format!("{static_prefix}_REGISTRY_ENTRY"), name.span());
    let fields_static_name = syn::Ident::new(&format!("{static_prefix}_FIELDS"), name.span());
    let variants_static_name = syn::Ident::new(&format!("{static_prefix}_VARIANTS"), name.span());
    let metadata_static_name = syn::Ident::new(&format!("{static_prefix}_METADATA"), name.span());

    // Generate field metadata
    let field_metadata: Vec<_> = ir.fields.iter().map(generate_field_metadata).collect();

    let help = quote_option_str(&ir.help_text);
    let url = quote_option_str(&ir.url);
    let severity = quote_option_str(&ir.severity);

    // Generate render function names
    let render_json_fn = syn::Ident::new(
        &format!("{}_render_json", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_debug_fn = syn::Ident::new(
        &format!("{}_render_debug", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_html_fn = syn::Ident::new(
        &format!("{}_render_html", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_graphql_fn = syn::Ident::new(
        &format!("{}_render_graphql", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_text_fn = syn::Ident::new(
        &format!("{}_render_text", static_prefix.to_lowercase()),
        name.span(),
    );
    let http_status_fn = syn::Ident::new(
        &format!("{}_http_status", static_prefix.to_lowercase()),
        name.span(),
    );
    let render_jsonrpc_fn = syn::Ident::new(
        &format!("{}_render_jsonrpc", static_prefix.to_lowercase()),
        name.span(),
    );
    let http_headers_fn = syn::Ident::new(
        &format!("{}_http_headers", static_prefix.to_lowercase()),
        name.span(),
    );

    // Generate downcast bodies
    let json_body = generate_downcast_attempts(name, "to_json");
    let debug_body = generate_downcast_attempts(name, "to_debug");
    let html_body = generate_downcast_attempts(name, "to_html");
    let graphql_body = generate_downcast_attempts(name, "to_graphql");
    let text_body = generate_downcast_attempts(name, "to_text");
    let http_status_body = generate_downcast_attempts(name, "http_status");
    let jsonrpc_body = generate_downcast_attempts(name, "to_jsonrpc");
    let http_headers_body = generate_downcast_attempts(name, "http_headers");

    quote! {
        // Field metadata static
        static #fields_static_name: &[::apollo_errors::private::FieldMetadata] = &[
            #(#field_metadata),*
        ];

        // Variants array (struct has a single implicit variant)
        static #variants_static_name: &[::apollo_errors::private::VariantMetadata] = &[
            ::apollo_errors::private::VariantMetadata::Regular(
                ::apollo_errors::private::RegularVariantMetadata {
                    name: #name_str,
                    message: #message,
                    code: #code,
                    http_status: #http_status,
                    jsonrpc_code: #jsonrpc_code,
                    help: #help,
                    url: #url,
                    severity: #severity,
                    fields: #fields_static_name,
                }
            )
        ];

        // Error metadata
        static #metadata_static_name: ::apollo_errors::private::ErrorMetadata =
            ::apollo_errors::private::ErrorMetadata {
                type_name: #name_str,
                variants: #variants_static_name,
            };

        // Render functions
        fn #render_json_fn(error: &(dyn std::error::Error + 'static)) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
            #json_body
        }

        fn #render_debug_fn(error: &(dyn std::error::Error + 'static)) -> Option<String> {
            #debug_body
        }

        fn #render_html_fn(error: &(dyn std::error::Error + 'static)) -> Option<String> {
            #html_body
        }

        fn #render_graphql_fn(error: &(dyn std::error::Error + 'static)) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
            #graphql_body
        }

        fn #render_text_fn(error: &(dyn std::error::Error + 'static)) -> Option<String> {
            #text_body
        }

        fn #http_status_fn(error: &(dyn std::error::Error + 'static)) -> Option<::apollo_errors::http::StatusCode> {
            #http_status_body
        }

        fn #render_jsonrpc_fn(error: &(dyn std::error::Error + 'static)) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
            #jsonrpc_body
        }

        fn #http_headers_fn(error: &(dyn std::error::Error + 'static)) -> Option<Vec<(::apollo_errors::http::HeaderName, ::apollo_errors::http::HeaderValue)>> {
            #http_headers_body
        }

        #[#linkme_crate::distributed_slice(::apollo_errors::private::ERROR_REGISTRY)]
        #[linkme(crate = #linkme_crate)]
        #[doc(hidden)]
        static #static_name: ::apollo_errors::private::ErrorRegistryEntry =
            ::apollo_errors::private::ErrorRegistryEntry {
                type_id: std::any::TypeId::of::<#name>(),
                type_name: #name_str,
                metadata: &#metadata_static_name,
                render_json: #render_json_fn,
                render_debug: #render_debug_fn,
                render_html: #render_html_fn,
                render_graphql: #render_graphql_fn,
                render_text: #render_text_fn,
                http_status: #http_status_fn,
                render_jsonrpc: #render_jsonrpc_fn,
                http_headers: #http_headers_fn,
            };
    }
}