#![deny(missing_docs)]
#![warn(rust_2018_idioms)]
#[cfg(feature = "graphql_query_derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate graphql_query_derive;
#[cfg(feature = "graphql_query_derive")]
#[doc(hidden)]
pub use graphql_query_derive::*;
#[cfg(any(
feature = "reqwest",
feature = "reqwest-rustls",
feature = "reqwest-blocking"
))]
pub mod reqwest;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{self, Display, Write};
pub trait GraphQLQuery {
type Variables: serde::Serialize;
type ResponseData: for<'de> serde::Deserialize<'de>;
fn build_query(variables: Self::Variables) -> QueryBody<Self::Variables>;
}
#[derive(Debug, Serialize, Deserialize)]
pub struct QueryBody<Variables> {
pub variables: Variables,
pub query: &'static str,
#[serde(rename = "operationName")]
pub operation_name: &'static str,
}
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Location {
pub line: i32,
pub column: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(untagged)]
pub enum PathFragment {
Key(String),
Index(i32),
}
impl Display for PathFragment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
PathFragment::Key(ref key) => write!(f, "{}", key),
PathFragment::Index(ref idx) => write!(f, "{}", idx),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Error {
pub message: String,
pub locations: Option<Vec<Location>>,
pub path: Option<Vec<PathFragment>>,
pub extensions: Option<HashMap<String, serde_json::Value>>,
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let path = self
.path
.as_ref()
.map(|fragments| {
fragments
.iter()
.fold(String::new(), |mut acc, item| {
let _ = write!(acc, "{}/", item);
acc
})
.trim_end_matches('/')
.to_string()
})
.unwrap_or_else(|| "<query>".to_string());
let loc = self
.locations
.as_ref()
.and_then(|locations| locations.iter().next())
.cloned()
.unwrap_or_default();
write!(f, "{}:{}:{}: {}", path, loc.line, loc.column, self.message)
}
}
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct Response<Data> {
pub data: Option<Data>,
pub errors: Option<Vec<Error>>,
pub extensions: Option<HashMap<String, serde_json::Value>>,
}
#[doc(hidden)]
pub mod _private {
pub use ::serde;
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn graphql_error_works_with_just_message() {
let err = json!({
"message": "I accidentally your whole query"
});
let deserialized_error: Error = serde_json::from_value(err).unwrap();
assert_eq!(
deserialized_error,
Error {
message: "I accidentally your whole query".to_string(),
locations: None,
path: None,
extensions: None,
}
)
}
#[test]
fn full_graphql_error_deserialization() {
let err = json!({
"message": "I accidentally your whole query",
"locations": [{ "line": 3, "column": 13}, {"line": 56, "column": 1}],
"path": ["home", "alone", 3, "rating"]
});
let deserialized_error: Error = serde_json::from_value(err).unwrap();
assert_eq!(
deserialized_error,
Error {
message: "I accidentally your whole query".to_string(),
locations: Some(vec![
Location {
line: 3,
column: 13,
},
Location {
line: 56,
column: 1,
},
]),
path: Some(vec![
PathFragment::Key("home".to_owned()),
PathFragment::Key("alone".to_owned()),
PathFragment::Index(3),
PathFragment::Key("rating".to_owned()),
]),
extensions: None,
}
)
}
#[test]
fn full_graphql_error_with_extensions_deserialization() {
let err = json!({
"message": "I accidentally your whole query",
"locations": [{ "line": 3, "column": 13}, {"line": 56, "column": 1}],
"path": ["home", "alone", 3, "rating"],
"extensions": {
"code": "CAN_NOT_FETCH_BY_ID",
"timestamp": "Fri Feb 9 14:33:09 UTC 2018"
}
});
let deserialized_error: Error = serde_json::from_value(err).unwrap();
let mut expected_extensions = HashMap::new();
expected_extensions.insert("code".to_owned(), json!("CAN_NOT_FETCH_BY_ID"));
expected_extensions.insert("timestamp".to_owned(), json!("Fri Feb 9 14:33:09 UTC 2018"));
let expected_extensions = Some(expected_extensions);
assert_eq!(
deserialized_error,
Error {
message: "I accidentally your whole query".to_string(),
locations: Some(vec![
Location {
line: 3,
column: 13,
},
Location {
line: 56,
column: 1,
},
]),
path: Some(vec![
PathFragment::Key("home".to_owned()),
PathFragment::Key("alone".to_owned()),
PathFragment::Index(3),
PathFragment::Key("rating".to_owned()),
]),
extensions: expected_extensions,
}
)
}
}