apollo-errors-derive 0.4.0

Proc macro for deriving apollo-errors::Error trait
Documentation
//! apollo_errors::Error trait implementation generation for struct errors

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

use crate::ir::StructDefinition;

use super::helpers::generate_field_value;
use crate::codegen::helpers::{DEFAULT_JSONRPC_CODE, generate_http_headers_body};

/// Generate the apollo_errors::Error trait implementation
pub fn generate_apollo_impl(ir: &StructDefinition) -> TokenStream {
    let private = quote!(::apollo_errors::private);
    let serde_json_crate = quote!(#private::serde_json);

    let name = &ir.name;
    let (impl_generics, ty_generics, where_clause) = ir.generics.split_for_impl();
    let http_status = ir.http_status.unwrap_or(500);

    // Generate extension field values
    let extension_fields: Vec<_> = ir
        .fields
        .iter()
        .filter(|f| f.is_extension)
        .map(generate_field_value)
        .collect();

    let to_json_body = generate_to_json_body(&extension_fields);
    let to_graphql_body = generate_to_graphql_body(ir);
    let to_debug_body = generate_to_debug_body(ir);
    let to_text_body = generate_to_text_body();
    let to_html_body = generate_to_html_body(ir);
    let to_jsonrpc_body = generate_to_jsonrpc_body(ir);
    let http_headers_body = generate_http_headers_body(&ir.fields, true);

    quote! {
        #[automatically_derived]
        impl #impl_generics ::apollo_errors::Error for #name #ty_generics #where_clause {
            fn to_json(&self) -> ::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error> {
                #to_json_body
            }

            fn to_debug(&self) -> ::std::string::String {
                #to_debug_body
            }

            fn to_html(&self) -> ::std::string::String {
                #to_html_body
            }

            fn to_graphql(&self) -> ::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error> {
                #to_graphql_body
            }

            fn to_text(&self) -> ::std::string::String {
                #to_text_body
            }

            fn to_jsonrpc(&self) -> ::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error> {
                #to_jsonrpc_body
            }

            fn http_status(&self) -> ::apollo_errors::http::StatusCode {
                ::apollo_errors::http::StatusCode::from_u16(#http_status).unwrap()
            }

            fn http_headers(&self) -> Vec<(::apollo_errors::http::HeaderName, ::apollo_errors::http::HeaderValue)> {
                #http_headers_body
            }
        }
    }
}

fn generate_to_json_body(extension_fields: &[TokenStream]) -> TokenStream {
    let private = quote!(::apollo_errors::private);
    let serde_json_crate = quote!(#private::serde_json);

    if extension_fields.is_empty() {
        quote! {
            let __apollo_code = ::apollo_errors::private::diagnostic_code(self);
            let __apollo_message = ::std::string::ToString::to_string(self);
            ::std::result::Result::Ok(#serde_json_crate::json!({
                "error": __apollo_code,
                "message": __apollo_message
            }))
        }
    } else {
        quote! {
            let __apollo_code = ::apollo_errors::private::diagnostic_code(self);
            let __apollo_message = ::std::string::ToString::to_string(self);
            let mut __apollo_obj = #serde_json_crate::json!({
                "error": __apollo_code,
                "message": __apollo_message
            });
            if let #serde_json_crate::Value::Object(ref mut __apollo_map) = __apollo_obj {
                #(
                    if let ::std::option::Option::Some((__apollo_key, __apollo_value)) = #extension_fields {
                        __apollo_map.insert(__apollo_key.to_string(), __apollo_value);
                    }
                )*
            }
            ::std::result::Result::Ok(__apollo_obj)
        }
    }
}

fn generate_to_graphql_body(ir: &StructDefinition) -> TokenStream {
    let private = quote!(::apollo_errors::private);
    let serde_json_crate = quote!(#private::serde_json);

    let extension_fields: Vec<_> = ir
        .fields
        .iter()
        .filter(|f| f.is_extension)
        .map(generate_field_value)
        .collect();

    if extension_fields.is_empty() {
        quote! {
            let __apollo_code = ::apollo_errors::private::diagnostic_code(self);
            let __apollo_message = ::std::string::ToString::to_string(self);
            ::std::result::Result::Ok(#serde_json_crate::json!({
                "message": __apollo_message,
                "extensions": {
                    "code": __apollo_code
                }
            }))
        }
    } else {
        quote! {
            let __apollo_code = ::apollo_errors::private::diagnostic_code(self);
            let __apollo_message = ::std::string::ToString::to_string(self);
            let mut __apollo_extensions = #serde_json_crate::json!({
                "code": __apollo_code
            });
            if let #serde_json_crate::Value::Object(ref mut __apollo_map) = __apollo_extensions {
                #(
                    if let ::std::option::Option::Some((__apollo_key, __apollo_value)) = #extension_fields {
                        __apollo_map.insert(__apollo_key.to_string(), __apollo_value);
                    }
                )*
            }
            ::std::result::Result::Ok(#serde_json_crate::json!({
                "message": __apollo_message,
                "extensions": __apollo_extensions
            }))
        }
    }
}

fn generate_to_debug_body(ir: &StructDefinition) -> TokenStream {
    let name = &ir.name;
    let name_str = name.to_string();

    let field_debug_items: Vec<_> = ir
        .fields
        .iter()
        .map(|field| {
            let rust_name = &field.rust_name;
            let field_name_str = rust_name.to_string();

            quote! { format!("{}: {:?}", #field_name_str, self.#rust_name) }
        })
        .collect();

    if field_debug_items.is_empty() {
        quote! { #name_str.to_string() }
    } else {
        quote! {
            format!("{} {{ {} }}", #name_str, [#(#field_debug_items),*].join(", "))
        }
    }
}

fn generate_to_text_body() -> TokenStream {
    quote! {
        let __apollo_code = ::apollo_errors::private::diagnostic_code(self);
        let __apollo_message = ::std::string::ToString::to_string(self);
        format!("[{__apollo_code}] {__apollo_message}")
    }
}

fn generate_to_html_body(ir: &StructDefinition) -> TokenStream {
    let html_field_items: Vec<_> = ir
        .fields
        .iter()
        .filter(|f| f.is_extension)
        .map(|field| {
            let rust_name = &field.rust_name;
            let output_name = &field.output_name;

            if field.is_option {
                quote! {
                    self.#rust_name.as_ref().map(|__apollo_inner| {
                        format!("<div class=\"error-field\"><span class=\"field-name\">{}:</span> <span class=\"field-value\">{}</span></div>",
                            #output_name,
                            ::apollo_errors::private::HtmlEscaped(__apollo_inner)
                        )
                    })
                }
            } else {
                quote! {
                    ::std::option::Option::Some(format!("<div class=\"error-field\"><span class=\"field-name\">{}:</span> <span class=\"field-value\">{}</span></div>",
                        #output_name,
                        ::apollo_errors::private::HtmlEscaped(&self.#rust_name)
                    ))
                }
            }
        })
        .collect();

    let extensions_html = if html_field_items.is_empty() {
        quote! { ::std::string::String::new() }
    } else {
        quote! {
            {
                let mut __apollo_fields: Vec<::std::string::String> = Vec::new();
                #(
                    if let ::std::option::Option::Some(__apollo_field_html) = #html_field_items {
                        __apollo_fields.push(__apollo_field_html);
                    }
                )*
                if __apollo_fields.is_empty() {
                    ::std::string::String::new()
                } else {
                    format!("\n<div class=\"error-extensions\">\n{}\n</div>", __apollo_fields.join("\n"))
                }
            }
        }
    };

    quote! {
        let __apollo_code = ::apollo_errors::private::diagnostic_code(self);
        let __apollo_message = ::std::string::ToString::to_string(self);
        let __apollo_help_html = ::apollo_errors::private::diagnostic_help(self)
            .map(|h| format!("<p class=\"error-help\">{}</p>", ::apollo_errors::private::HtmlEscaped(h)))
            .unwrap_or_default();
        let __apollo_extensions_html = #extensions_html;
        format!(
            "<div class=\"error\">\n<h3 class=\"error-code\">{}</h3>\n<p class=\"error-message\">{}</p>\n{}{}\n</div>",
            ::apollo_errors::private::HtmlEscaped(__apollo_code),
            ::apollo_errors::private::HtmlEscaped(&__apollo_message),
            __apollo_help_html,
            __apollo_extensions_html
        )
    }
}

fn generate_to_jsonrpc_body(ir: &StructDefinition) -> TokenStream {
    let private = quote!(::apollo_errors::private);
    let serde_json_crate = quote!(#private::serde_json);

    let extension_fields: Vec<_> = ir
        .fields
        .iter()
        .filter(|f| f.is_extension)
        .map(generate_field_value)
        .collect();

    let jsonrpc_code = ir.jsonrpc_code.unwrap_or(DEFAULT_JSONRPC_CODE);

    // Use Display (self.to_string()) to get the interpolated message
    if extension_fields.is_empty() {
        quote! {
            let __apollo_diagnostic_code = ::apollo_errors::private::diagnostic_code(self);
            let __apollo_message = ::std::string::ToString::to_string(self);
            ::std::result::Result::Ok(#serde_json_crate::json!({
                "code": #jsonrpc_code,
                "message": __apollo_message,
                "data": {
                    "diagnostic_code": __apollo_diagnostic_code
                }
            }))
        }
    } else {
        quote! {
            let __apollo_diagnostic_code = ::apollo_errors::private::diagnostic_code(self);
            let __apollo_message = ::std::string::ToString::to_string(self);
            let mut __apollo_data = #serde_json_crate::json!({
                "diagnostic_code": __apollo_diagnostic_code
            });
            if let #serde_json_crate::Value::Object(ref mut __apollo_map) = __apollo_data {
                #(
                    if let ::std::option::Option::Some((__apollo_key, __apollo_value)) = #extension_fields {
                        __apollo_map.insert(__apollo_key.to_string(), __apollo_value);
                    }
                )*
            }
            ::std::result::Result::Ok(#serde_json_crate::json!({
                "code": #jsonrpc_code,
                "message": __apollo_message,
                "data": __apollo_data
            }))
        }
    }
}