Repository: strawberry-graphql/strawberry

Issue:
Mutation query cannot utilize object arguments in codegen
<!-- Provide a general summary of the bug in the title above. -->

<!--- This template is entirely optional and can be removed, but is here to help both you and us. -->
<!--- Anything on lines wrapped in comments like these will not show up in the final text. -->

## Describe the Bug

The current codegen chokes on the following query with a `ValueError`:

```
mutation addBlogPosts($input: [BlogPostInput!]!) {
  addBlogPosts(input: {posts: $input}) {
    posts {
      title
    }
  }
}
```

## System Information

 - Operating system:
 - Strawberry version (if applicable):

## Additional Context

<!-- Add any other relevant information about the problem here. -->



Discussion:

Resolving patch:
diff --git a/strawberry/codegen/plugins/print_operation.py b/strawberry/codegen/plugins/print_operation.py
--- a/strawberry/codegen/plugins/print_operation.py
+++ b/strawberry/codegen/plugins/print_operation.py
@@ -16,6 +16,7 @@
     GraphQLList,
     GraphQLListValue,
     GraphQLObjectType,
+    GraphQLObjectValue,
     GraphQLOptional,
     GraphQLStringValue,
     GraphQLVariableReference,
@@ -124,6 +125,16 @@ def _print_argument_value(self, value: GraphQLArgumentValue) -> str:
         if isinstance(value, GraphQLBoolValue):
             return str(value.value).lower()
 
+        if isinstance(value, GraphQLObjectValue):
+            return (
+                "{"
+                + ", ".join(
+                    f"{name}: {self._print_argument_value(v)}"
+                    for name, v in value.values.items()
+                )
+                + "}"
+            )
+
         raise ValueError(f"not supported: {type(value)}")  # pragma: no cover
 
     def _print_arguments(self, arguments: List[GraphQLArgument]) -> str:
diff --git a/strawberry/codegen/query_codegen.py b/strawberry/codegen/query_codegen.py
--- a/strawberry/codegen/query_codegen.py
+++ b/strawberry/codegen/query_codegen.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 from dataclasses import dataclass
-from functools import partial
+from functools import cmp_to_key, partial
 from typing import (
     TYPE_CHECKING,
     Callable,
@@ -27,6 +27,7 @@
     ListValueNode,
     NamedTypeNode,
     NonNullTypeNode,
+    ObjectValueNode,
     OperationDefinitionNode,
     StringValueNode,
     VariableNode,
@@ -61,6 +62,7 @@
     GraphQLList,
     GraphQLListValue,
     GraphQLObjectType,
+    GraphQLObjectValue,
     GraphQLOperation,
     GraphQLOptional,
     GraphQLScalar,
@@ -126,15 +128,64 @@ def generate_code(
         return []
 
 
+def _get_deps(t: GraphQLType) -> Iterable[GraphQLType]:
+    """Get all the types that `t` depends on.
+
+    To keep things simple, `t` depends on itself.
+    """
+    yield t
+
+    if isinstance(t, GraphQLObjectType):
+        for fld in t.fields:
+            yield from _get_deps(fld.type)
+
+    elif isinstance(t, (GraphQLEnum, GraphQLScalar)):
+        # enums and scalars have no dependent types
+        pass
+
+    elif isinstance(t, (GraphQLOptional, GraphQLList)):
+        yield from _get_deps(t.of_type)
+
+    elif isinstance(t, GraphQLUnion):
+        for gql_type in t.types:
+            yield from _get_deps(gql_type)
+    else:
+        # Want to make sure that all types are covered.
+        raise ValueError(f"Unknown GraphQLType: {t}")
+
+
 class QueryCodegenPluginManager:
     def __init__(self, plugins: List[QueryCodegenPlugin]) -> None:
         self.plugins = plugins
 
+    def _sort_types(self, types: List[GraphQLType]) -> List[GraphQLType]:
+        """Sort the types.
+
+        t1 < t2 iff t2 has a dependency on t1.
+        t1 == t2 iff neither type has a dependency on the other.
+        """
+
+        def type_cmp(t1: GraphQLType, t2: GraphQLType) -> int:
+            """Compare the types."""
+            if t1 is t2:
+                return 0
+
+            if t1 in _get_deps(t2):
+                return -1
+            elif t2 in _get_deps(t1):
+                return 1
+            else:
+                return 0
+
+        return sorted(types, key=cmp_to_key(type_cmp))
+
     def generate_code(
         self, types: List[GraphQLType], operation: GraphQLOperation
     ) -> CodegenResult:
         result = CodegenResult(files=[])
 
+        types = self._sort_types(types)
+
         for plugin in self.plugins:
             files = plugin.generate_code(types, operation)
 
@@ -270,6 +321,14 @@ def _convert_value(self, value: ValueNode) -> GraphQLArgumentValue:
         if isinstance(value, BooleanValueNode):
             return GraphQLBoolValue(value.value)
 
+        if isinstance(value, ObjectValueNode):
+            return GraphQLObjectValue(
+                {
+                    field.name.value: self._convert_value(field.value)
+                    for field in value.fields
+                }
+            )
+
         raise ValueError(f"Unsupported type: {type(value)}")  # pragma: no cover
 
     def _convert_arguments(
@@ -460,7 +519,7 @@ def _field_from_selection(
         if selection.name.value == "__typename":
             return GraphQLField("__typename", None, GraphQLScalar("String", None))
         field = self.schema.get_field_for_type(selection.name.value, parent_type.name)
-        assert field
+        assert field, f"{parent_type.name},{selection.name.value}"
 
         field_type = self._get_field_type(field.type)
 
diff --git a/strawberry/codegen/types.py b/strawberry/codegen/types.py
--- a/strawberry/codegen/types.py
+++ b/strawberry/codegen/types.py
@@ -1,7 +1,7 @@
 from __future__ import annotations
 
 from dataclasses import dataclass, field
-from typing import TYPE_CHECKING, List, Optional, Type, Union
+from typing import TYPE_CHECKING, List, Mapping, Optional, Type, Union
 
 if TYPE_CHECKING:
     from enum import EnumMeta
@@ -126,6 +126,11 @@ class GraphQLListValue:
     values: List[GraphQLArgumentValue]
 
 
+@dataclass
+class GraphQLObjectValue:
+    values: Mapping[str, GraphQLArgumentValue]
+
+
 @dataclass
 class GraphQLVariableReference:
     value: str
@@ -138,6 +143,7 @@ class GraphQLVariableReference:
     GraphQLListValue,
     GraphQLEnumValue,
     GraphQLBoolValue,
+    GraphQLObjectValue,
 ]
 
 


Test patch:
diff --git a/tests/codegen/conftest.py b/tests/codegen/conftest.py
--- a/tests/codegen/conftest.py
+++ b/tests/codegen/conftest.py
@@ -110,12 +110,35 @@ def get_person_or_animal(self) -> Union[Person, Animal]:
         return p_or_a
 
 
+@strawberry.input
+class BlogPostInput:
+    title: str
+
+
+@strawberry.input
+class AddBlogPostsInput:
+    posts: List[BlogPostInput]
+
+
+@strawberry.type
+class AddBlogPostsOutput:
+    posts: List[BlogPost]
+
+
 @strawberry.type
 class Mutation:
     @strawberry.mutation
     def add_book(self, name: str) -> BlogPost:
         return BlogPost(id="c6f1c3ce-5249-4570-9182-c2836b836d14", name=name)
 
+    @strawberry.mutation
+    def add_blog_posts(self, input: AddBlogPostsInput) -> AddBlogPostsOutput:
+        output = AddBlogPostsOutput()
+        output.posts = []
+        for i, title in enumerate(input.posts):
+            output.posts.append(BlogPost(str(i), title))
+        return output
+
 
 @pytest.fixture
 def schema() -> strawberry.Schema:
diff --git a/tests/codegen/queries/mutation_with_object.graphql b/tests/codegen/queries/mutation_with_object.graphql
new file mode 100644
--- /dev/null
+++ b/tests/codegen/queries/mutation_with_object.graphql
@@ -0,0 +1,7 @@
+mutation AddBlogPosts($input: [BlogPostInput!]!) {
+  addBlogPosts(input: {posts: $input}) {
+    posts {
+      title
+    }
+  }
+}
diff --git a/tests/codegen/snapshots/python/mutation_with_object.py b/tests/codegen/snapshots/python/mutation_with_object.py
new file mode 100644
--- /dev/null
+++ b/tests/codegen/snapshots/python/mutation_with_object.py
@@ -0,0 +1,16 @@
+from typing import List
+
+class AddBlogPostsResultAddBlogPostsPosts:
+    title: str
+
+class AddBlogPostsResultAddBlogPosts:
+    posts: List[AddBlogPostsResultAddBlogPostsPosts]
+
+class AddBlogPostsResult:
+    add_blog_posts: AddBlogPostsResultAddBlogPosts
+
+class BlogPostInput:
+    title: str
+
+class AddBlogPostsVariables:
+    input: List[BlogPostInput]
diff --git a/tests/codegen/snapshots/python/variables.py b/tests/codegen/snapshots/python/variables.py
--- a/tests/codegen/snapshots/python/variables.py
+++ b/tests/codegen/snapshots/python/variables.py
@@ -3,13 +3,6 @@
 class OperationNameResult:
     with_inputs: bool
 
-class OperationNameVariables:
-    id: Optional[str]
-    input: ExampleInput
-    ids: List[str]
-    ids2: Optional[List[Optional[str]]]
-    ids3: Optional[List[Optional[List[Optional[str]]]]]
-
 class PersonInput:
     name: str
 
@@ -20,3 +13,10 @@ class ExampleInput:
     person: Optional[PersonInput]
     people: Optional[PersonInput]
     optional_people: Optional[Optional[PersonInput]]
+
+class OperationNameVariables:
+    id: Optional[str]
+    input: ExampleInput
+    ids: List[str]
+    ids2: Optional[List[Optional[str]]]
+    ids3: Optional[List[Optional[List[Optional[str]]]]]
diff --git a/tests/codegen/snapshots/typescript/mutation_with_object.ts b/tests/codegen/snapshots/typescript/mutation_with_object.ts
new file mode 100644
--- /dev/null
+++ b/tests/codegen/snapshots/typescript/mutation_with_object.ts
@@ -0,0 +1,19 @@
+type AddBlogPostsResultAddBlogPostsPosts = {
+    title: string
+}
+
+type AddBlogPostsResultAddBlogPosts = {
+    posts: AddBlogPostsResultAddBlogPostsPosts[]
+}
+
+type AddBlogPostsResult = {
+    add_blog_posts: AddBlogPostsResultAddBlogPosts
+}
+
+type BlogPostInput = {
+    title: string
+}
+
+type AddBlogPostsVariables = {
+    input: BlogPostInput[]
+}
diff --git a/tests/codegen/snapshots/typescript/variables.ts b/tests/codegen/snapshots/typescript/variables.ts
--- a/tests/codegen/snapshots/typescript/variables.ts
+++ b/tests/codegen/snapshots/typescript/variables.ts
@@ -2,14 +2,6 @@ type OperationNameResult = {
     with_inputs: boolean
 }
 
-type OperationNameVariables = {
-    id: string | undefined
-    input: ExampleInput
-    ids: string[]
-    ids2: (string | undefined)[] | undefined
-    ids3: ((string | undefined)[] | undefined)[] | undefined
-}
-
 type PersonInput = {
     name: string
 }
@@ -22,3 +14,11 @@ type ExampleInput = {
     people: PersonInput | undefined
     optional_people: PersonInput | undefined | undefined
 }
+
+type OperationNameVariables = {
+    id: string | undefined
+    input: ExampleInput
+    ids: string[]
+    ids2: (string | undefined)[] | undefined
+    ids3: ((string | undefined)[] | undefined)[] | undefined
+}