Skip to main content

hive_router_plan_executor/response/
subgraph_response.rs

1use core::fmt;
2use std::sync::Arc;
3
4use crate::response::{graphql_error::GraphQLError, value::Value};
5use bytes::Bytes;
6use http::HeaderMap;
7use serde::de::{self, Deserializer, MapAccess, Visitor};
8
9#[derive(Debug, Default)]
10pub struct SubgraphResponse<'a> {
11    pub data: Value<'a>,
12    pub errors: Option<Vec<GraphQLError>>,
13    pub extensions: Option<Value<'a>>,
14    pub headers: Option<Arc<HeaderMap>>,
15    pub bytes: Option<Bytes>,
16}
17
18impl<'de> de::Deserialize<'de> for SubgraphResponse<'de> {
19    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
20    where
21        D: Deserializer<'de>,
22    {
23        struct SubgraphResponseVisitor<'de> {
24            _marker: std::marker::PhantomData<&'de ()>,
25        }
26
27        impl<'de> Visitor<'de> for SubgraphResponseVisitor<'de> {
28            type Value = SubgraphResponse<'de>;
29
30            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
31                formatter
32                    .write_str("a GraphQL response object with data, errors, and extensions fields")
33            }
34
35            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
36            where
37                A: MapAccess<'de>,
38            {
39                let mut data = None;
40                let mut errors = None;
41                let mut extensions = None;
42
43                while let Some(key) = map.next_key::<&str>()? {
44                    match key {
45                        "data" => {
46                            if data.is_some() {
47                                return Err(de::Error::duplicate_field("data"));
48                            }
49                            // Let Value's deserializer handle the data field
50                            data = Some(map.next_value()?);
51                        }
52                        "errors" => {
53                            if errors.is_some() {
54                                return Err(de::Error::duplicate_field("errors"));
55                            }
56                            // For errors, deserialize into our new `GraphQLError` struct
57                            errors = Some(map.next_value()?);
58                        }
59                        "extensions" => {
60                            if extensions.is_some() {
61                                return Err(de::Error::duplicate_field("extensions"));
62                            }
63                            // Let Value's deserializer handle the extensions field
64                            extensions = Some(map.next_value()?);
65                        }
66                        _ => {
67                            // Skip unknown fields
68                            let _ = map.next_value::<de::IgnoredAny>()?;
69                        }
70                    }
71                }
72
73                // Data field is required in a successful response, but might be null in case of errors
74                let data = data.unwrap_or(Value::Null);
75
76                Ok(SubgraphResponse {
77                    data,
78                    errors,
79                    extensions,
80                    headers: None,
81                    bytes: None,
82                })
83            }
84        }
85
86        deserializer.deserialize_map(SubgraphResponseVisitor {
87            _marker: std::marker::PhantomData,
88        })
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    // When subgraph returns an error with custom extensions but without `data` field
95    #[test]
96    fn deserialize_response_without_data_with_errors_with_extensions() {
97        let json_response = r#"
98        {
99            "errors": [
100                {
101                    "message": "Random error from subgraph",
102                    "extensions":{
103                        "statusCode": 400
104                    }
105                }
106            ]
107        }"#;
108
109        let response: super::SubgraphResponse =
110            sonic_rs::from_str(json_response).expect("Failed to deserialize");
111
112        assert!(response.data.is_null());
113        let errors = response.errors.as_ref().unwrap();
114        insta::assert_snapshot!(sonic_rs::to_string_pretty(&errors).unwrap(), @r###"
115        [
116          {
117            "message": "Random error from subgraph",
118            "extensions": {
119              "statusCode": 400
120            }
121          }
122        ]"###);
123    }
124}