2020-03-31 03:19:18 +00:00
|
|
|
use std::any::Any;
|
2020-09-30 03:37:12 +00:00
|
|
|
use std::collections::HashMap;
|
2020-09-25 16:38:12 +00:00
|
|
|
use std::fmt::{self, Debug, Formatter};
|
2020-03-17 09:26:59 +00:00
|
|
|
|
2020-10-15 06:38:10 +00:00
|
|
|
use serde::{Deserialize, Deserializer};
|
|
|
|
|
|
|
|
use crate::{Data, ParseRequestError, UploadValue, Value, Variables};
|
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
/// GraphQL request.
|
|
|
|
///
|
|
|
|
/// This can be deserialized from a structure of the query string, the operation name and the
|
|
|
|
/// variables. The names are all in `camelCase` (e.g. `operationName`).
|
2020-09-21 07:53:07 +00:00
|
|
|
#[derive(Deserialize)]
|
2020-09-12 16:07:46 +00:00
|
|
|
#[serde(rename_all = "camelCase")]
|
2020-09-10 08:39:43 +00:00
|
|
|
pub struct Request {
|
2020-09-12 16:07:46 +00:00
|
|
|
/// The query source of the request.
|
2020-09-29 12:47:37 +00:00
|
|
|
#[serde(default)]
|
2020-09-12 16:07:46 +00:00
|
|
|
pub query: String,
|
2020-09-21 07:53:07 +00:00
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
/// The operation name of the request.
|
|
|
|
#[serde(default, rename = "operationName")]
|
|
|
|
pub operation_name: Option<String>,
|
2020-09-21 07:53:07 +00:00
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
/// The variables of the request.
|
2020-10-15 06:38:10 +00:00
|
|
|
#[serde(default)]
|
2020-09-12 16:07:46 +00:00
|
|
|
pub variables: Variables,
|
2020-09-21 07:53:07 +00:00
|
|
|
|
2020-10-15 06:38:10 +00:00
|
|
|
/// Uploads sent with the request.
|
2020-10-10 02:32:43 +00:00
|
|
|
#[serde(skip)]
|
|
|
|
pub uploads: Vec<UploadValue>,
|
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
/// The data of the request that can be accessed through `Context::data`.
|
|
|
|
///
|
|
|
|
/// **This data is only valid for this request**
|
|
|
|
#[serde(skip)]
|
|
|
|
pub data: Data,
|
2020-09-30 03:37:12 +00:00
|
|
|
|
|
|
|
/// The extensions config of the request.
|
|
|
|
#[serde(default)]
|
2020-10-12 02:17:05 +00:00
|
|
|
pub extensions: HashMap<String, Value>,
|
2020-09-10 04:49:08 +00:00
|
|
|
}
|
|
|
|
|
2020-09-10 08:39:43 +00:00
|
|
|
impl Request {
|
|
|
|
/// Create a request object with query source.
|
2020-09-10 04:49:08 +00:00
|
|
|
pub fn new(query: impl Into<String>) -> Self {
|
|
|
|
Self {
|
|
|
|
query: query.into(),
|
|
|
|
operation_name: None,
|
|
|
|
variables: Variables::default(),
|
2020-10-10 02:32:43 +00:00
|
|
|
uploads: Vec::default(),
|
2020-09-12 16:07:46 +00:00
|
|
|
data: Data::default(),
|
2020-09-30 03:37:12 +00:00
|
|
|
extensions: Default::default(),
|
2020-09-10 04:49:08 +00:00
|
|
|
}
|
2020-04-21 02:14:14 +00:00
|
|
|
}
|
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
/// Specify the operation name of the request.
|
2020-06-22 07:59:53 +00:00
|
|
|
pub fn operation_name<T: Into<String>>(self, name: T) -> Self {
|
2020-09-10 04:49:08 +00:00
|
|
|
Self {
|
2020-04-01 08:53:49 +00:00
|
|
|
operation_name: Some(name.into()),
|
|
|
|
..self
|
|
|
|
}
|
2020-03-17 09:26:59 +00:00
|
|
|
}
|
|
|
|
|
2020-04-01 08:53:49 +00:00
|
|
|
/// Specify the variables.
|
|
|
|
pub fn variables(self, variables: Variables) -> Self {
|
2020-09-10 04:49:08 +00:00
|
|
|
Self { variables, ..self }
|
2020-04-01 08:53:49 +00:00
|
|
|
}
|
2020-03-26 03:34:28 +00:00
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
/// Insert some data for this request.
|
2020-04-01 08:53:49 +00:00
|
|
|
pub fn data<D: Any + Send + Sync>(mut self, data: D) -> Self {
|
2020-09-12 16:07:46 +00:00
|
|
|
self.data.insert(data);
|
2020-04-01 08:53:49 +00:00
|
|
|
self
|
|
|
|
}
|
2020-03-17 09:26:59 +00:00
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
/// Set a variable to an upload value.
|
|
|
|
///
|
|
|
|
/// `var_path` is a dot-separated path to the item that begins with `variables`, for example
|
|
|
|
/// `variables.files.2.content` is equivalent to the Rust code
|
|
|
|
/// `request.variables["files"][2]["content"]`. If no variable exists at the path this function
|
|
|
|
/// won't do anything.
|
2020-10-10 02:32:43 +00:00
|
|
|
pub fn set_upload(&mut self, var_path: &str, upload: UploadValue) {
|
2020-09-06 05:38:31 +00:00
|
|
|
let variable = match self.variables.variable_path(var_path) {
|
|
|
|
Some(variable) => variable,
|
|
|
|
None => return,
|
|
|
|
};
|
2020-10-10 02:32:43 +00:00
|
|
|
self.uploads.push(upload);
|
|
|
|
*variable = Value::String(format!("#__graphql_file__:{}", self.uploads.len() - 1));
|
2020-03-17 09:26:59 +00:00
|
|
|
}
|
2020-09-10 04:49:08 +00:00
|
|
|
}
|
2020-03-17 09:26:59 +00:00
|
|
|
|
2020-09-10 08:39:43 +00:00
|
|
|
impl<T: Into<String>> From<T> for Request {
|
2020-09-10 04:49:08 +00:00
|
|
|
fn from(query: T) -> Self {
|
|
|
|
Self::new(query)
|
|
|
|
}
|
|
|
|
}
|
2020-09-12 16:07:46 +00:00
|
|
|
|
2020-09-25 16:38:12 +00:00
|
|
|
impl Debug for Request {
|
|
|
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
|
|
|
f.debug_struct("Request")
|
|
|
|
.field("query", &self.query)
|
|
|
|
.field("operation_name", &self.operation_name)
|
|
|
|
.field("variables", &self.variables)
|
2020-10-15 06:38:10 +00:00
|
|
|
.field("extensions", &self.extensions)
|
2020-09-25 16:38:12 +00:00
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-17 08:39:55 +00:00
|
|
|
/// Batch support for GraphQL requests, which is either a single query, or an array of queries
|
|
|
|
///
|
|
|
|
/// **Reference:** <https://www.apollographql.com/blog/batching-client-graphql-queries-a685f5bcd41b/>
|
2020-09-25 16:38:12 +00:00
|
|
|
#[derive(Debug, Deserialize)]
|
2020-09-17 08:39:55 +00:00
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum BatchRequest {
|
|
|
|
/// Single query
|
|
|
|
Single(Request),
|
|
|
|
|
|
|
|
/// Non-empty array of queries
|
|
|
|
#[serde(deserialize_with = "deserialize_non_empty_vec")]
|
|
|
|
Batch(Vec<Request>),
|
|
|
|
}
|
|
|
|
|
2020-09-17 18:43:03 +00:00
|
|
|
impl BatchRequest {
|
2020-09-25 16:38:12 +00:00
|
|
|
/// Attempt to convert the batch request into a single request.
|
|
|
|
///
|
|
|
|
/// # Errors
|
|
|
|
///
|
|
|
|
/// Fails if the batch request is a list of requests with a message saying that batch requests
|
|
|
|
/// aren't supported.
|
|
|
|
pub fn into_single(self) -> Result<Request, ParseRequestError> {
|
2020-09-17 18:43:03 +00:00
|
|
|
match self {
|
|
|
|
Self::Single(req) => Ok(req),
|
|
|
|
Self::Batch(_) => Err(ParseRequestError::UnsupportedBatch),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-15 06:38:10 +00:00
|
|
|
fn deserialize_non_empty_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
|
2020-09-17 08:39:55 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
T: Deserialize<'de>,
|
|
|
|
{
|
|
|
|
use serde::de::Error as _;
|
|
|
|
|
2020-10-15 06:38:10 +00:00
|
|
|
let v = <Vec<T>>::deserialize(deserializer)?;
|
2020-09-17 08:39:55 +00:00
|
|
|
if v.is_empty() {
|
2020-10-15 06:38:10 +00:00
|
|
|
Err(D::Error::invalid_length(0, &"a non-empty sequence"))
|
2020-09-17 08:39:55 +00:00
|
|
|
} else {
|
|
|
|
Ok(v)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Request> for BatchRequest {
|
|
|
|
fn from(r: Request) -> Self {
|
|
|
|
BatchRequest::Single(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Vec<Request>> for BatchRequest {
|
|
|
|
fn from(r: Vec<Request>) -> Self {
|
|
|
|
BatchRequest::Batch(r)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-12 16:07:46 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2020-10-13 02:19:30 +00:00
|
|
|
use crate::*;
|
2020-09-12 16:07:46 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_request() {
|
2020-10-13 02:19:30 +00:00
|
|
|
let request: Request = from_value(value! ({
|
2020-09-12 16:07:46 +00:00
|
|
|
"query": "{ a b c }"
|
|
|
|
}))
|
|
|
|
.unwrap();
|
|
|
|
assert!(request.variables.0.is_empty());
|
|
|
|
assert!(request.operation_name.is_none());
|
|
|
|
assert_eq!(request.query, "{ a b c }");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_request_with_operation_name() {
|
2020-10-13 02:19:30 +00:00
|
|
|
let request: Request = from_value(value! ({
|
2020-09-12 16:07:46 +00:00
|
|
|
"query": "{ a b c }",
|
|
|
|
"operationName": "a"
|
|
|
|
}))
|
|
|
|
.unwrap();
|
|
|
|
assert!(request.variables.0.is_empty());
|
|
|
|
assert_eq!(request.operation_name.as_deref(), Some("a"));
|
|
|
|
assert_eq!(request.query, "{ a b c }");
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_request_with_variables() {
|
2020-10-13 02:19:30 +00:00
|
|
|
let request: Request = from_value(value! ({
|
2020-09-12 16:07:46 +00:00
|
|
|
"query": "{ a b c }",
|
|
|
|
"variables": {
|
|
|
|
"v1": 100,
|
|
|
|
"v2": [1, 2, 3],
|
|
|
|
"v3": "str",
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(
|
2020-10-13 02:19:30 +00:00
|
|
|
request.variables.into_value(),
|
|
|
|
value!({
|
2020-09-12 16:07:46 +00:00
|
|
|
"v1": 100,
|
|
|
|
"v2": [1, 2, 3],
|
|
|
|
"v3": "str",
|
|
|
|
})
|
|
|
|
);
|
|
|
|
assert!(request.operation_name.is_none());
|
|
|
|
assert_eq!(request.query, "{ a b c }");
|
|
|
|
}
|
2020-09-17 08:39:55 +00:00
|
|
|
|
2020-09-27 11:10:49 +00:00
|
|
|
#[test]
|
|
|
|
fn test_deserialize_request_with_null_variables() {
|
2020-10-13 02:19:30 +00:00
|
|
|
let request: Request = from_value(value! ({
|
2020-09-27 11:10:49 +00:00
|
|
|
"query": "{ a b c }",
|
|
|
|
"variables": null
|
|
|
|
}))
|
|
|
|
.unwrap();
|
|
|
|
assert!(request.operation_name.is_none());
|
|
|
|
assert!(request.variables.0.is_empty());
|
|
|
|
}
|
|
|
|
|
2020-09-17 08:39:55 +00:00
|
|
|
#[test]
|
|
|
|
fn test_batch_request_single() {
|
2020-10-13 02:19:30 +00:00
|
|
|
let request: BatchRequest = from_value(value! ({
|
2020-09-17 08:39:55 +00:00
|
|
|
"query": "{ a b c }"
|
|
|
|
}))
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
if let BatchRequest::Single(request) = request {
|
|
|
|
assert!(request.variables.0.is_empty());
|
|
|
|
assert!(request.operation_name.is_none());
|
|
|
|
assert_eq!(request.query, "{ a b c }");
|
|
|
|
} else {
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_batch_request_batch() {
|
2020-10-13 02:19:30 +00:00
|
|
|
let request: BatchRequest = from_value(value!([
|
2020-09-17 08:39:55 +00:00
|
|
|
{
|
|
|
|
"query": "{ a b c }"
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"query": "{ d e }"
|
|
|
|
}
|
|
|
|
]))
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
if let BatchRequest::Batch(requests) = request {
|
|
|
|
assert!(requests[0].variables.0.is_empty());
|
|
|
|
assert!(requests[0].operation_name.is_none());
|
|
|
|
assert_eq!(requests[0].query, "{ a b c }");
|
|
|
|
|
|
|
|
assert!(requests[1].variables.0.is_empty());
|
|
|
|
assert!(requests[1].operation_name.is_none());
|
|
|
|
assert_eq!(requests[1].query, "{ d e }");
|
|
|
|
} else {
|
|
|
|
unreachable!()
|
|
|
|
}
|
|
|
|
}
|
2020-09-12 16:07:46 +00:00
|
|
|
}
|