use proc_macro2::TokenStream;
use quote::quote;
use crate::ir::{HttpStatusValue, StructDefinition};
use super::helpers::{generate_field_key, generate_field_value};
use crate::codegen::helpers::{
DEFAULT_JSONRPC_CODE, generate_code_expr, generate_http_headers_body,
};
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_expr = match &ir.http_status {
Some(HttpStatusValue::Code(code)) => {
quote! { ::apollo_errors::private::http_status_from_u16(#code) }
}
Some(HttpStatusValue::Path(path)) => quote! { #path },
None => quote! { ::http::StatusCode::INTERNAL_SERVER_ERROR },
};
let extension_fields: Vec<_> = ir
.fields
.iter()
.filter(|f| f.is_extension)
.map(generate_field_value)
.collect();
let code_expr = generate_code_expr(&ir.diagnostic_code);
let to_json_body = generate_to_json_body(&extension_fields, &code_expr);
let to_graphql_body = generate_to_graphql_body(ir, &code_expr);
let to_text_body = generate_to_text_body(&code_expr);
let to_html_body = generate_to_html_body(ir, &code_expr);
let to_jsonrpc_body = generate_to_jsonrpc_body(ir, &code_expr);
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, config: ::apollo_errors::private::FormatConfig) -> ::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error> {
#to_json_body
}
fn to_html(&self, config: ::apollo_errors::private::FormatConfig) -> ::std::string::String {
#to_html_body
}
fn to_graphql(&self, config: ::apollo_errors::private::FormatConfig) -> ::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error> {
#to_graphql_body
}
fn to_text(&self, config: ::apollo_errors::private::FormatConfig) -> ::std::string::String {
#to_text_body
}
fn to_jsonrpc(&self, config: ::apollo_errors::private::FormatConfig) -> ::std::result::Result<#serde_json_crate::Value, #serde_json_crate::Error> {
#to_jsonrpc_body
}
fn http_status(&self) -> ::apollo_errors::http::StatusCode {
#http_status_expr
}
fn http_headers(&self) -> Vec<(::apollo_errors::http::HeaderName, ::apollo_errors::http::HeaderValue)> {
#http_headers_body
}
}
}
}
fn generate_to_json_body(extension_fields: &[TokenStream], code_expr: &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 = #code_expr;
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 = #code_expr;
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, code_expr: &TokenStream) -> 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 = #code_expr;
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 = #code_expr;
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_text_body(code_expr: &TokenStream) -> TokenStream {
quote! {
let __apollo_code = #code_expr;
let __apollo_message = ::std::string::ToString::to_string(self);
format!("[{__apollo_code}] {__apollo_message}")
}
}
fn generate_to_html_body(ir: &StructDefinition, code_expr: &TokenStream) -> TokenStream {
let html_field_items: Vec<_> = ir
.fields
.iter()
.filter(|f| f.is_extension)
.map(|field| {
let rust_name = &field.rust_name;
let field_key = generate_field_key(field);
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>",
#field_key,
::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>",
#field_key,
::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 = #code_expr;
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, code_expr: &TokenStream) -> 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);
if extension_fields.is_empty() {
quote! {
let __apollo_diagnostic_code = #code_expr;
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 = #code_expr;
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
}))
}
}
}