Repository: edgedb/edgedb

Issue:
Enums aren't accepted as input variables in GraphQL
This is in response to https://github.com/edgedb/edgedb/discussions/2413

The issue is that although enums should behave like scalars, they aren't accepted as inputs. So a query like:

```graphql
mutation updateItem($category: Category) {
  update_Item(data: {category: {set: $category}}) {
    category
  }
}
```
results in an error like:
`QueryError: Only scalar input variables are allowed. Variable 'category' has non-scalar value.`


Discussion:
Thank you for opening this issue

Resolving patch:
diff --git a/edb/graphql/translator.py b/edb/graphql/translator.py
--- a/edb/graphql/translator.py
+++ b/edb/graphql/translator.py
@@ -1515,6 +1515,8 @@ def _visit_order_item(self, node):
     def visit_VariableNode(self, node):
         varname = node.name.value
         var = self._context.vars[varname]
+        err_msg = (f"Only scalar input variables are allowed. "
+                   f"Variable {varname!r} has non-scalar value.")
 
         vartype = var.defn.type
         optional = True
@@ -1522,15 +1524,25 @@ def visit_VariableNode(self, node):
             vartype = vartype.type
             optional = False
 
-        if vartype.name.value not in gt.GQL_TO_EDB_SCALARS_MAP:
-            raise errors.QueryError(
-                f"Only scalar input variables are allowed. "
-                f"Variable {varname!r} has non-scalar value.")
+        if self.is_list_type(vartype):
+            raise errors.QueryError(err_msg)
 
-        casttype = qlast.TypeName(
-            maintype=qlast.ObjectRef(
+        if vartype.name.value in gt.GQL_TO_EDB_SCALARS_MAP:
+            castname = qlast.ObjectRef(
                 name=gt.GQL_TO_EDB_SCALARS_MAP[vartype.name.value])
-        )
+        else:
+            try:
+                vtype = self.get_type(
+                    self._context.gqlcore.gql_to_edb_name(vartype.name.value))
+            except AssertionError:
+                raise errors.QueryError(err_msg)
+
+            if vtype.is_enum:
+                castname = vtype.edb_base_name_ast
+            else:
+                raise errors.QueryError(err_msg)
+
+        casttype = qlast.TypeName(maintype=castname)
         # potentially this is an array
         if self.is_list_type(vartype):
             casttype = qlast.TypeName(
@@ -1789,7 +1801,9 @@ def convert_default(
     node: gql_ast.ValueNode,
     varname: str
 ) -> Union[str, float, int, bool]:
-    if isinstance(node, (gql_ast.StringValueNode, gql_ast.BooleanValueNode)):
+    if isinstance(node, (gql_ast.StringValueNode,
+                         gql_ast.BooleanValueNode,
+                         gql_ast.EnumValueNode)):
         return node.value
     elif isinstance(node, gql_ast.IntValueNode):
         return int(node.value)


Test patch:
diff --git a/tests/test_http_graphql_mutation.py b/tests/test_http_graphql_mutation.py
--- a/tests/test_http_graphql_mutation.py
+++ b/tests/test_http_graphql_mutation.py
@@ -476,6 +476,63 @@ def test_graphql_mutation_insert_enum_01(self):
             "other__Foo": []
         })
 
+    def test_graphql_mutation_insert_enum_02(self):
+        # This tests enum values in insertion, using variables.
+        data = {
+            'select': 'New EnumTest02',
+            'color': 'RED',
+        }
+
+        validation_query = r"""
+            query {
+                other__Foo(filter: {select: {eq: "New EnumTest02"}}) {
+                    select
+                    color
+                }
+            }
+        """
+
+        self.assert_graphql_query_result(r"""
+            mutation insert_other__Foo(
+                $select: String!,
+                $color: other__ColorEnum!
+            ) {
+                insert_other__Foo(
+                    data: [{
+                        select: $select,
+                        color: $color,
+                    }]
+                ) {
+                    select
+                    color
+                }
+            }
+        """, {
+            "insert_other__Foo": [data]
+        }, variables=data)
+
+        self.assert_graphql_query_result(validation_query, {
+            "other__Foo": [data]
+        })
+
+        self.assert_graphql_query_result(r"""
+            mutation delete_other__Foo {
+                delete_other__Foo(
+                    filter: {select: {eq: "New EnumTest02"}}
+                ) {
+                    select
+                    color
+                }
+            }
+        """, {
+            "delete_other__Foo": [data]
+        })
+
+        # validate that the deletion worked
+        self.assert_graphql_query_result(validation_query, {
+            "other__Foo": []
+        })
+
     def test_graphql_mutation_insert_nested_01(self):
         # Test nested insert.
         data = {
@@ -2131,6 +2188,66 @@ def test_graphql_mutation_update_enum_01(self):
             }]
         })
 
+    def test_graphql_mutation_update_enum_02(self):
+        # This tests enum values in updates using variables.
+
+        self.assert_graphql_query_result(r"""
+            mutation insert_other__Foo {
+                insert_other__Foo(
+                    data: [{
+                        select: "Update EnumTest02",
+                        color: BLUE
+                    }]
+                ) {
+                    select
+                    color
+                }
+            }
+        """, {
+            "insert_other__Foo": [{
+                'select': 'Update EnumTest02',
+                'color': 'BLUE',
+            }]
+        })
+
+        self.assert_graphql_query_result(r"""
+            mutation update_other__Foo (
+                $color: other__ColorEnum!
+            ) {
+                update_other__Foo(
+                    filter: {select: {eq: "Update EnumTest02"}}
+                    data: {
+                        color: {set: $color}
+                    }
+                ) {
+                    select
+                    color
+                }
+            }
+        """, {
+            "update_other__Foo": [{
+                'select': 'Update EnumTest02',
+                'color': 'RED',
+            }]
+        }, variables={"color": "RED"})
+
+        # clean up
+        self.assert_graphql_query_result(r"""
+            mutation delete_other__Foo {
+                delete_other__Foo(
+                    filter: {select: {eq: "Update EnumTest02"}}
+                ) {
+                    select
+                    color
+                }
+            }
+        """, {
+            "delete_other__Foo": [{
+                'select': 'Update EnumTest02',
+                'color': 'RED',
+            }]
+        })
+
     def test_graphql_mutation_update_link_01(self):
         orig_data = {
             'name': 'John',
diff --git a/tests/test_http_graphql_query.py b/tests/test_http_graphql_query.py
--- a/tests/test_http_graphql_query.py
+++ b/tests/test_http_graphql_query.py
@@ -1421,6 +1421,21 @@ def test_graphql_functional_enums_03(self):
             }]
         })
 
+    def test_graphql_functional_enums_04(self):
+        with self.assertRaisesRegex(
+                edgedb.QueryError,
+                r'String cannot represent a non string value: admin',
+                _line=4, _col=51):
+            self.graphql_query(r"""
+                query {
+                    # enum supplied instead of a string
+                    UserGroup(filter: {name: {eq: admin}}) {
+                        id,
+                        name,
+                    }
+                }
+            """)
+
     def test_graphql_functional_fragment_01(self):
         self.assert_graphql_query_result(r"""
             fragment groupFrag on UserGroup {
@@ -3015,20 +3030,44 @@ def test_graphql_functional_variables_43(self):
                 }
             """, variables={"f": {"name": {"eq": "Alice"}}})
 
-    def test_graphql_functional_enum_01(self):
-        with self.assertRaisesRegex(
-                edgedb.QueryError,
-                r'String cannot represent a non string value: admin',
-                _line=4, _col=51):
-            self.graphql_query(r"""
-                query {
-                    # enum supplied instead of a string
-                    UserGroup(filter: {name: {eq: admin}}) {
-                        id,
-                        name,
+    def test_graphql_functional_variables_44(self):
+        self.assert_graphql_query_result(
+            r"""
+                query foo($color: other__ColorEnum!) {
+                    other__Foo(
+                        filter: {color: {eq: $color}},
+                    ) {
+                        select
+                        color
                     }
                 }
-            """)
+            """, {
+                "other__Foo": [{
+                    "select": "a",
+                    "color": "RED",
+                }]
+            },
+            variables={"color": "RED"},
+        )
+
+    def test_graphql_functional_variables_45(self):
+        self.assert_graphql_query_result(
+            r"""
+                query foo($color: other__ColorEnum! = GREEN) {
+                    other__Foo(
+                        filter: {color: {eq: $color}},
+                    ) {
+                        select
+                        color
+                    }
+                }
+            """, {
+                "other__Foo": [{
+                    "select": "b",
+                    "color": "GREEN",
+                }]
+            },
+        )
 
     def test_graphql_functional_inheritance_01(self):
         # ISSUE: #709