1#![allow(missing_docs)] use std::time::Instant;
3
4use apollo_compiler::response::ExecutionResponse;
5use bytes::Bytes;
6use displaydoc::Display;
7use serde::Deserialize;
8use serde::Serialize;
9use serde_json_bytes::ByteString;
10use serde_json_bytes::Map;
11
12use crate::error::Error;
13use crate::graphql::IntoGraphQLErrors;
14use crate::json_ext::Object;
15use crate::json_ext::Path;
16use crate::json_ext::Value;
17
18#[derive(thiserror::Error, Display, Debug, Eq, PartialEq)]
19#[error("GraphQL response was malformed: {reason}")]
20pub(crate) struct MalformedResponseError {
21 pub(crate) reason: String,
23}
24
25#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)]
28#[serde(rename_all = "camelCase")]
29#[non_exhaustive]
30pub struct Response {
31 #[serde(skip_serializing_if = "Option::is_none", default)]
33 pub label: Option<String>,
34
35 #[serde(skip_serializing_if = "Option::is_none", default)]
37 pub data: Option<Value>,
38
39 #[serde(skip_serializing_if = "Option::is_none", default)]
41 pub path: Option<Path>,
42
43 #[serde(skip_serializing_if = "Vec::is_empty", default)]
45 pub errors: Vec<Error>,
46
47 #[serde(skip_serializing_if = "Object::is_empty", default)]
49 pub extensions: Object,
50
51 #[serde(skip_serializing_if = "Option::is_none", default)]
52 pub has_next: Option<bool>,
53
54 #[serde(skip, default)]
55 pub subscribed: Option<bool>,
56
57 #[serde(skip, default)]
59 pub created_at: Option<Instant>,
60
61 #[serde(skip_serializing_if = "Vec::is_empty", default)]
62 pub incremental: Vec<IncrementalResponse>,
63}
64
65#[buildstructor::buildstructor]
66impl Response {
67 #[builder(visibility = "pub")]
69 fn new(
70 label: Option<String>,
71 data: Option<Value>,
72 path: Option<Path>,
73 errors: Vec<Error>,
74 extensions: Map<ByteString, Value>,
75 _subselection: Option<String>,
76 has_next: Option<bool>,
77 subscribed: Option<bool>,
78 incremental: Vec<IncrementalResponse>,
79 created_at: Option<Instant>,
80 ) -> Self {
81 Self {
82 label,
83 data,
84 path,
85 errors,
86 extensions,
87 has_next,
88 subscribed,
89 incremental,
90 created_at,
91 }
92 }
93
94 pub fn is_primary(&self) -> bool {
96 self.path.is_none()
97 }
98
99 pub fn append_errors(&mut self, errors: &mut Vec<Error>) {
101 self.errors.append(errors)
102 }
103
104 pub(crate) fn from_bytes(b: Bytes) -> Result<Response, MalformedResponseError> {
108 let value = Value::from_bytes(b).map_err(|error| MalformedResponseError {
109 reason: error.to_string(),
110 })?;
111 Response::from_value(value)
112 }
113
114 pub(crate) fn from_value(value: Value) -> Result<Response, MalformedResponseError> {
115 let mut object = ensure_object!(value).map_err(|error| MalformedResponseError {
116 reason: error.to_string(),
117 })?;
118 let data = object.remove("data");
119 let errors = extract_key_value_from_object!(object, "errors", Value::Array(v) => v)
120 .map_err(|err| MalformedResponseError {
121 reason: err.to_string(),
122 })?
123 .into_iter()
124 .flatten()
125 .map(Error::from_value)
126 .collect::<Result<Vec<Error>, MalformedResponseError>>()?;
127 let extensions =
128 extract_key_value_from_object!(object, "extensions", Value::Object(o) => o)
129 .map_err(|err| MalformedResponseError {
130 reason: err.to_string(),
131 })?
132 .unwrap_or_default();
133 let label = extract_key_value_from_object!(object, "label", Value::String(s) => s)
134 .map_err(|err| MalformedResponseError {
135 reason: err.to_string(),
136 })?
137 .map(|s| s.as_str().to_string());
138 let path = extract_key_value_from_object!(object, "path")
139 .map(serde_json_bytes::from_value)
140 .transpose()
141 .map_err(|err| MalformedResponseError {
142 reason: err.to_string(),
143 })?;
144 let has_next = extract_key_value_from_object!(object, "hasNext", Value::Bool(b) => b)
145 .map_err(|err| MalformedResponseError {
146 reason: err.to_string(),
147 })?;
148 let incremental =
149 extract_key_value_from_object!(object, "incremental", Value::Array(a) => a).map_err(
150 |err| MalformedResponseError {
151 reason: err.to_string(),
152 },
153 )?;
154 let incremental: Vec<IncrementalResponse> = match incremental {
155 Some(v) => v
156 .into_iter()
157 .map(serde_json_bytes::from_value)
158 .collect::<Result<Vec<IncrementalResponse>, _>>()
159 .map_err(|err| MalformedResponseError {
160 reason: err.to_string(),
161 })?,
162 None => vec![],
163 };
164 if data.is_none() && errors.is_empty() {
168 return Err(MalformedResponseError {
169 reason: "graphql response without data must contain at least one error".to_string(),
170 });
171 }
172
173 Ok(Response {
174 label,
175 data,
176 path,
177 errors,
178 extensions,
179 has_next,
180 subscribed: None,
181 incremental,
182 created_at: None,
183 })
184 }
185}
186
187#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)]
190#[serde(rename_all = "camelCase")]
191#[non_exhaustive]
192pub struct IncrementalResponse {
193 #[serde(skip_serializing_if = "Option::is_none", default)]
195 pub label: Option<String>,
196
197 #[serde(skip_serializing_if = "Option::is_none", default)]
199 pub data: Option<Value>,
200
201 #[serde(skip_serializing_if = "Option::is_none", default)]
203 pub path: Option<Path>,
204
205 #[serde(skip_serializing_if = "Vec::is_empty", default)]
207 pub errors: Vec<Error>,
208
209 #[serde(skip_serializing_if = "Object::is_empty", default)]
211 pub extensions: Object,
212}
213
214#[buildstructor::buildstructor]
215impl IncrementalResponse {
216 #[builder(visibility = "pub")]
218 fn new(
219 label: Option<String>,
220 data: Option<Value>,
221 path: Option<Path>,
222 errors: Vec<Error>,
223 extensions: Map<ByteString, Value>,
224 ) -> Self {
225 Self {
226 label,
227 data,
228 path,
229 errors,
230 extensions,
231 }
232 }
233
234 pub fn append_errors(&mut self, errors: &mut Vec<Error>) {
236 self.errors.append(errors)
237 }
238}
239
240impl From<ExecutionResponse> for Response {
241 fn from(response: ExecutionResponse) -> Response {
242 let ExecutionResponse { errors, data } = response;
243 Self {
244 errors: errors.into_graphql_errors().unwrap(),
245 data: data.map(serde_json_bytes::Value::Object),
246 extensions: Default::default(),
247 label: None,
248 path: None,
249 has_next: None,
250 subscribed: None,
251 created_at: None,
252 incremental: Vec::new(),
253 }
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use serde_json::json;
260 use serde_json_bytes::json as bjson;
261
262 use super::*;
263 use crate::graphql::Location;
264
265 #[test]
266 fn test_append_errors_path_fallback_and_override() {
267 let expected_errors = vec![
268 Error {
269 message: "Something terrible happened!".to_string(),
270 path: Some(Path::from("here")),
271 ..Default::default()
272 },
273 Error {
274 message: "I mean for real".to_string(),
275 ..Default::default()
276 },
277 ];
278
279 let mut errors_to_append = vec![
280 Error {
281 message: "Something terrible happened!".to_string(),
282 path: Some(Path::from("here")),
283 ..Default::default()
284 },
285 Error {
286 message: "I mean for real".to_string(),
287 ..Default::default()
288 },
289 ];
290
291 let mut response = Response::builder().build();
292 response.append_errors(&mut errors_to_append);
293 assert_eq!(response.errors, expected_errors);
294 }
295
296 #[test]
297 fn test_response() {
298 let result = serde_json::from_str::<Response>(
299 json!(
300 {
301 "errors": [
302 {
303 "message": "Name for character with ID 1002 could not be fetched.",
304 "locations": [{ "line": 6, "column": 7 }],
305 "path": ["hero", "heroFriends", 1, "name"],
306 "extensions": {
307 "error-extension": 5,
308 }
309 }
310 ],
311 "data": {
312 "hero": {
313 "name": "R2-D2",
314 "heroFriends": [
315 {
316 "id": "1000",
317 "name": "Luke Skywalker"
318 },
319 {
320 "id": "1002",
321 "name": null
322 },
323 {
324 "id": "1003",
325 "name": "Leia Organa"
326 }
327 ]
328 }
329 },
330 "extensions": {
331 "response-extension": 3,
332 }
333 })
334 .to_string()
335 .as_str(),
336 );
337 assert_eq!(
338 result.unwrap(),
339 Response::builder()
340 .data(json!({
341 "hero": {
342 "name": "R2-D2",
343 "heroFriends": [
344 {
345 "id": "1000",
346 "name": "Luke Skywalker"
347 },
348 {
349 "id": "1002",
350 "name": null
351 },
352 {
353 "id": "1003",
354 "name": "Leia Organa"
355 }
356 ]
357 }
358 }))
359 .errors(vec![Error {
360 message: "Name for character with ID 1002 could not be fetched.".into(),
361 locations: vec!(Location { line: 6, column: 7 }),
362 path: Some(Path::from("hero/heroFriends/1/name")),
363 extensions: bjson!({
364 "error-extension": 5,
365 })
366 .as_object()
367 .cloned()
368 .unwrap()
369 }])
370 .extensions(
371 bjson!({
372 "response-extension": 3,
373 })
374 .as_object()
375 .cloned()
376 .unwrap()
377 )
378 .build()
379 );
380 }
381
382 #[test]
383 fn test_patch_response() {
384 let result = serde_json::from_str::<Response>(
385 json!(
386 {
387 "label": "part",
388 "hasNext": true,
389 "path": ["hero", "heroFriends", 1, "name"],
390 "errors": [
391 {
392 "message": "Name for character with ID 1002 could not be fetched.",
393 "locations": [{ "line": 6, "column": 7 }],
394 "path": ["hero", "heroFriends", 1, "name"],
395 "extensions": {
396 "error-extension": 5,
397 }
398 }
399 ],
400 "data": {
401 "hero": {
402 "name": "R2-D2",
403 "heroFriends": [
404 {
405 "id": "1000",
406 "name": "Luke Skywalker"
407 },
408 {
409 "id": "1002",
410 "name": null
411 },
412 {
413 "id": "1003",
414 "name": "Leia Organa"
415 }
416 ]
417 }
418 },
419 "extensions": {
420 "response-extension": 3,
421 }
422 })
423 .to_string()
424 .as_str(),
425 );
426 assert_eq!(
427 result.unwrap(),
428 Response::builder()
429 .label("part".to_owned())
430 .data(json!({
431 "hero": {
432 "name": "R2-D2",
433 "heroFriends": [
434 {
435 "id": "1000",
436 "name": "Luke Skywalker"
437 },
438 {
439 "id": "1002",
440 "name": null
441 },
442 {
443 "id": "1003",
444 "name": "Leia Organa"
445 }
446 ]
447 }
448 }))
449 .path(Path::from("hero/heroFriends/1/name"))
450 .errors(vec![Error {
451 message: "Name for character with ID 1002 could not be fetched.".into(),
452 locations: vec!(Location { line: 6, column: 7 }),
453 path: Some(Path::from("hero/heroFriends/1/name")),
454 extensions: bjson!({
455 "error-extension": 5,
456 })
457 .as_object()
458 .cloned()
459 .unwrap()
460 }])
461 .extensions(
462 bjson!({
463 "response-extension": 3,
464 })
465 .as_object()
466 .cloned()
467 .unwrap()
468 )
469 .has_next(true)
470 .build()
471 );
472 }
473
474 #[test]
475 fn test_no_data_and_no_errors() {
476 let response = Response::from_bytes("{\"errors\":null}".into());
477 assert_eq!(
478 response.expect_err("no data and no errors"),
479 MalformedResponseError {
480 reason: "graphql response without data must contain at least one error".to_string(),
481 }
482 );
483 }
484
485 #[test]
486 fn test_data_null() {
487 let response = Response::from_bytes("{\"data\":null}".into()).unwrap();
488 assert_eq!(
489 response,
490 Response::builder().data(Some(Value::Null)).build(),
491 );
492 }
493}