async_graphql/
response.rs1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::{CacheControl, Result, ServerError, Value};
6
7#[non_exhaustive]
9#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
10pub struct Response {
11 #[serde(default)]
13 pub data: Value,
14
15 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
17 pub extensions: BTreeMap<String, Value>,
18
19 #[serde(skip)]
21 pub cache_control: CacheControl,
22
23 #[serde(skip_serializing_if = "Vec::is_empty", default)]
25 pub errors: Vec<ServerError>,
26
27 #[serde(skip)]
29 pub http_headers: http::HeaderMap,
30}
31
32impl Response {
33 #[must_use]
35 pub fn new(data: impl Into<Value>) -> Self {
36 Self {
37 data: data.into(),
38 ..Default::default()
39 }
40 }
41
42 #[must_use]
44 pub fn from_errors(errors: Vec<ServerError>) -> Self {
45 Self {
46 errors,
47 ..Default::default()
48 }
49 }
50
51 #[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 #[must_use]
60 pub fn http_headers(self, http_headers: http::HeaderMap) -> Self {
61 Self {
62 http_headers,
63 ..self
64 }
65 }
66
67 #[must_use]
69 pub fn cache_control(self, cache_control: CacheControl) -> Self {
70 Self {
71 cache_control,
72 ..self
73 }
74 }
75
76 #[inline]
78 pub fn is_ok(&self) -> bool {
79 self.errors.is_empty()
80 }
81
82 #[inline]
84 pub fn is_err(&self) -> bool {
85 !self.is_ok()
86 }
87
88 #[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#[allow(clippy::large_enum_variant)]
102#[derive(Debug, Serialize)]
103#[serde(untagged)]
104pub enum BatchResponse {
105 Single(Response),
107
108 Batch(Vec<Response>),
110}
111
112impl BatchResponse {
113 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 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 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 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}