Repository: edgedb/edgedb

Issue:
graphql: unknown type error
Here's the error we report when a user queries an unknown type:

```
{
  "errors": [
    {
      "message": "QueryError: Cannot query field \"Movie\" on type \"Query\".",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ]
    }
  ]
}
```

Instead of this rather obscure message we should simply say something like this: `Unknown type "Movie"`


Discussion:
This error message is generated by the `graphql` module. Of course, we can intercept it and change it, but there are a few things to consider:
1) This error message is identical in structure if you try to access an unknown nested field, except, of course, the type will be something other than "Query". If the name is slightly misspelled the error message will even offer a hint of what the correct spelling could be.
2) Strictly speaking this is perfectly reasonable GraphQL error. Remember that all top-level queries are *fields* of the type `Query` (and once we allow mutations or subscriptions then it may also be a field of type `Mutation` or `Subscription`). That's just the canonical GraphQL structure. Since we know and control all our types we can at least distinguish the particular case when the field is on a top-level Query.
3) Saying that it is an "Unknown type" really means "Unknown mapped EdgeDB type". The wording is a little awkward since technically it can also be a view, not just a type. So the message then would be more like "Unknown mapped EdgeDB type or view".
4) In the current GraphQL implementation we only have one field per type/view matching the name of the EdgeDB concept exactly. However, it is a very popular pattern in other GraphQL implementations to have one field "getFoo" and another "getAllFoo" per each reflected type "Foo". This roughly corresponds to `fetchone` and `fetchall` so it's not unreasonable that we may decide to also have something similar. This means that we probably should keep the original error message as it refers to the specific GraphQL field.

From the above, it appears that the updated error message would be something like:
` "QueryError: Cannot query field \"Movie\" on type \"Query\". There's no corresponding type or view \"Movie\" exposed in EdgeDB."`

With possible future variations of:
` "QueryError: Cannot query field \"fetchone_Movie\" on type \"Query\". There's no corresponding type or view \"Movie\" exposed in EdgeDB."`

It's a good idea to suggest checking the GraphQL port configuration to make sure which database this port is actually connected to. The most likely cause of not having the expected top-level fields is that the user is connecting to the wrong port or exposed the wrong database.

Resolving patch:
diff --git a/edb/graphql/translator.py b/edb/graphql/translator.py
--- a/edb/graphql/translator.py
+++ b/edb/graphql/translator.py
@@ -18,6 +18,7 @@
 
 
 import json
+import re
 import typing
 
 import graphql
@@ -1080,8 +1081,13 @@ def translate(gqlcore: gt.GQLCoreSchema, query, *,
     if validation_errors:
         err = validation_errors[0]
         if isinstance(err, graphql.GraphQLError):
+
+            # possibly add additional information and/or hints to the
+            # error message
+            msg = augment_error_message(gqlcore, err.message)
+
             err_loc = (err.locations[0].line, err.locations[0].column)
-            raise g_errors.GraphQLCoreError(err.message, loc=err_loc)
+            raise g_errors.GraphQLCoreError(msg, loc=err_loc)
         else:
             raise err
 
@@ -1117,3 +1123,21 @@ def translate(gqlcore: gt.GQLCoreSchema, query, *,
         cache_deps_vars=dict(critvars) if critvars else None,
         variables_desc=defvars,
     )
+
+
+def augment_error_message(gqlcore: gt.GQLCoreSchema, message: str):
+    # If the error is about wrong Query field, we can add more details
+    # about what seems to have gone wrong. The type is missing,
+    # possibly because this connection is to the wrong DB. However,
+    # this is only relevant if the message doesn't contain a hint already.
+    if (re.match(r'^Cannot query field "(.+?)" on type "Query"\.$', message)):
+        field = message.split('"', 2)[1]
+        name = gqlcore.gql_to_edb_name(field)
+
+        message += (
+            f' There\'s no corresponding type or view "{name}" exposed in '
+            'EdgeDB. Please check the configuration settings for this port '
+            'to make sure that you\'re connecting to the right database.'
+        )
+
+    return message
diff --git a/edb/graphql/types.py b/edb/graphql/types.py
--- a/edb/graphql/types.py
+++ b/edb/graphql/types.py
@@ -166,6 +166,14 @@ def get_input_name(self, inputtype, name):
         else:
             return f'{inputtype}{name}'
 
+    def gql_to_edb_name(self, name):
+        '''Convert the GraphQL field name into an EdgeDB type/view name.'''
+
+        if '__' in name:
+            return name.replace('__', '::', 1)
+        else:
+            return name
+
     def _convert_edb_type(self, edb_target):
         target = None
 


Test patch:
diff --git a/tests/test_http_graphql.py b/tests/test_http_graphql.py
--- a/tests/test_http_graphql.py
+++ b/tests/test_http_graphql.py
@@ -420,6 +420,51 @@ def test_graphql_functional_query_12(self):
             operation_name='users'
         )
 
+    def test_graphql_functional_query_13(self):
+        # Test special case errors.
+        with self.assertRaisesRegex(
+                edgedb.QueryError,
+                r"Cannot query field \"gibberish\" on type \"Query\"\. "
+                r"There's no corresponding type or view \"gibberish\" "
+                r"exposed in EdgeDB\. Please check the configuration settings "
+                r"for this port to make sure that you're connecting to the "
+                r"right database\.",
+                _line=3, _col=21):
+            self.graphql_query(r"""
+                query {
+                    gibberish
+                }
+            """)
+
+    def test_graphql_functional_query_14(self):
+        # Test special case errors.
+        with self.assertRaisesRegex(
+                edgedb.QueryError,
+                r"Cannot query field \"more__gibberish\" on type \"Query\"\. "
+                r"There's no corresponding type or view \"more::gibberish\" "
+                r"exposed in EdgeDB\. Please check the configuration settings "
+                r"for this port to make sure that you're connecting to the "
+                r"right database\.",
+                _line=3, _col=21):
+            self.graphql_query(r"""
+                query {
+                    more__gibberish
+                }
+            """)
+
+    def test_graphql_functional_query_15(self):
+        # Test special case errors.
+        with self.assertRaisesRegex(
+                edgedb.QueryError,
+                r"Cannot query field \"Uxer\" on type \"Query\"\. "
+                r"Did you mean \"User\"\?",
+                _line=3, _col=21):
+            self.graphql_query(r"""
+                query {
+                    Uxer
+                }
+            """)
+
     def test_graphql_functional_view_01(self):
         self.assert_graphql_query_result(
             r"""