#[derive(Debug, Clone)]
pub struct GraphQlResponse<T, ErrorExtensions = serde::de::IgnoredAny> {
pub data: Option<T>,
pub errors: Option<Vec<GraphQlError<ErrorExtensions>>>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, thiserror::Error)]
#[error("{message}")]
pub struct GraphQlError<Extensions = serde::de::IgnoredAny> {
pub message: String,
pub locations: Option<Vec<GraphQlErrorLocation>>,
pub path: Option<Vec<GraphQlErrorPathSegment>>,
pub extensions: Option<Extensions>,
}
impl<ErrorExtensions> GraphQlError<ErrorExtensions> {
pub fn new(
message: String,
locations: Option<Vec<GraphQlErrorLocation>>,
path: Option<Vec<GraphQlErrorPathSegment>>,
extensions: Option<ErrorExtensions>,
) -> Self {
GraphQlError {
message,
locations,
path,
extensions,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
pub struct GraphQlErrorLocation {
pub line: i32,
pub column: i32,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
#[serde(untagged)]
pub enum GraphQlErrorPathSegment {
Field(String),
Index(i32),
}
impl<'de, T, ErrorExtensions> serde::Deserialize<'de> for GraphQlResponse<T, ErrorExtensions>
where
T: serde::Deserialize<'de>,
ErrorExtensions: serde::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
#[derive(serde::Deserialize)]
struct ResponseDeser<T, ErrorExtensions> {
data: Option<T>,
errors: Option<Vec<GraphQlError<ErrorExtensions>>>,
}
let ResponseDeser { data, errors } = ResponseDeser::deserialize(deserializer)?;
if data.is_none() && errors.is_none() {
return Err(D::Error::custom(
"Either data or errors must be present in a GraphQL response",
));
}
Ok(GraphQlResponse { data, errors })
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn test_default_graphql_response_ignores_extensions() {
let response = json!({
"data": null,
"errors": [{
"message": "hello",
"locations": null,
"path": null,
"extensions": {"some": "string"}
}]
});
insta::assert_debug_snapshot!(serde_json::from_value::<GraphQlResponse<()>>(response).unwrap(), @r###"
GraphQlResponse {
data: None,
errors: Some(
[
GraphQlError {
message: "hello",
locations: None,
path: None,
extensions: Some(
IgnoredAny,
),
},
],
),
}
"###);
}
#[test]
fn test_graphql_response_fails_on_completely_invalid_response() {
let response = json!({
"message": "This endpoint requires you to be authenticated.",
});
serde_json::from_value::<GraphQlResponse<()>>(response).unwrap_err();
}
}