use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use crate::codegen::helpers::{
DEFAULT_JSONRPC_CODE, generate_downcast_attempts, generate_field_metadata,
precompute_code_variants, quote_option_str,
};
use crate::ir::{
EnumDefinition, HttpStatusValue, RegularVariantDefinition, TransparentVariantDefinition,
VariantDefinition,
};
fn generate_variant_metadata(
variant: &VariantDefinition,
static_prefix: &str,
) -> (TokenStream, TokenStream) {
match variant {
VariantDefinition::Regular(regular) => {
generate_regular_variant_metadata(regular, static_prefix)
}
VariantDefinition::Transparent(transparent) => {
generate_transparent_variant_metadata(transparent)
}
}
}
fn generate_regular_variant_metadata(
variant: &RegularVariantDefinition,
static_prefix: &str,
) -> (TokenStream, TokenStream) {
let variant_name = variant.name.to_string();
let message = &variant.error_message;
let code = &variant.diagnostic_code;
let http_status_expr = match &variant.http_status {
Some(HttpStatusValue::Code(code)) => quote! { #code },
Some(HttpStatusValue::Path(path)) => quote! { #path.as_u16() },
None => quote! { 500u16 },
};
let jsonrpc_code = variant.jsonrpc_code.unwrap_or(DEFAULT_JSONRPC_CODE);
let help = quote_option_str(&variant.help_text);
let url = quote_option_str(&variant.url);
let severity = quote_option_str(&variant.severity);
let (code_screaming_snake, code_camel, code_pascal, code_kebab) =
precompute_code_variants(code);
let field_metadata: Vec<_> = variant.fields.iter().map(generate_field_metadata).collect();
let fields_static_name = syn::Ident::new(
&format!("{}_FIELDS_{}", static_prefix, variant_name.to_uppercase()),
variant.name.span(),
);
let fields_static = quote! {
static #fields_static_name: &[::apollo_errors::private::FieldMetadata] = &[
#(#field_metadata),*
];
};
let variant_metadata = quote! {
::apollo_errors::private::VariantMetadata::Regular(
::apollo_errors::private::RegularVariantMetadata {
name: #variant_name,
message: #message,
code: #code,
code_screaming_snake: #code_screaming_snake,
code_camel: #code_camel,
code_pascal: #code_pascal,
code_kebab: #code_kebab,
http_status: #http_status_expr,
jsonrpc_code: #jsonrpc_code,
help: #help,
url: #url,
severity: #severity,
fields: #fields_static_name,
}
)
};
(fields_static, variant_metadata)
}
fn generate_transparent_variant_metadata(
variant: &TransparentVariantDefinition,
) -> (TokenStream, TokenStream) {
let variant_name = variant.name.to_string();
let inner_type = &variant.field.ty;
let forward_to = quote!(#inner_type).to_string();
let fields_static = quote! {};
let variant_metadata = quote! {
::apollo_errors::private::VariantMetadata::Transparent(
::apollo_errors::private::TransparentVariantMetadata {
name: #variant_name,
forward_to: #forward_to,
}
)
};
(fields_static, variant_metadata)
}
pub fn generate_registry_entry(ir: &EnumDefinition) -> TokenStream {
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 static_prefix = format!("_APOLLO_ERROR_{}", name_str.to_uppercase());
let static_name = syn::Ident::new(&format!("{static_prefix}_REGISTRY_ENTRY"), name.span());
if !ir.generics.params.is_empty() {
let error_msg = format!(
"Generic error type `{name_str}` cannot be registered in the global error registry. \
Consider using a concrete type or removing generic parameters."
);
return quote_spanned! {name.span()=>
compile_error!(#error_msg);
};
}
let mut field_statics = Vec::new();
let mut variant_metadata = Vec::new();
for variant in &ir.variants {
let (fields_static, variant_meta) = generate_variant_metadata(variant, &static_prefix);
field_statics.push(fields_static);
variant_metadata.push(variant_meta);
}
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());
let render_json_fn = syn::Ident::new(
&format!("{}_render_json", 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(),
);
let json_body = generate_downcast_attempts(name, "to_json", true);
let html_body = generate_downcast_attempts(name, "to_html", true);
let graphql_body = generate_downcast_attempts(name, "to_graphql", true);
let text_body = generate_downcast_attempts(name, "to_text", true);
let http_status_body = generate_downcast_attempts(name, "http_status", false);
let jsonrpc_body = generate_downcast_attempts(name, "to_jsonrpc", true);
let http_headers_body = generate_downcast_attempts(name, "http_headers", false);
quote! {
#(#field_statics)*
static #variants_static_name: &[::apollo_errors::private::VariantMetadata] =
&[#(#variant_metadata),*];
static #metadata_static_name: ::apollo_errors::private::ErrorMetadata =
::apollo_errors::private::ErrorMetadata {
type_name: #name_str,
variants: #variants_static_name,
};
fn #render_json_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
#json_body
}
fn #render_html_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<String> {
#html_body
}
fn #render_graphql_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> Option<::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error>> {
#graphql_body
}
fn #render_text_fn(error: &(dyn std::error::Error + 'static), config: ::apollo_errors::private::FormatConfig) -> 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), config: ::apollo_errors::private::FormatConfig) -> 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_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,
};
}
}