hive_router_plan_executor/response/
graphql_error.rs

1use core::fmt;
2use graphql_parser::Pos;
3use graphql_tools::validation::utils::ValidationError;
4use serde::{de, Deserialize, Deserializer, Serialize};
5use sonic_rs::Value;
6use std::collections::HashMap;
7
8#[derive(Clone, Debug, Deserialize, Serialize)]
9#[serde(rename_all = "camelCase")]
10pub struct GraphQLError {
11    pub message: String,
12    #[serde(default, skip_serializing_if = "is_none_or_empty")]
13    pub locations: Option<Vec<GraphQLErrorLocation>>,
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub path: Option<GraphQLErrorPath>,
16    #[serde(default, skip_serializing_if = "GraphQLErrorExtensions::is_empty")]
17    pub extensions: GraphQLErrorExtensions,
18}
19
20fn is_none_or_empty<T>(opt: &Option<Vec<T>>) -> bool {
21    opt.as_ref().is_none_or(|v| v.is_empty())
22}
23
24impl From<String> for GraphQLError {
25    fn from(message: String) -> Self {
26        GraphQLError {
27            message,
28            locations: None,
29            path: None,
30            extensions: GraphQLErrorExtensions::default(),
31        }
32    }
33}
34
35impl From<&str> for GraphQLError {
36    fn from(message: &str) -> Self {
37        GraphQLError {
38            message: message.to_string(),
39            locations: None,
40            path: None,
41            extensions: GraphQLErrorExtensions::default(),
42        }
43    }
44}
45
46impl From<&ValidationError> for GraphQLError {
47    fn from(val: &ValidationError) -> Self {
48        GraphQLError {
49            message: val.message.to_string(),
50            locations: Some(val.locations.iter().map(|pos| pos.into()).collect()),
51            path: None,
52            extensions: GraphQLErrorExtensions::new_from_code("GRAPHQL_VALIDATION_FAILED"),
53        }
54    }
55}
56
57impl From<&Pos> for GraphQLErrorLocation {
58    fn from(val: &Pos) -> Self {
59        GraphQLErrorLocation {
60            line: val.line,
61            column: val.column,
62        }
63    }
64}
65
66impl GraphQLError {
67    pub fn entity_index_and_path<'a>(&'a self) -> Option<EntityIndexAndPath<'a>> {
68        self.path.as_ref().and_then(|p| p.entity_index_and_path())
69    }
70
71    pub fn normalize_entity_error(
72        self,
73        entity_index_error_map: &HashMap<&usize, Vec<GraphQLErrorPath>>,
74    ) -> Vec<GraphQLError> {
75        if let Some(entity_index_and_path) = &self.entity_index_and_path() {
76            if let Some(entity_error_paths) =
77                entity_index_error_map.get(&entity_index_and_path.entity_index)
78            {
79                return entity_error_paths
80                    .iter()
81                    .map(|error_path| {
82                        let mut new_error_path = error_path.clone();
83                        new_error_path.extend_from_slice(entity_index_and_path.rest_of_path);
84                        GraphQLError {
85                            path: Some(new_error_path),
86                            ..self.clone()
87                        }
88                    })
89                    .collect();
90            }
91        }
92        vec![self]
93    }
94
95    pub fn from_message_and_extensions(
96        message: String,
97        extensions: GraphQLErrorExtensions,
98    ) -> Self {
99        GraphQLError {
100            message,
101            locations: None,
102            path: None,
103            extensions,
104        }
105    }
106
107    pub fn add_subgraph_name(mut self, subgraph_name: &str) -> Self {
108        self.extensions
109            .service_name
110            .get_or_insert(subgraph_name.to_string());
111        self.extensions
112            .code
113            .get_or_insert("DOWNSTREAM_SERVICE_ERROR".to_string());
114        self
115    }
116
117    pub fn add_affected_path(mut self, affected_path: String) -> Self {
118        self.extensions.affected_path = Some(affected_path);
119        self
120    }
121}
122
123#[derive(Clone, Debug, Deserialize, Serialize)]
124pub struct GraphQLErrorLocation {
125    pub line: usize,
126    pub column: usize,
127}
128
129#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
130#[serde(untagged)]
131pub enum GraphQLErrorPathSegment {
132    String(String),
133    Index(usize),
134}
135
136#[derive(Clone, Debug, Default, Deserialize, Serialize)]
137#[serde(transparent)]
138pub struct GraphQLErrorPath {
139    pub segments: Vec<GraphQLErrorPathSegment>,
140}
141
142pub struct EntityIndexAndPath<'a> {
143    pub entity_index: usize,
144    pub rest_of_path: &'a [GraphQLErrorPathSegment],
145}
146
147impl GraphQLErrorPath {
148    pub fn with_capacity(capacity: usize) -> Self {
149        GraphQLErrorPath {
150            segments: Vec::with_capacity(capacity),
151        }
152    }
153    pub fn concat(&self, segment: GraphQLErrorPathSegment) -> Self {
154        let mut new_path = self.segments.clone();
155        new_path.push(segment);
156        GraphQLErrorPath { segments: new_path }
157    }
158
159    pub fn concat_index(&self, index: usize) -> Self {
160        self.concat(GraphQLErrorPathSegment::Index(index))
161    }
162
163    pub fn concat_str(&self, field: String) -> Self {
164        self.concat(GraphQLErrorPathSegment::String(field))
165    }
166
167    pub fn extend_from_slice(&mut self, other: &[GraphQLErrorPathSegment]) {
168        self.segments.extend_from_slice(other);
169    }
170
171    pub fn entity_index_and_path<'a>(&'a self) -> Option<EntityIndexAndPath<'a>> {
172        match &self.segments.as_slice() {
173            [GraphQLErrorPathSegment::String(maybe_entities), GraphQLErrorPathSegment::Index(entity_index), rest_of_path @ ..]
174                if maybe_entities == "_entities" =>
175            {
176                Some(EntityIndexAndPath {
177                    entity_index: *entity_index,
178                    rest_of_path,
179                })
180            }
181            _ => None,
182        }
183    }
184}
185
186#[derive(Clone, Debug, Serialize, Default)]
187#[serde(rename_all = "camelCase")]
188pub struct GraphQLErrorExtensions {
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub code: Option<String>,
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub service_name: Option<String>,
193    /// Corresponds to a path of a Flatten(Fetch) node that caused the error.
194    #[serde(default, skip_serializing_if = "Option::is_none")]
195    pub affected_path: Option<String>,
196    #[serde(flatten)]
197    pub extensions: HashMap<String, Value>,
198}
199
200// Workaround for https://github.com/cloudwego/sonic-rs/issues/114
201
202impl<'de> Deserialize<'de> for GraphQLErrorExtensions {
203    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
204    where
205        D: Deserializer<'de>,
206    {
207        struct GraphQLErrorExtensionsVisitor;
208
209        impl<'de> de::Visitor<'de> for GraphQLErrorExtensionsVisitor {
210            type Value = GraphQLErrorExtensions;
211
212            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
213                formatter.write_str("a map for GraphQLErrorExtensions")
214            }
215
216            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
217            where
218                A: de::MapAccess<'de>,
219            {
220                let mut code = None;
221                let mut service_name = None;
222                let mut affected_path = None;
223                let mut extensions = HashMap::new();
224
225                while let Some(key) = map.next_key::<String>()? {
226                    match key.as_str() {
227                        "code" => {
228                            if code.is_some() {
229                                return Err(de::Error::duplicate_field("code"));
230                            }
231                            code = Some(map.next_value()?);
232                        }
233                        "serviceName" => {
234                            if service_name.is_some() {
235                                return Err(de::Error::duplicate_field("serviceName"));
236                            }
237                            service_name = Some(map.next_value()?);
238                        }
239                        "affectedPath" => {
240                            if affected_path.is_some() {
241                                return Err(de::Error::duplicate_field("affectedPath"));
242                            }
243                            affected_path = map.next_value()?;
244                        }
245                        other_key => {
246                            let value: Value = map.next_value()?;
247                            extensions.insert(other_key.to_string(), value);
248                        }
249                    }
250                }
251
252                Ok(GraphQLErrorExtensions {
253                    code,
254                    service_name,
255                    affected_path,
256                    extensions,
257                })
258            }
259        }
260
261        deserializer.deserialize_map(GraphQLErrorExtensionsVisitor)
262    }
263}
264
265impl GraphQLErrorExtensions {
266    pub fn new_from_code(code: &str) -> Self {
267        GraphQLErrorExtensions {
268            code: Some(code.to_string()),
269            service_name: None,
270            affected_path: None,
271            extensions: HashMap::new(),
272        }
273    }
274
275    pub fn new_from_code_and_service_name(code: &str, service_name: &str) -> Self {
276        GraphQLErrorExtensions {
277            code: Some(code.to_string()),
278            service_name: Some(service_name.to_string()),
279            affected_path: None,
280            extensions: HashMap::new(),
281        }
282    }
283
284    pub fn get(&self, key: &str) -> Option<&Value> {
285        self.extensions.get(key)
286    }
287
288    pub fn set(&mut self, key: String, value: Value) {
289        self.extensions.insert(key, value);
290    }
291
292    pub fn is_empty(&self) -> bool {
293        self.code.is_none()
294            && self.service_name.is_none()
295            && self.affected_path.is_none()
296            && self.extensions.is_empty()
297    }
298}