async-graphql/src/request.rs

223 lines
6.1 KiB
Rust
Raw Normal View History

2020-09-10 08:39:43 +00:00
use crate::parser::types::UploadValue;
2020-09-10 11:35:48 +00:00
use crate::{Data, Value, Variables};
2020-09-17 08:39:55 +00:00
use serde::{Deserialize, Deserializer};
2020-03-31 03:19:18 +00:00
use std::any::Any;
use std::fs::File;
2020-03-17 09:26:59 +00:00
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`).
#[derive(Debug, Deserialize)]
#[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.
pub query: String,
/// The operation name of the request.
#[serde(default, rename = "operationName")]
pub operation_name: Option<String>,
/// The variables of the request.
#[serde(default)]
pub variables: Variables,
/// 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-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-09-12 16:07:46 +00:00
data: Data::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-03-17 09:26:59 +00:00
pub fn set_upload(
&mut self,
var_path: &str,
filename: String,
content_type: Option<String>,
content: File,
2020-03-17 09:26:59 +00:00
) {
2020-09-06 05:38:31 +00:00
let variable = match self.variables.variable_path(var_path) {
Some(variable) => variable,
None => return,
};
*variable = Value::Upload(UploadValue {
filename,
content_type,
content,
});
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-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/>
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub enum BatchRequest {
/// Single query
Single(Request),
/// Non-empty array of queries
#[serde(deserialize_with = "deserialize_non_empty_vec")]
Batch(Vec<Request>),
}
fn deserialize_non_empty_vec<'de, D, T>(deserializer: D) -> std::result::Result<Vec<T>, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de>,
{
use serde::de::Error as _;
let v = Vec::<T>::deserialize(deserializer)?;
if v.is_empty() {
Err(D::Error::invalid_length(0, &"a positive integer"))
} 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 {
use super::*;
use serde_json::json;
#[test]
fn test_request() {
let request: Request = serde_json::from_value(json! ({
"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() {
let request: Request = serde_json::from_value(json! ({
"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() {
let request: Request = serde_json::from_value(json! ({
"query": "{ a b c }",
"variables": {
"v1": 100,
"v2": [1, 2, 3],
"v3": "str",
}
}))
.unwrap();
assert_eq!(
request.variables.into_value().into_json().unwrap(),
json!({
"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
#[test]
fn test_batch_request_single() {
let request: BatchRequest = serde_json::from_value(json! ({
"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() {
let request: BatchRequest = serde_json::from_value(json!([
{
"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
}