Repository: mirumee/ariadne

Issue:
Subscriptions "Complete Example" breaks under 0.10.0
I'm working with Subscriptions in ariadne using the exact code from the [Complete Example](https://ariadnegraphql.org/docs/subscriptions#complete-example) in the docs. When I load the playground and issue the query `subscription { counter }`, instead of a working counter, an "unsupported operand" error is raised:

```
Traceback (most recent call last):",
  File \"/Users/...REDACTED.../virtualenvs/graphql-Pfr5HTvn/lib/python3.7/site-packages/graphql/execution/execute.py\", line 625, in resolve_field_value_or_error",
    result = resolve_fn(source, info, **args)",
  File \"/Users/...REDACTED.../app/__init__.py\", line 26, in counter_resolver",
    return count + 1",
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'"
```

What's wild is that the example in the docs works with ariadne 0.8.0 and 0.9.0, but not 0.10.0. I'm not familiar enough with the internals, though, to spot the issue in the [0.9.0...0.10.0 diff](https://github.com/mirumee/ariadne/compare/0.9.0...0.10.0).

(For completeness, I'm using Python 3.7.6, ariadne 0.10.0, under uvicorn 0.11.2 and gunicorn 20.0.4.)


Discussion:
Looks like #288 borked this. We'll have to implement more proper approach for finding if executed operation will be query or subscription.

In a meantime workaround for the issue would be to specify operation's name:

```
subscription MySubscription {
    counter
}
```
> Looks like #288 borked this. We'll have to implement more proper approach for finding if executed operation will be query or subscription.
> 
> In a meantime workaround for the issue would be to specify operation's name:
> 
> ```
> subscription MySubscription {
>     counter
> }
> ```

just curious why does this work? 
New logic introduced in #288 checks for operation name. `subscription {}` has no operation name, but `subscription Something {}` does.
Hi all -- I'm still having issues using the example, even with the workaround from above.

The code that I've saved to `app.py` is

```python
import asyncio
from ariadne import SubscriptionType, make_executable_schema
from ariadne.asgi import GraphQL

type_def = """
    type Query {
        _unused: Boolean
    }

    type Subscription {
        counter: Int!
    }
"""

subscription = SubscriptionType()

@subscription.source("counter")
async def counter_generator(obj, info):
    for i in range(5):
        await asyncio.sleep(1)
        yield i


@subscription.field("counter")
def counter_resolver(count, info):
    return count + 1


schema = make_executable_schema(type_def, subscription)
app = GraphQL(schema, debug=True)
```

To run the app, I'm using `uvicorn app:app`

---

Issuing the following command to the GraphQL endpoint...

```
subscription Something {
    counter
}
```

...still returns

```json
{
  "data": null,
  "errors": [
    {
      "message": "unsupported operand type(s) for +: 'NoneType' and 'int'",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ],
      "path": [
        "counter"
      ],
      "extensions": {
        "exception": {
          "stacktrace": [
            "Traceback (most recent call last):",
            "  File \"/usr/local/lib/python3.8/site-packages/graphql/execution/execute.py\", line 625, in resolve_field_value_or_error",
            "    result = resolve_fn(source, info, **args)",
            "  File \"./app.py\", line 26, in counter_resolver",
            "    return count + 1",
            "TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'"
          ],
          "context": {
            "count": "None",
            "info": "GraphQLResolv...f3569f90850>})"
          }
        }
      }
    }
  ]
}
```

If I change line 26 to `5` and re-issue the command, I get:

```json
{
  "data": {
    "counter": 5
  }
}
```

---

After the code change, removing the bogus subscription name works.

```
subscription {
    counter
}
```

```json
{
  "data": {
    "counter": 5
  }
}
```

---

Version info:

```
$ docker images python:latest
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
python              latest              f88b2f81f83a        3 weeks ago         933MB
```

```
root@afb1e285eff1:/app# python --version
Python 3.8.2
```

```
root@afb1e285eff1:/app# pip show ariadne
Name: ariadne
Version: 0.10.0
Summary: Ariadne is a Python library for implementing GraphQL servers.
Home-page: https://github.com/mirumee/ariadne
Author: Mirumee Software
Author-email: hello@mirumee.com
License: BSD
Location: /usr/local/lib/python3.8/site-packages
Requires: graphql-core, starlette, typing-extensions
Required-by: 
```

```
root@afb1e285eff1:/app# pip show uvicorn
Name: uvicorn
Version: 0.11.3
Summary: The lightning-fast ASGI server.
Home-page: https://github.com/encode/uvicorn
Author: Tom Christie
Author-email: tom@tomchristie.com
License: BSD
Location: /usr/local/lib/python3.8/site-packages
Requires: uvloop, websockets, click, h11, httptools
Required-by: 
```

Resolving patch:
diff --git a/ariadne/asgi.py b/ariadne/asgi.py
--- a/ariadne/asgi.py
+++ b/ariadne/asgi.py
@@ -25,9 +25,9 @@
 from .exceptions import HttpBadRequestError, HttpError
 from .file_uploads import combine_multipart_data
 from .format_error import format_error
-from .graphql import graphql, subscribe, validate_data
+from .graphql import graphql, subscribe
 from .logger import log_error
-from .types import ContextValue, ErrorFormatter, Extension, GraphQLResult, RootValue
+from .types import ContextValue, ErrorFormatter, Extension, RootValue
 
 GQL_CONNECTION_INIT = "connection_init"  # Client -> Server
 GQL_CONNECTION_ACK = "connection_ack"  # Server -> Client
@@ -142,8 +142,21 @@ async def graphql_http_server(self, request: Request) -> Response:
         except HttpError as error:
             return PlainTextResponse(error.message or error.status, status_code=400)
 
-        success, response = await self.execute_graphql_query(request, data)
+        context_value = await self.get_context_for_request(request)
+        extensions = await self.get_extensions_for_request(request, context_value)
+        middleware = await self.get_middleware_for_request(request, context_value)
 
+        success, response = await graphql(
+            self.schema,
+            data,
+            context_value=context_value,
+            root_value=self.root_value,
+            debug=self.debug,
+            logger=self.logger,
+            error_formatter=self.error_formatter,
+            extensions=extensions,
+            middleware=middleware,
+        )
         status_code = 200 if success else 400
         return JSONResponse(response, status_code=status_code)
 
@@ -226,7 +239,9 @@ async def handle_websocket_message(
         elif message_type == GQL_CONNECTION_TERMINATE:
             await websocket.close()
         elif message_type == GQL_START:
-            await self.process_single_message(websocket, subscriptions, message)
+            await self.start_websocket_subscription(
+                message.get("payload"), operation_id, websocket, subscriptions
+            )
         elif message_type == GQL_STOP:
             if operation_id in subscriptions:
                 await subscriptions[operation_id].aclose()
@@ -242,44 +257,6 @@ async def keep_websocket_alive(self, websocket: WebSocket):
                 return
             await asyncio.sleep(self.keepalive)
 
-    async def process_single_message(
-        self,
-        websocket: WebSocket,
-        subscriptions: Dict[str, AsyncGenerator],
-        message: dict,
-    ) -> None:
-        operation_id = cast(str, message.get("id"))
-
-        payload = message.get("payload")
-        validate_data(payload)
-
-        if payload and "operationName" in payload and payload["operationName"] is None:
-            _, result = await self.execute_graphql_query(websocket, payload)
-            await websocket.send_json(
-                {"type": GQL_DATA, "id": operation_id, "payload": result}
-            )
-        else:
-            await self.start_websocket_subscription(
-                payload, operation_id, websocket, subscriptions
-            )
-
-    async def execute_graphql_query(self, request: Any, data: Any) -> GraphQLResult:
-        context_value = await self.get_context_for_request(request)
-        extensions = await self.get_extensions_for_request(request, context_value)
-        middleware = await self.get_middleware_for_request(request, context_value)
-
-        return await graphql(
-            self.schema,
-            data,
-            context_value=context_value,
-            root_value=self.root_value,
-            debug=self.debug,
-            logger=self.logger,
-            error_formatter=self.error_formatter,
-            extensions=extensions,
-            middleware=middleware,
-        )
-
     async def start_websocket_subscription(
         self,
         data: Any,


Test patch:
diff --git a/tests/asgi/test_websockets.py b/tests/asgi/test_websockets.py
--- a/tests/asgi/test_websockets.py
+++ b/tests/asgi/test_websockets.py
@@ -9,7 +9,7 @@
 )
 
 
-def test_ping_can_be_subscribed_using_websocket_connection(client):
+def test_field_can_be_subscribed_using_websocket_connection(client):
     with client.websocket_connect("/", "graphql-ws") as ws:
         ws.send_json({"type": GQL_CONNECTION_INIT})
         ws.send_json(
@@ -32,22 +32,54 @@ def test_ping_can_be_subscribed_using_websocket_connection(client):
         ws.send_json({"type": GQL_CONNECTION_TERMINATE})
 
 
-def test_query_can_be_executed_using_websocket_connection(client):
+def test_field_can_be_subscribed_using_unnamed_operation_in_websocket_connection(
+    client,
+):
     with client.websocket_connect("/", "graphql-ws") as ws:
         ws.send_json({"type": GQL_CONNECTION_INIT})
+        ws.send_json(
+            {
+                "type": GQL_START,
+                "id": "test1",
+                "payload": {"operationName": None, "query": "subscription { ping }",},
+            }
+        )
         response = ws.receive_json()
         assert response["type"] == GQL_CONNECTION_ACK
+        response = ws.receive_json()
+        assert response["type"] == GQL_DATA
+        assert response["id"] == "test1"
+        assert response["payload"]["data"] == {"ping": "pong"}
+        ws.send_json({"type": GQL_STOP, "id": "test1"})
+        response = ws.receive_json()
+        assert response["type"] == GQL_COMPLETE
+        assert response["id"] == "test1"
+        ws.send_json({"type": GQL_CONNECTION_TERMINATE})
+
+
+def test_field_can_be_subscribed_using_named_operation_in_websocket_connection(client):
+    with client.websocket_connect("/", "graphql-ws") as ws:
+        ws.send_json({"type": GQL_CONNECTION_INIT})
         ws.send_json(
             {
                 "type": GQL_START,
-                "id": "test2",
-                "payload": {"operationName": None, "query": "{ testRoot }",},
+                "id": "test1",
+                "payload": {
+                    "operationName": "PingTest",
+                    "query": "subscription PingTest { ping }",
+                },
             }
         )
         response = ws.receive_json()
+        assert response["type"] == GQL_CONNECTION_ACK
+        response = ws.receive_json()
         assert response["type"] == GQL_DATA
-        assert response["id"] == "test2"
-        assert response["payload"]["data"] == {"testRoot": None}
+        assert response["id"] == "test1"
+        assert response["payload"]["data"] == {"ping": "pong"}
+        ws.send_json({"type": GQL_STOP, "id": "test1"})
+        response = ws.receive_json()
+        assert response["type"] == GQL_COMPLETE
+        assert response["id"] == "test1"
         ws.send_json({"type": GQL_CONNECTION_TERMINATE})