Repository: graphql-rust/juniper (language: rust)

Issue:
Disallow empty GraphQLBatchRequests
**Is your feature request related to a problem? Please describe.**  
At the moment, it's possible to deserialize `GraphQLBatchRequest` as an empty `Vec<GraphQLRequest>`. The downstream implementations (like `juniper_warp`) rely transparently on that, executing empty batch and returning empty response.

I'm unsure this is OK. Maybe, it's better to reject empty batches at deserialization level?

**Describe the solution you'd like**  
The one was implemented once in early `juniper_actix` iterations:
```rust
enum GraphQLBatchRequest<S = DefaultScalarValue>
where
    S: ScalarValue,
{
    Single(GraphQLRequest<S>),
    #[serde(deserialize_with = "deserialize_non_empty_vec")]
    Batch(Vec<GraphQLRequest<S>>),
}

fn deserialize_non_empty_vec<'de, D, T>(
    deserializer: D,
) -> Result<Vec<T>, D::Error>
where
    D: de::Deserializer<'de>,
    T: Deserialize<'de>,
{
    let v = Vec::<T>::deserialize(deserializer)?;
    if v.is_empty() {
        Err(de::Error::invalid_length(0, &"a positive integer"))
    } else {
        Ok(v)
    }
}
```

**Describe alternatives you've considered**  
Leave it "as is". But seems to be more error-prone for frontends.


-------------------

If it something desirable, I'll submit the PR shortly.



Discussion / hints:
Yeah, I think we should reject empty batch requests as it almost certainly points to a bug on the client side.

Fix patch:
diff --git a/juniper/src/http/mod.rs b/juniper/src/http/mod.rs
index c448da2dc..79e3d8d88 100644
--- a/juniper/src/http/mod.rs
+++ b/juniper/src/http/mod.rs
@@ -4,6 +4,7 @@ pub mod graphiql;
 pub mod playground;
 
 use serde::{
+    de,
     ser::{self, SerializeMap},
     Deserialize, Serialize,
 };
@@ -216,7 +217,7 @@ where
     }
 }
 
-/// Simple wrapper around GraphQLRequest to allow the handling of Batch requests
+/// Simple wrapper around GraphQLRequest to allow the handling of Batch requests.
 #[derive(Debug, Deserialize, PartialEq)]
 #[serde(untagged)]
 #[serde(bound = "InputValue<S>: Deserialize<'de>")]
@@ -226,10 +227,29 @@ where
 {
     /// A single operation request.
     Single(GraphQLRequest<S>),
+
     /// A batch operation request.
+    ///
+    /// Empty batch is considered as invalid value, so cannot be deserialized.
+    #[serde(deserialize_with = "deserialize_non_empty_vec")]
     Batch(Vec<GraphQLRequest<S>>),
 }
 
+fn deserialize_non_empty_vec<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
+where
+    D: de::Deserializer<'de>,
+    T: Deserialize<'de>,
+{
+    use 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<S> GraphQLBatchRequest<S>
 where
     S: ScalarValue,
@@ -373,6 +393,9 @@ pub mod tests {
         println!("  - test_batched_post");
         test_batched_post(integration);
 
+        println!("  - test_empty_batched_post");
+        test_empty_batched_post(integration);
+
         println!("  - test_invalid_json");
         test_invalid_json(integration);
 
@@ -499,6 +522,11 @@ pub mod tests {
         );
     }
 
+    fn test_empty_batched_post<T: HTTPIntegration>(integration: &T) {
+        let response = integration.post("/", "[]");
+        assert_eq!(response.status_code, 400);
+    }
+
     fn test_invalid_json<T: HTTPIntegration>(integration: &T) {
         let response = integration.get("/?query=blah");
         assert_eq!(response.status_code, 400);


Test patch: