graphql_starter/error/
graphql.rs1use std::{any::Any, sync::Arc};
2
3use async_graphql::{ErrorExtensions, Name};
4use error_info::ErrorInfo;
5use indexmap::IndexMap;
6use tracing_error::SpanTrace;
7
8use super::{Error, GenericErrorCode};
9
10pub type GraphQLResult<T, E = Box<GraphQLError>> = std::result::Result<T, E>;
12
13#[derive(Clone)]
15pub enum GraphQLError {
16 Async(async_graphql::Error, SpanTrace),
17 Custom(Box<Error>),
18}
19
20impl GraphQLError {
21 pub fn new(info: impl ErrorInfo + Send + Sync + 'static) -> Box<Self> {
23 Box::new(Self::Custom(Error::new(info)))
24 }
25
26 pub fn internal(reason: impl Into<String>) -> Box<Self> {
28 Box::new(Self::Custom(Error::internal(reason)))
29 }
30
31 pub fn from_err(error: Box<Error>) -> Box<Self> {
33 Box::new(Self::Custom(error))
34 }
35
36 #[allow(clippy::boxed_local)]
38 pub fn with_property(self: Box<Self>, key: &str, value: serde_json::Value) -> Box<Self> {
39 match *self {
40 Self::Async(err, ctx) => {
41 let err = err.extend_with(|_, e| match async_graphql::Value::try_from(value) {
42 Ok(value) => e.set(key, value),
43 Err(err) => tracing::error!("Couldn't deserialize error value: {err}"),
44 });
45 Box::new(Self::Async(err, ctx))
46 }
47 Self::Custom(err) => {
48 let err = err.with_property(key, value);
49 Box::new(Self::Custom(err))
50 }
51 }
52 }
53
54 fn is_unexpected(&self) -> bool {
56 match self {
57 GraphQLError::Async(_, _) => true,
59 GraphQLError::Custom(err) => err.unexpected,
60 }
61 }
62
63 pub fn to_string(&self, include_context: bool) -> String {
65 match self {
66 GraphQLError::Async(err, context) => {
67 let code = GenericErrorCode::InternalServerError;
68 let status = code.status();
69 if include_context {
70 format!(
71 "[{} {}] {}: {}\n{}",
72 status.as_str(),
73 status.canonical_reason().unwrap_or("Unknown"),
74 code.code(),
75 &err.message,
76 &context
77 )
78 } else {
79 format!(
80 "[{} {}] {}: {}",
81 status.as_str(),
82 status.canonical_reason().unwrap_or("Unknown"),
83 code.code(),
84 &err.message
85 )
86 }
87 }
88 GraphQLError::Custom(err) => {
89 if include_context {
90 format!("{err:#}")
91 } else {
92 format!("{err}")
93 }
94 }
95 }
96 }
97}
98
99impl From<async_graphql::Error> for Box<GraphQLError> {
100 fn from(err: async_graphql::Error) -> Self {
101 Box::new(GraphQLError::Async(err, SpanTrace::capture()))
102 }
103}
104impl From<Box<Error>> for Box<GraphQLError> {
105 fn from(err: Box<Error>) -> Self {
106 GraphQLError::from_err(err)
107 }
108}
109
110impl From<Box<GraphQLError>> for async_graphql::Error {
111 fn from(value: Box<GraphQLError>) -> Self {
112 let e = *value;
113
114 let new_error = match &e {
116 GraphQLError::Async(err, _) => err
117 .extensions
118 .as_ref()
119 .map(|e| e.get("statusCode").is_none())
120 .unwrap_or(true),
121 GraphQLError::Custom(_) => true,
122 };
123 if new_error {
124 if e.is_unexpected() {
125 tracing::error!("{}", e.to_string(true))
126 } else if tracing::event_enabled!(tracing::Level::DEBUG) {
127 tracing::warn!("{}", e.to_string(true))
128 } else {
129 tracing::warn!("{}", e.to_string(false))
130 }
131 }
132
133 let (gql_err, err_info): (async_graphql::Error, Option<Arc<dyn ErrorInfo + Send + Sync + 'static>>) = match e {
135 GraphQLError::Async(mut err, _) => {
136 if new_error {
137 err.source = Some(Arc::new(err.message));
139 err.message = GenericErrorCode::InternalServerError.raw_message().into();
140 (err, Some(Arc::new(GenericErrorCode::InternalServerError)))
141 } else {
142 (err, None)
144 }
145 }
146 GraphQLError::Custom(err) => {
147 let err = *err;
148 let source = err.source.map(|s| {
149 let source: Arc<dyn Any + Send + Sync> = Arc::new(s);
150 source
151 });
152 let async_err = async_graphql::Error {
153 message: err.info.message(),
154 source,
155 extensions: None,
156 }
157 .extend_with(|_, e| {
158 if let Some(prop) = err.properties {
159 for (k, v) in prop.into_iter() {
160 if k == "statusCode"
161 || k == "statusKind"
162 || k == "errorCode"
163 || k == "rawMessage"
164 || k == "messageFields"
165 {
166 tracing::error!("Error '{}' contains a reserved property: {}", err.info.code(), k);
167 continue;
168 }
169 match async_graphql::Value::try_from(v) {
170 Ok(v) => e.set(k, v),
171 Err(err) => tracing::error!("Couldn't deserialize error value: {err}"),
172 }
173 }
174 }
175 });
176 (async_err, Some(err.info))
177 }
178 };
179 if let Some(err_info) = err_info {
180 gql_err.extend_with(|_, e| {
182 let status = err_info.status();
183 e.set("statusCode", status.as_u16());
184 if let Some(reason) = status.canonical_reason() {
185 e.set("statusKind", reason);
186 }
187 e.set("errorCode", err_info.code());
188 e.set("rawMessage", err_info.raw_message());
189 let fields = err_info.fields();
190 if !fields.is_empty() {
191 let fields_map = IndexMap::from_iter(fields.into_iter().map(|(k, v)| (Name::new(k), v.into())));
192 e.set("messageFields", fields_map);
193 }
194 })
195 } else {
196 gql_err
197 }
198 }
199}