apollo_compiler/response.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
//! GraphQL [responses](https://spec.graphql.org/draft/#sec-Response)
//!
//! This exists primarily to support [`introspection::partial_execute`].
#[cfg(doc)]
use crate::introspection;
use crate::parser::LineColumn;
use crate::parser::SourceMap;
use crate::parser::SourceSpan;
use serde::Deserialize;
use serde::Serialize;
/// Re-export of the version of the `serde_json_bytes` crate used for [`JsonValue`] and [`JsonMap`]
pub use serde_json_bytes;
/// A JSON-compatible dynamically-typed value.
///
/// Note: [`serde_json_bytes::Value`] is similar
/// to [`serde_json::Value`][serde_json_bytes::serde_json::Value]
/// but uses its reference-counted [`ByteString`][serde_json_bytes::ByteString]
/// for string values and map keys.
pub type JsonValue = serde_json_bytes::Value;
/// A JSON-compatible object/map with string keys and dynamically-typed values.
pub type JsonMap = serde_json_bytes::Map<serde_json_bytes::ByteString, JsonValue>;
/// A [response](https://spec.graphql.org/October2021/#sec-Response-Format)
/// to a GraphQL request that did not cause any [request error][crate::request::RequestError]
/// and started [execution](https://spec.graphql.org/draft/#sec-Execution)
/// of selection sets and fields.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ExecutionResponse {
// <https://spec.graphql.org/October2021/#note-6f005> suggests serializing this first
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub errors: Vec<GraphQLError>,
pub data: Option<JsonMap>,
}
/// A serializable [error](https://spec.graphql.org/October2021/#sec-Errors.Error-result-format),
/// as found in a GraphQL response.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct GraphQLError {
/// The error message.
pub message: String,
/// Locations in relevant to the error, if any.
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub locations: Vec<LineColumn>,
/// If non-empty, the error is a [field error]
/// for the particular field found at this path in [`ExecutionResponse::data`].
///
/// [field error]: https://spec.graphql.org/October2021/#sec-Errors.Field-errors
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub path: Vec<ResponseDataPathSegment>,
/// Reserved for any additional information
#[serde(skip_serializing_if = "JsonMap::is_empty")]
#[serde(default)]
pub extensions: JsonMap,
}
/// A `Vec<ResponseDataPathSegment>` like in [`GraphQLError::path`]
/// represents a [path](https://spec.graphql.org/draft/#sec-Errors.Error-Result-Format)
/// into [`ExecutionResponse::data`],
/// starting at the root and indexing into increasingly nested JSON objects or arrays.
///
/// # Example
///
/// In a GraphQL response like this:
///
/// ```json
/// {
/// "data": {
/// "players": [
/// {"name": "Alice"},
/// {"name": "Bob"}
/// ]
/// },
/// "errors": [
/// {
/// "message": "Something went wrong",
/// "path": ["players", 1, "name"]
/// }
/// ]
/// }
/// ```
///
/// The error path would have a Rust representation like
/// `vec![Field("players"), ListIndex(1), Field("name")]`
/// and designate the value `"name": "Bob"`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ResponseDataPathSegment {
/// The relevant key in an object value
Field(crate::Name),
/// The index of the relevant item in a list value
ListIndex(usize),
}
impl GraphQLError {
pub fn new(
message: impl Into<String>,
location: Option<SourceSpan>,
sources: &SourceMap,
) -> Self {
Self {
message: message.into(),
locations: location
.into_iter()
.filter_map(|location| location.line_column(sources))
.collect(),
path: Default::default(),
extensions: Default::default(),
}
}
}