async_graphql/
response.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{CacheControl, Result, ServerError, Value};
6
7/// Query response
8#[non_exhaustive]
9#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
10pub struct Response {
11    /// Data of query result
12    #[serde(default)]
13    pub data: Value,
14
15    /// Extensions result
16    #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
17    pub extensions: BTreeMap<String, Value>,
18
19    /// Cache control value
20    #[serde(skip)]
21    pub cache_control: CacheControl,
22
23    /// Errors
24    #[serde(skip_serializing_if = "Vec::is_empty", default)]
25    pub errors: Vec<ServerError>,
26
27    /// HTTP headers
28    #[serde(skip)]
29    pub http_headers: http::HeaderMap,
30}
31
32impl Response {
33    /// Create a new successful response with the data.
34    #[must_use]
35    pub fn new(data: impl Into<Value>) -> Self {
36        Self {
37            data: data.into(),
38            ..Default::default()
39        }
40    }
41
42    /// Create a response from some errors.
43    #[must_use]
44    pub fn from_errors(errors: Vec<ServerError>) -> Self {
45        Self {
46            errors,
47            ..Default::default()
48        }
49    }
50
51    /// Set the extension result of the response.
52    #[must_use]
53    pub fn extension(mut self, name: impl Into<String>, value: Value) -> Self {
54        self.extensions.insert(name.into(), value);
55        self
56    }
57
58    /// Set the http headers of the response.
59    #[must_use]
60    pub fn http_headers(self, http_headers: http::HeaderMap) -> Self {
61        Self {
62            http_headers,
63            ..self
64        }
65    }
66
67    /// Set the cache control of the response.
68    #[must_use]
69    pub fn cache_control(self, cache_control: CacheControl) -> Self {
70        Self {
71            cache_control,
72            ..self
73        }
74    }
75
76    /// Returns `true` if the response is ok.
77    #[inline]
78    pub fn is_ok(&self) -> bool {
79        self.errors.is_empty()
80    }
81
82    /// Returns `true` if the response is error.
83    #[inline]
84    pub fn is_err(&self) -> bool {
85        !self.is_ok()
86    }
87
88    /// Extract the error from the response. Only if the `error` field is empty
89    /// will this return `Ok`.
90    #[inline]
91    pub fn into_result(self) -> Result<Self, Vec<ServerError>> {
92        if self.is_err() {
93            Err(self.errors)
94        } else {
95            Ok(self)
96        }
97    }
98}
99
100/// Response for batchable queries
101#[allow(clippy::large_enum_variant)]
102#[derive(Debug, Serialize)]
103#[serde(untagged)]
104pub enum BatchResponse {
105    /// Response for single queries
106    Single(Response),
107
108    /// Response for batch queries
109    Batch(Vec<Response>),
110}
111
112impl BatchResponse {
113    /// Gets cache control value
114    pub fn cache_control(&self) -> CacheControl {
115        match self {
116            BatchResponse::Single(resp) => resp.cache_control,
117            BatchResponse::Batch(resp) => resp.iter().fold(CacheControl::default(), |acc, item| {
118                acc.merge(&item.cache_control)
119            }),
120        }
121    }
122
123    /// Returns `true` if all responses are ok.
124    pub fn is_ok(&self) -> bool {
125        match self {
126            BatchResponse::Single(resp) => resp.is_ok(),
127            BatchResponse::Batch(resp) => resp.iter().all(Response::is_ok),
128        }
129    }
130
131    /// Returns HTTP headers map.
132    pub fn http_headers(&self) -> http::HeaderMap {
133        match self {
134            BatchResponse::Single(resp) => resp.http_headers.clone(),
135            BatchResponse::Batch(resp) => {
136                resp.iter().fold(http::HeaderMap::new(), |mut acc, resp| {
137                    acc.extend(resp.http_headers.clone());
138                    acc
139                })
140            }
141        }
142    }
143
144    /// Returns HTTP headers iterator.
145    pub fn http_headers_iter(&self) -> impl Iterator<Item = (http::HeaderName, http::HeaderValue)> {
146        let headers = self.http_headers();
147
148        let mut current_name = None;
149        headers.into_iter().filter_map(move |(name, value)| {
150            if let Some(name) = name {
151                current_name = Some(name);
152            }
153            current_name
154                .clone()
155                .map(|current_name| (current_name, value))
156        })
157    }
158}
159
160impl From<Response> for BatchResponse {
161    fn from(response: Response) -> Self {
162        Self::Single(response)
163    }
164}
165
166impl From<Vec<Response>> for BatchResponse {
167    fn from(responses: Vec<Response>) -> Self {
168        Self::Batch(responses)
169    }
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_batch_response_single() {
178        let resp = BatchResponse::Single(Response::new(Value::Boolean(true)));
179        assert_eq!(serde_json::to_string(&resp).unwrap(), r#"{"data":true}"#);
180    }
181
182    #[test]
183    fn test_batch_response_batch() {
184        let resp = BatchResponse::Batch(vec![
185            Response::new(Value::Boolean(true)),
186            Response::new(Value::String("1".to_string())),
187        ]);
188        assert_eq!(
189            serde_json::to_string(&resp).unwrap(),
190            r#"[{"data":true},{"data":"1"}]"#
191        );
192    }
193}