apollo_errors/error.rs
1//! Core Error trait definition
2
3use crate::metadata::FormatConfig;
4
5/// Extract the diagnostic code from a `miette::Diagnostic` as a string.
6///
7/// Returns `"UNKNOWN_ERROR"` if no code is present.
8#[inline]
9pub fn diagnostic_code<D: miette::Diagnostic + ?Sized>(diagnostic: &D) -> String {
10 diagnostic
11 .code()
12 .map(|c| c.to_string())
13 .unwrap_or_else(|| "UNKNOWN_ERROR".to_string())
14}
15
16/// Extract the help text from a `miette::Diagnostic` as an optional string.
17#[inline]
18pub fn diagnostic_help<D: miette::Diagnostic + ?Sized>(diagnostic: &D) -> Option<String> {
19 diagnostic.help().map(|h| h.to_string())
20}
21
22/// The core trait for apollo errors.
23///
24/// This trait extends `std::error::Error` and `miette::Diagnostic`
25/// to provide multi-format rendering.
26///
27/// # Derive Macro
28///
29/// Use `#[derive(apollo_errors::Error)]` to automatically implement this trait.
30/// The derive macro generates format renderers for JSON, GraphQL, HTML, and text output.
31///
32/// # Example
33///
34/// ```ignore
35/// use apollo_errors::Error;
36///
37/// #[derive(Debug, Error)]
38/// pub enum MyError {
39/// #[error("Something went wrong")]
40/// #[diagnostic(code(my_product::component::error_name))]
41/// SomethingWrong {
42/// #[extension]
43/// field: String,
44/// },
45/// }
46/// ```
47pub trait Error: std::error::Error + miette::Diagnostic {
48 /// Render this error as JSON format
49 ///
50 /// `config` controls field name casing and error code format in the output.
51 ///
52 /// Returns a serde_json::Value with:
53 /// - `error`: The error code
54 /// - `message`: The error message
55 /// - Additional fields from `#[extension]` fields
56 ///
57 /// # Errors
58 ///
59 /// Returns an error if any field fails to serialize to JSON
60 fn to_json(&self, config: FormatConfig) -> Result<serde_json::Value, serde_json::Error>;
61
62 /// Render this error in debug format using Rust's standard `Debug` output.
63 fn to_debug(&self) -> String
64 where
65 Self: std::fmt::Debug,
66 {
67 format!("{self:?}")
68 }
69
70 /// Render this error as HTML
71 ///
72 /// `config` controls field name casing and error code format in the output.
73 /// Returns an HTML representation suitable for browser display.
74 fn to_html(&self, config: FormatConfig) -> String;
75
76 /// Render this error as GraphQL JSON format
77 ///
78 /// `config` controls field name casing and error code format in the output.
79 ///
80 /// Returns a serde_json::Value with:
81 /// - `message`: The error message
82 /// - `extensions`: Additional structured data from `#[extension]` fields
83 /// - `code`: The diagnostic code (e.g., APOLLO_PRODUCT_COMPONENT_ERROR)
84 ///
85 /// # Errors
86 ///
87 /// Returns an error if any field fails to serialize to JSON
88 fn to_graphql(&self, config: FormatConfig) -> Result<serde_json::Value, serde_json::Error>;
89
90 /// Render this error as plain text
91 ///
92 /// `config` controls field name casing and error code format in the output.
93 /// Returns a human-readable plain text representation.
94 fn to_text(&self, config: FormatConfig) -> String;
95
96 /// Render this error as JSON-RPC 2.0 error format
97 ///
98 /// `config` controls field name casing and error code format in the output.
99 ///
100 /// Returns a serde_json::Value with:
101 /// - `code`: Integer error code (from `#[jsonrpc_code(...)]` or default -32000)
102 /// - `message`: The error message
103 /// - `data`: Object containing `diagnostic_code` and any `#[extension]` fields
104 ///
105 /// # Errors
106 ///
107 /// Returns an error if any field fails to serialize to JSON
108 fn to_jsonrpc(&self, config: FormatConfig) -> Result<serde_json::Value, serde_json::Error>;
109
110 /// Get the HTTP status code for this error
111 ///
112 /// Returns the appropriate HTTP status code (e.g., 400, 404, 500).
113 /// Defaults to 500 if not specified.
114 fn http_status(&self) -> http::StatusCode;
115
116 /// Get HTTP headers that should be returned with this error
117 ///
118 /// Returns header name-value pairs from fields marked with `#[http_header("...")]`.
119 /// For Option fields, headers are only included if the value is Some.
120 fn http_headers(&self) -> Vec<(http::HeaderName, http::HeaderValue)>;
121}