We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Query cost validation is skipping `InlineFragmentNode`
I've got tipped by @przlada that our query cost validator skips `InlineFragmentNode` when calculating the costs.

`InlineFragmentNode` is a fragment used when querying interfaces and unions:

```graphql
{
  search(query: "lorem ipsum") {
    ... on User {
      id
      username
    }
    ... on Comment {
      id
      content
    }
  }
}
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ariadne/validation/query_cost.py`
Content:
```
1 from functools import reduce
2 from operator import add, mul
3 from typing import Any, Dict, List, Optional, Type, Union, cast
4 
5 from graphql import (
6     GraphQLError,
7     GraphQLInterfaceType,
8     GraphQLObjectType,
9     GraphQLSchema,
10     get_named_type,
11 )
12 from graphql.execution.values import get_argument_values
13 from graphql.language import (
14     BooleanValueNode,
15     FieldNode,
16     FragmentDefinitionNode,
17     FragmentSpreadNode,
18     InlineFragmentNode,
19     IntValueNode,
20     ListValueNode,
21     Node,
22     OperationDefinitionNode,
23     OperationType,
24     StringValueNode,
25 )
26 from graphql.type import GraphQLFieldMap
27 from graphql.validation import ValidationContext
28 from graphql.validation.rules import ASTValidationRule, ValidationRule
29 
30 cost_directive = """
31 directive @cost(complexity: Int, multipliers: [String!], useMultipliers: Boolean) on FIELD | FIELD_DEFINITION
32 """
33 
34 
35 CostAwareNode = Union[
36     FieldNode,
37     FragmentDefinitionNode,
38     FragmentSpreadNode,
39     InlineFragmentNode,
40     OperationDefinitionNode,
41 ]
42 
43 
44 class CostValidator(ValidationRule):
45     context: ValidationContext
46     maximum_cost: int
47     default_cost: int = 0
48     default_complexity: int = 1
49     variables: Optional[Dict] = None
50     cost_map: Optional[Dict[str, Dict[str, Any]]] = None
51 
52     def __init__(
53         self,
54         context: ValidationContext,
55         maximum_cost: int,
56         *,
57         default_cost: int = 0,
58         default_complexity: int = 1,
59         variables: Optional[Dict] = None,
60         cost_map: Optional[Dict[str, Dict[str, Any]]] = None,
61     ) -> None:
62         super().__init__(context)
63 
64         self.maximum_cost = maximum_cost
65         self.variables = variables
66         self.cost_map = cost_map
67         self.default_cost = default_cost
68         self.default_complexity = default_complexity
69         self.cost = 0
70         self.operation_multipliers: List[Any] = []
71 
72     def compute_node_cost(self, node: CostAwareNode, type_def, parent_multipliers=None):
73         if parent_multipliers is None:
74             parent_multipliers = []
75         if isinstance(node, FragmentSpreadNode) or not node.selection_set:
76             return 0
77         fields: GraphQLFieldMap = {}
78         if isinstance(type_def, (GraphQLObjectType, GraphQLInterfaceType)):
79             fields = type_def.fields
80         total = 0
81         for child_node in node.selection_set.selections:
82             self.operation_multipliers = parent_multipliers[:]
83             node_cost = self.default_cost
84             if isinstance(child_node, FieldNode):
85                 field = fields.get(child_node.name.value)
86                 if not field:
87                     continue
88                 field_type = get_named_type(field.type)
89                 try:
90                     field_args: Dict[str, Any] = get_argument_values(
91                         field, child_node, self.variables
92                     )
93                 except Exception as e:
94                     report_error(self.context, e)
95                     field_args = {}
96                 if self.cost_map:
97                     cost_map_args = (
98                         self.get_args_from_cost_map(
99                             child_node, type_def.name, field_args
100                         )
101                         if type_def and type_def.name
102                         else None
103                     )
104                     if cost_map_args is not None:
105                         try:
106                             node_cost = self.compute_cost(**cost_map_args)
107                         except (TypeError, ValueError) as e:
108                             report_error(self.context, e)
109                 else:
110                     cost_is_computed = False
111                     if field.ast_node and field.ast_node.directives:
112                         directives_args = self.get_args_from_directives(
113                             field.ast_node.directives, field_args
114                         )
115                         if directives_args is not None:
116                             try:
117                                 node_cost = self.compute_cost(**directives_args)
118                             except (TypeError, ValueError) as e:
119                                 report_error(self.context, e)
120                             cost_is_computed = True
121                     if (
122                         field_type
123                         and field_type.ast_node
124                         and field_type.ast_node.directives
125                     ):
126                         if not cost_is_computed and isinstance(
127                             field_type, GraphQLObjectType
128                         ):
129                             directives_args = self.get_args_from_directives(
130                                 field_type.ast_node.directives, field_args
131                             )
132                             if directives_args is not None:
133                                 try:
134                                     node_cost = self.compute_cost(**directives_args)
135                                 except (TypeError, ValueError) as e:
136                                     report_error(self.context, e)
137                 child_cost = self.compute_node_cost(
138                     child_node, field_type, self.operation_multipliers
139                 )
140                 node_cost += child_cost
141             if isinstance(child_node, FragmentSpreadNode):
142                 fragment = self.context.get_fragment(child_node.name.value)
143                 if fragment:
144                     fragment_type = self.context.schema.get_type(
145                         fragment.type_condition.name.value
146                     )
147                     node_cost = self.compute_node_cost(fragment, fragment_type)
148             if isinstance(child_node, InlineFragmentNode):
149                 inline_fragment_type = type_def
150                 if child_node.type_condition and child_node.type_condition.name:
151                     inline_fragment_type = self.context.schema.get_type(
152                         child_node.type_condition.name.value
153                     )
154                 node_cost = self.compute_node_cost(child_node, inline_fragment_type)
155             total += node_cost
156         return total
157 
158     def enter_operation_definition(
159         self, node, key, parent, path, ancestors
160     ):  # pylint: disable=unused-argument
161         if self.cost_map:
162             try:
163                 validate_cost_map(self.cost_map, self.context.schema)
164             except GraphQLError as cost_map_error:
165                 self.context.report_error(cost_map_error)
166                 return
167 
168         if node.operation is OperationType.QUERY:
169             self.cost += self.compute_node_cost(node, self.context.schema.query_type)
170         if node.operation is OperationType.MUTATION:
171             self.cost += self.compute_node_cost(node, self.context.schema.mutation_type)
172         if node.operation is OperationType.SUBSCRIPTION:
173             self.cost += self.compute_node_cost(
174                 node, self.context.schema.subscription_type
175             )
176 
177     def leave_operation_definition(
178         self, node, key, parent, path, ancestors
179     ):  # pylint: disable=unused-argument
180         if self.cost > self.maximum_cost:
181             self.context.report_error(self.get_cost_exceeded_error())
182 
183     def compute_cost(self, multipliers=None, use_multipliers=True, complexity=None):
184         if complexity is None:
185             complexity = self.default_complexity
186         if use_multipliers:
187             if multipliers:
188                 multiplier = reduce(add, multipliers, 0)
189                 self.operation_multipliers = self.operation_multipliers + [multiplier]
190             return reduce(mul, self.operation_multipliers, complexity)
191         return complexity
192 
193     def get_args_from_cost_map(
194         self, node: FieldNode, parent_type: str, field_args: Dict
195     ):
196         cost_args = None
197         cost_map = cast(Dict[Any, Dict], self.cost_map)
198         if parent_type in cost_map:
199             cost_args = cost_map[parent_type].get(node.name.value)
200         if not cost_args:
201             return None
202         cost_args = cost_args.copy()
203         if "multipliers" in cost_args:
204             cost_args["multipliers"] = self.get_multipliers_from_string(
205                 cost_args["multipliers"], field_args
206             )
207         return cost_args
208 
209     def get_args_from_directives(self, directives, field_args):
210         cost_directive = next(
211             (directive for directive in directives if directive.name.value == "cost"),
212             None,
213         )
214         if cost_directive and cost_directive.arguments:
215             complexity_arg = next(
216                 (
217                     argument
218                     for argument in cost_directive.arguments
219                     if argument.name.value == "complexity"
220                 ),
221                 None,
222             )
223             use_multipliers_arg = next(
224                 (
225                     argument
226                     for argument in cost_directive.arguments
227                     if argument.name.value == "useMultipliers"
228                 ),
229                 None,
230             )
231             multipliers_arg = next(
232                 (
233                     argument
234                     for argument in cost_directive.arguments
235                     if argument.name.value == "multipliers"
236                 ),
237                 None,
238             )
239             use_multipliers = (
240                 use_multipliers_arg.value.value
241                 if use_multipliers_arg
242                 and use_multipliers_arg.value
243                 and isinstance(use_multipliers_arg.value, BooleanValueNode)
244                 else True
245             )
246             multipliers = (
247                 self.get_multipliers_from_list_node(
248                     cast(List[Node], multipliers_arg.value.values), field_args
249                 )
250                 if multipliers_arg
251                 and multipliers_arg.value
252                 and isinstance(multipliers_arg.value, ListValueNode)
253                 else []
254             )
255             complexity = (
256                 int(complexity_arg.value.value)
257                 if complexity_arg
258                 and complexity_arg.value
259                 and isinstance(complexity_arg.value, IntValueNode)
260                 else None
261             )
262             return {
263                 "complexity": complexity,
264                 "multipliers": multipliers,
265                 "use_multipliers": use_multipliers,
266             }
267 
268         return None
269 
270     def get_multipliers_from_list_node(self, multipliers: List[Node], field_args):
271         multipliers = [
272             node.value  # type: ignore
273             for node in multipliers
274             if isinstance(node, StringValueNode)
275         ]
276         return self.get_multipliers_from_string(multipliers, field_args)  # type: ignore
277 
278     def get_multipliers_from_string(self, multipliers: List[str], field_args):
279         accessors = [s.split(".") for s in multipliers]
280         multipliers = []
281         for accessor in accessors:
282             val = field_args
283             for key in accessor:
284                 val = val.get(key)
285             try:
286                 multipliers.append(int(val))  # type: ignore
287             except (ValueError, TypeError):
288                 pass
289         multipliers = [
290             len(multiplier) if isinstance(multiplier, (list, tuple)) else multiplier
291             for multiplier in multipliers
292         ]
293         return [m for m in multipliers if m > 0]  # type: ignore
294 
295     def get_cost_exceeded_error(self) -> GraphQLError:
296         return GraphQLError(
297             cost_analysis_message(self.maximum_cost, self.cost),
298             extensions={
299                 "cost": {
300                     "requestedQueryCost": self.cost,
301                     "maximumAvailable": self.maximum_cost,
302                 }
303             },
304         )
305 
306 
307 def validate_cost_map(cost_map: Dict[str, Dict[str, Any]], schema: GraphQLSchema):
308     for type_name, type_fields in cost_map.items():
309         if type_name not in schema.type_map:
310             raise GraphQLError(
311                 "The query cost could not be calculated because cost map specifies a type "
312                 f"{type_name} that is not defined by the schema."
313             )
314 
315         if not isinstance(schema.type_map[type_name], GraphQLObjectType):
316             raise GraphQLError(
317                 "The query cost could not be calculated because cost map specifies a type "
318                 f"{type_name} that is defined by the schema, but is not an object type."
319             )
320 
321         for field_name in type_fields:
322             graphql_type = cast(GraphQLObjectType, schema.type_map[type_name])
323             if field_name not in graphql_type.fields:
324                 raise GraphQLError(
325                     "The query cost could not be calculated because cost map contains "
326                     f"a field {field_name} not defined by the {type_name} type."
327                 )
328 
329 
330 def report_error(context: ValidationContext, error: Exception):
331     context.report_error(GraphQLError(str(error), original_error=error))
332 
333 
334 def cost_analysis_message(maximum_cost: int, cost: int) -> str:
335     return "The query exceeds the maximum cost of %d. Actual cost is %d" % (
336         maximum_cost,
337         cost,
338     )
339 
340 
341 def cost_validator(
342     maximum_cost: int,
343     *,
344     default_cost: int = 0,
345     default_complexity: int = 1,
346     variables: Optional[Dict] = None,
347     cost_map: Optional[Dict[str, Dict[str, Any]]] = None,
348 ) -> Type[ASTValidationRule]:
349     class _CostValidator(CostValidator):
350         def __init__(self, context: ValidationContext) -> None:
351             super().__init__(
352                 context,
353                 maximum_cost=maximum_cost,
354                 default_cost=default_cost,
355                 default_complexity=default_complexity,
356                 variables=variables,
357                 cost_map=cost_map,
358             )
359 
360     return cast(Type[ASTValidationRule], _CostValidator)
361
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/ariadne/validation/query_cost.py b/ariadne/validation/query_cost.py
--- a/ariadne/validation/query_cost.py
+++ b/ariadne/validation/query_cost.py
@@ -144,14 +144,18 @@
                     fragment_type = self.context.schema.get_type(
                         fragment.type_condition.name.value
                     )
-                    node_cost = self.compute_node_cost(fragment, fragment_type)
+                    node_cost = self.compute_node_cost(
+                        fragment, fragment_type, self.operation_multipliers
+                    )
             if isinstance(child_node, InlineFragmentNode):
                 inline_fragment_type = type_def
                 if child_node.type_condition and child_node.type_condition.name:
                     inline_fragment_type = self.context.schema.get_type(
                         child_node.type_condition.name.value
                     )
-                node_cost = self.compute_node_cost(child_node, inline_fragment_type)
+                node_cost = self.compute_node_cost(
+                    child_node, inline_fragment_type, self.operation_multipliers
+                )
             total += node_cost
         return total

{"golden_diff": "diff --git a/ariadne/validation/query_cost.py b/ariadne/validation/query_cost.py\n--- a/ariadne/validation/query_cost.py\n+++ b/ariadne/validation/query_cost.py\n@@ -144,14 +144,18 @@\n                     fragment_type = self.context.schema.get_type(\n                         fragment.type_condition.name.value\n                     )\n-                    node_cost = self.compute_node_cost(fragment, fragment_type)\n+                    node_cost = self.compute_node_cost(\n+                        fragment, fragment_type, self.operation_multipliers\n+                    )\n             if isinstance(child_node, InlineFragmentNode):\n                 inline_fragment_type = type_def\n                 if child_node.type_condition and child_node.type_condition.name:\n                     inline_fragment_type = self.context.schema.get_type(\n                         child_node.type_condition.name.value\n                     )\n-                node_cost = self.compute_node_cost(child_node, inline_fragment_type)\n+                node_cost = self.compute_node_cost(\n+                    child_node, inline_fragment_type, self.operation_multipliers\n+                )\n             total += node_cost\n         return total\n", "issue": "Query cost validation is skipping `InlineFragmentNode`\nI've got tipped by @przlada that our query cost validator skips `InlineFragmentNode` when calculating the costs.\r\n\r\n`InlineFragmentNode` is a fragment used when querying interfaces and unions:\r\n\r\n```graphql\r\n{\r\n  search(query: \"lorem ipsum\") {\r\n    ... on User {\r\n      id\r\n      username\r\n    }\r\n    ... on Comment {\r\n      id\r\n      content\r\n    }\r\n  }\r\n}\r\n```\n", "before_files": [{"content": "from functools import reduce\nfrom operator import add, mul\nfrom typing import Any, Dict, List, Optional, Type, Union, cast\n\nfrom graphql import (\n    GraphQLError,\n    GraphQLInterfaceType,\n    GraphQLObjectType,\n    GraphQLSchema,\n    get_named_type,\n)\nfrom graphql.execution.values import get_argument_values\nfrom graphql.language import (\n    BooleanValueNode,\n    FieldNode,\n    FragmentDefinitionNode,\n    FragmentSpreadNode,\n    InlineFragmentNode,\n    IntValueNode,\n    ListValueNode,\n    Node,\n    OperationDefinitionNode,\n    OperationType,\n    StringValueNode,\n)\nfrom graphql.type import GraphQLFieldMap\nfrom graphql.validation import ValidationContext\nfrom graphql.validation.rules import ASTValidationRule, ValidationRule\n\ncost_directive = \"\"\"\ndirective @cost(complexity: Int, multipliers: [String!], useMultipliers: Boolean) on FIELD | FIELD_DEFINITION\n\"\"\"\n\n\nCostAwareNode = Union[\n    FieldNode,\n    FragmentDefinitionNode,\n    FragmentSpreadNode,\n    InlineFragmentNode,\n    OperationDefinitionNode,\n]\n\n\nclass CostValidator(ValidationRule):\n    context: ValidationContext\n    maximum_cost: int\n    default_cost: int = 0\n    default_complexity: int = 1\n    variables: Optional[Dict] = None\n    cost_map: Optional[Dict[str, Dict[str, Any]]] = None\n\n    def __init__(\n        self,\n        context: ValidationContext,\n        maximum_cost: int,\n        *,\n        default_cost: int = 0,\n        default_complexity: int = 1,\n        variables: Optional[Dict] = None,\n        cost_map: Optional[Dict[str, Dict[str, Any]]] = None,\n    ) -> None:\n        super().__init__(context)\n\n        self.maximum_cost = maximum_cost\n        self.variables = variables\n        self.cost_map = cost_map\n        self.default_cost = default_cost\n        self.default_complexity = default_complexity\n        self.cost = 0\n        self.operation_multipliers: List[Any] = []\n\n    def compute_node_cost(self, node: CostAwareNode, type_def, parent_multipliers=None):\n        if parent_multipliers is None:\n            parent_multipliers = []\n        if isinstance(node, FragmentSpreadNode) or not node.selection_set:\n            return 0\n        fields: GraphQLFieldMap = {}\n        if isinstance(type_def, (GraphQLObjectType, GraphQLInterfaceType)):\n            fields = type_def.fields\n        total = 0\n        for child_node in node.selection_set.selections:\n            self.operation_multipliers = parent_multipliers[:]\n            node_cost = self.default_cost\n            if isinstance(child_node, FieldNode):\n                field = fields.get(child_node.name.value)\n                if not field:\n                    continue\n                field_type = get_named_type(field.type)\n                try:\n                    field_args: Dict[str, Any] = get_argument_values(\n                        field, child_node, self.variables\n                    )\n                except Exception as e:\n                    report_error(self.context, e)\n                    field_args = {}\n                if self.cost_map:\n                    cost_map_args = (\n                        self.get_args_from_cost_map(\n                            child_node, type_def.name, field_args\n                        )\n                        if type_def and type_def.name\n                        else None\n                    )\n                    if cost_map_args is not None:\n                        try:\n                            node_cost = self.compute_cost(**cost_map_args)\n                        except (TypeError, ValueError) as e:\n                            report_error(self.context, e)\n                else:\n                    cost_is_computed = False\n                    if field.ast_node and field.ast_node.directives:\n                        directives_args = self.get_args_from_directives(\n                            field.ast_node.directives, field_args\n                        )\n                        if directives_args is not None:\n                            try:\n                                node_cost = self.compute_cost(**directives_args)\n                            except (TypeError, ValueError) as e:\n                                report_error(self.context, e)\n                            cost_is_computed = True\n                    if (\n                        field_type\n                        and field_type.ast_node\n                        and field_type.ast_node.directives\n                    ):\n                        if not cost_is_computed and isinstance(\n                            field_type, GraphQLObjectType\n                        ):\n                            directives_args = self.get_args_from_directives(\n                                field_type.ast_node.directives, field_args\n                            )\n                            if directives_args is not None:\n                                try:\n                                    node_cost = self.compute_cost(**directives_args)\n                                except (TypeError, ValueError) as e:\n                                    report_error(self.context, e)\n                child_cost = self.compute_node_cost(\n                    child_node, field_type, self.operation_multipliers\n                )\n                node_cost += child_cost\n            if isinstance(child_node, FragmentSpreadNode):\n                fragment = self.context.get_fragment(child_node.name.value)\n                if fragment:\n                    fragment_type = self.context.schema.get_type(\n                        fragment.type_condition.name.value\n                    )\n                    node_cost = self.compute_node_cost(fragment, fragment_type)\n            if isinstance(child_node, InlineFragmentNode):\n                inline_fragment_type = type_def\n                if child_node.type_condition and child_node.type_condition.name:\n                    inline_fragment_type = self.context.schema.get_type(\n                        child_node.type_condition.name.value\n                    )\n                node_cost = self.compute_node_cost(child_node, inline_fragment_type)\n            total += node_cost\n        return total\n\n    def enter_operation_definition(\n        self, node, key, parent, path, ancestors\n    ):  # pylint: disable=unused-argument\n        if self.cost_map:\n            try:\n                validate_cost_map(self.cost_map, self.context.schema)\n            except GraphQLError as cost_map_error:\n                self.context.report_error(cost_map_error)\n                return\n\n        if node.operation is OperationType.QUERY:\n            self.cost += self.compute_node_cost(node, self.context.schema.query_type)\n        if node.operation is OperationType.MUTATION:\n            self.cost += self.compute_node_cost(node, self.context.schema.mutation_type)\n        if node.operation is OperationType.SUBSCRIPTION:\n            self.cost += self.compute_node_cost(\n                node, self.context.schema.subscription_type\n            )\n\n    def leave_operation_definition(\n        self, node, key, parent, path, ancestors\n    ):  # pylint: disable=unused-argument\n        if self.cost > self.maximum_cost:\n            self.context.report_error(self.get_cost_exceeded_error())\n\n    def compute_cost(self, multipliers=None, use_multipliers=True, complexity=None):\n        if complexity is None:\n            complexity = self.default_complexity\n        if use_multipliers:\n            if multipliers:\n                multiplier = reduce(add, multipliers, 0)\n                self.operation_multipliers = self.operation_multipliers + [multiplier]\n            return reduce(mul, self.operation_multipliers, complexity)\n        return complexity\n\n    def get_args_from_cost_map(\n        self, node: FieldNode, parent_type: str, field_args: Dict\n    ):\n        cost_args = None\n        cost_map = cast(Dict[Any, Dict], self.cost_map)\n        if parent_type in cost_map:\n            cost_args = cost_map[parent_type].get(node.name.value)\n        if not cost_args:\n            return None\n        cost_args = cost_args.copy()\n        if \"multipliers\" in cost_args:\n            cost_args[\"multipliers\"] = self.get_multipliers_from_string(\n                cost_args[\"multipliers\"], field_args\n            )\n        return cost_args\n\n    def get_args_from_directives(self, directives, field_args):\n        cost_directive = next(\n            (directive for directive in directives if directive.name.value == \"cost\"),\n            None,\n        )\n        if cost_directive and cost_directive.arguments:\n            complexity_arg = next(\n                (\n                    argument\n                    for argument in cost_directive.arguments\n                    if argument.name.value == \"complexity\"\n                ),\n                None,\n            )\n            use_multipliers_arg = next(\n                (\n                    argument\n                    for argument in cost_directive.arguments\n                    if argument.name.value == \"useMultipliers\"\n                ),\n                None,\n            )\n            multipliers_arg = next(\n                (\n                    argument\n                    for argument in cost_directive.arguments\n                    if argument.name.value == \"multipliers\"\n                ),\n                None,\n            )\n            use_multipliers = (\n                use_multipliers_arg.value.value\n                if use_multipliers_arg\n                and use_multipliers_arg.value\n                and isinstance(use_multipliers_arg.value, BooleanValueNode)\n                else True\n            )\n            multipliers = (\n                self.get_multipliers_from_list_node(\n                    cast(List[Node], multipliers_arg.value.values), field_args\n                )\n                if multipliers_arg\n                and multipliers_arg.value\n                and isinstance(multipliers_arg.value, ListValueNode)\n                else []\n            )\n            complexity = (\n                int(complexity_arg.value.value)\n                if complexity_arg\n                and complexity_arg.value\n                and isinstance(complexity_arg.value, IntValueNode)\n                else None\n            )\n            return {\n                \"complexity\": complexity,\n                \"multipliers\": multipliers,\n                \"use_multipliers\": use_multipliers,\n            }\n\n        return None\n\n    def get_multipliers_from_list_node(self, multipliers: List[Node], field_args):\n        multipliers = [\n            node.value  # type: ignore\n            for node in multipliers\n            if isinstance(node, StringValueNode)\n        ]\n        return self.get_multipliers_from_string(multipliers, field_args)  # type: ignore\n\n    def get_multipliers_from_string(self, multipliers: List[str], field_args):\n        accessors = [s.split(\".\") for s in multipliers]\n        multipliers = []\n        for accessor in accessors:\n            val = field_args\n            for key in accessor:\n                val = val.get(key)\n            try:\n                multipliers.append(int(val))  # type: ignore\n            except (ValueError, TypeError):\n                pass\n        multipliers = [\n            len(multiplier) if isinstance(multiplier, (list, tuple)) else multiplier\n            for multiplier in multipliers\n        ]\n        return [m for m in multipliers if m > 0]  # type: ignore\n\n    def get_cost_exceeded_error(self) -> GraphQLError:\n        return GraphQLError(\n            cost_analysis_message(self.maximum_cost, self.cost),\n            extensions={\n                \"cost\": {\n                    \"requestedQueryCost\": self.cost,\n                    \"maximumAvailable\": self.maximum_cost,\n                }\n            },\n        )\n\n\ndef validate_cost_map(cost_map: Dict[str, Dict[str, Any]], schema: GraphQLSchema):\n    for type_name, type_fields in cost_map.items():\n        if type_name not in schema.type_map:\n            raise GraphQLError(\n                \"The query cost could not be calculated because cost map specifies a type \"\n                f\"{type_name} that is not defined by the schema.\"\n            )\n\n        if not isinstance(schema.type_map[type_name], GraphQLObjectType):\n            raise GraphQLError(\n                \"The query cost could not be calculated because cost map specifies a type \"\n                f\"{type_name} that is defined by the schema, but is not an object type.\"\n            )\n\n        for field_name in type_fields:\n            graphql_type = cast(GraphQLObjectType, schema.type_map[type_name])\n            if field_name not in graphql_type.fields:\n                raise GraphQLError(\n                    \"The query cost could not be calculated because cost map contains \"\n                    f\"a field {field_name} not defined by the {type_name} type.\"\n                )\n\n\ndef report_error(context: ValidationContext, error: Exception):\n    context.report_error(GraphQLError(str(error), original_error=error))\n\n\ndef cost_analysis_message(maximum_cost: int, cost: int) -> str:\n    return \"The query exceeds the maximum cost of %d. Actual cost is %d\" % (\n        maximum_cost,\n        cost,\n    )\n\n\ndef cost_validator(\n    maximum_cost: int,\n    *,\n    default_cost: int = 0,\n    default_complexity: int = 1,\n    variables: Optional[Dict] = None,\n    cost_map: Optional[Dict[str, Dict[str, Any]]] = None,\n) -> Type[ASTValidationRule]:\n    class _CostValidator(CostValidator):\n        def __init__(self, context: ValidationContext) -> None:\n            super().__init__(\n                context,\n                maximum_cost=maximum_cost,\n                default_cost=default_cost,\n                default_complexity=default_complexity,\n                variables=variables,\n                cost_map=cost_map,\n            )\n\n    return cast(Type[ASTValidationRule], _CostValidator)\n", "path": "ariadne/validation/query_cost.py"}], "after_files": [{"content": "from functools import reduce\nfrom operator import add, mul\nfrom typing import Any, Dict, List, Optional, Type, Union, cast\n\nfrom graphql import (\n    GraphQLError,\n    GraphQLInterfaceType,\n    GraphQLObjectType,\n    GraphQLSchema,\n    get_named_type,\n)\nfrom graphql.execution.values import get_argument_values\nfrom graphql.language import (\n    BooleanValueNode,\n    FieldNode,\n    FragmentDefinitionNode,\n    FragmentSpreadNode,\n    InlineFragmentNode,\n    IntValueNode,\n    ListValueNode,\n    Node,\n    OperationDefinitionNode,\n    OperationType,\n    StringValueNode,\n)\nfrom graphql.type import GraphQLFieldMap\nfrom graphql.validation import ValidationContext\nfrom graphql.validation.rules import ASTValidationRule, ValidationRule\n\ncost_directive = \"\"\"\ndirective @cost(complexity: Int, multipliers: [String!], useMultipliers: Boolean) on FIELD | FIELD_DEFINITION\n\"\"\"\n\n\nCostAwareNode = Union[\n    FieldNode,\n    FragmentDefinitionNode,\n    FragmentSpreadNode,\n    InlineFragmentNode,\n    OperationDefinitionNode,\n]\n\n\nclass CostValidator(ValidationRule):\n    context: ValidationContext\n    maximum_cost: int\n    default_cost: int = 0\n    default_complexity: int = 1\n    variables: Optional[Dict] = None\n    cost_map: Optional[Dict[str, Dict[str, Any]]] = None\n\n    def __init__(\n        self,\n        context: ValidationContext,\n        maximum_cost: int,\n        *,\n        default_cost: int = 0,\n        default_complexity: int = 1,\n        variables: Optional[Dict] = None,\n        cost_map: Optional[Dict[str, Dict[str, Any]]] = None,\n    ) -> None:\n        super().__init__(context)\n\n        self.maximum_cost = maximum_cost\n        self.variables = variables\n        self.cost_map = cost_map\n        self.default_cost = default_cost\n        self.default_complexity = default_complexity\n        self.cost = 0\n        self.operation_multipliers: List[Any] = []\n\n    def compute_node_cost(self, node: CostAwareNode, type_def, parent_multipliers=None):\n        if parent_multipliers is None:\n            parent_multipliers = []\n        if isinstance(node, FragmentSpreadNode) or not node.selection_set:\n            return 0\n        fields: GraphQLFieldMap = {}\n        if isinstance(type_def, (GraphQLObjectType, GraphQLInterfaceType)):\n            fields = type_def.fields\n        total = 0\n        for child_node in node.selection_set.selections:\n            self.operation_multipliers = parent_multipliers[:]\n            node_cost = self.default_cost\n            if isinstance(child_node, FieldNode):\n                field = fields.get(child_node.name.value)\n                if not field:\n                    continue\n                field_type = get_named_type(field.type)\n                try:\n                    field_args: Dict[str, Any] = get_argument_values(\n                        field, child_node, self.variables\n                    )\n                except Exception as e:\n                    report_error(self.context, e)\n                    field_args = {}\n                if self.cost_map:\n                    cost_map_args = (\n                        self.get_args_from_cost_map(\n                            child_node, type_def.name, field_args\n                        )\n                        if type_def and type_def.name\n                        else None\n                    )\n                    if cost_map_args is not None:\n                        try:\n                            node_cost = self.compute_cost(**cost_map_args)\n                        except (TypeError, ValueError) as e:\n                            report_error(self.context, e)\n                else:\n                    cost_is_computed = False\n                    if field.ast_node and field.ast_node.directives:\n                        directives_args = self.get_args_from_directives(\n                            field.ast_node.directives, field_args\n                        )\n                        if directives_args is not None:\n                            try:\n                                node_cost = self.compute_cost(**directives_args)\n                            except (TypeError, ValueError) as e:\n                                report_error(self.context, e)\n                            cost_is_computed = True\n                    if (\n                        field_type\n                        and field_type.ast_node\n                        and field_type.ast_node.directives\n                    ):\n                        if not cost_is_computed and isinstance(\n                            field_type, GraphQLObjectType\n                        ):\n                            directives_args = self.get_args_from_directives(\n                                field_type.ast_node.directives, field_args\n                            )\n                            if directives_args is not None:\n                                try:\n                                    node_cost = self.compute_cost(**directives_args)\n                                except (TypeError, ValueError) as e:\n                                    report_error(self.context, e)\n                child_cost = self.compute_node_cost(\n                    child_node, field_type, self.operation_multipliers\n                )\n                node_cost += child_cost\n            if isinstance(child_node, FragmentSpreadNode):\n                fragment = self.context.get_fragment(child_node.name.value)\n                if fragment:\n                    fragment_type = self.context.schema.get_type(\n                        fragment.type_condition.name.value\n                    )\n                    node_cost = self.compute_node_cost(\n                        fragment, fragment_type, self.operation_multipliers\n                    )\n            if isinstance(child_node, InlineFragmentNode):\n                inline_fragment_type = type_def\n                if child_node.type_condition and child_node.type_condition.name:\n                    inline_fragment_type = self.context.schema.get_type(\n                        child_node.type_condition.name.value\n                    )\n                node_cost = self.compute_node_cost(\n                    child_node, inline_fragment_type, self.operation_multipliers\n                )\n            total += node_cost\n        return total\n\n    def enter_operation_definition(\n        self, node, key, parent, path, ancestors\n    ):  # pylint: disable=unused-argument\n        if self.cost_map:\n            try:\n                validate_cost_map(self.cost_map, self.context.schema)\n            except GraphQLError as cost_map_error:\n                self.context.report_error(cost_map_error)\n                return\n\n        if node.operation is OperationType.QUERY:\n            self.cost += self.compute_node_cost(node, self.context.schema.query_type)\n        if node.operation is OperationType.MUTATION:\n            self.cost += self.compute_node_cost(node, self.context.schema.mutation_type)\n        if node.operation is OperationType.SUBSCRIPTION:\n            self.cost += self.compute_node_cost(\n                node, self.context.schema.subscription_type\n            )\n\n    def leave_operation_definition(\n        self, node, key, parent, path, ancestors\n    ):  # pylint: disable=unused-argument\n        if self.cost > self.maximum_cost:\n            self.context.report_error(self.get_cost_exceeded_error())\n\n    def compute_cost(self, multipliers=None, use_multipliers=True, complexity=None):\n        if complexity is None:\n            complexity = self.default_complexity\n        if use_multipliers:\n            if multipliers:\n                multiplier = reduce(add, multipliers, 0)\n                self.operation_multipliers = self.operation_multipliers + [multiplier]\n            return reduce(mul, self.operation_multipliers, complexity)\n        return complexity\n\n    def get_args_from_cost_map(\n        self, node: FieldNode, parent_type: str, field_args: Dict\n    ):\n        cost_args = None\n        cost_map = cast(Dict[Any, Dict], self.cost_map)\n        if parent_type in cost_map:\n            cost_args = cost_map[parent_type].get(node.name.value)\n        if not cost_args:\n            return None\n        cost_args = cost_args.copy()\n        if \"multipliers\" in cost_args:\n            cost_args[\"multipliers\"] = self.get_multipliers_from_string(\n                cost_args[\"multipliers\"], field_args\n            )\n        return cost_args\n\n    def get_args_from_directives(self, directives, field_args):\n        cost_directive = next(\n            (directive for directive in directives if directive.name.value == \"cost\"),\n            None,\n        )\n        if cost_directive and cost_directive.arguments:\n            complexity_arg = next(\n                (\n                    argument\n                    for argument in cost_directive.arguments\n                    if argument.name.value == \"complexity\"\n                ),\n                None,\n            )\n            use_multipliers_arg = next(\n                (\n                    argument\n                    for argument in cost_directive.arguments\n                    if argument.name.value == \"useMultipliers\"\n                ),\n                None,\n            )\n            multipliers_arg = next(\n                (\n                    argument\n                    for argument in cost_directive.arguments\n                    if argument.name.value == \"multipliers\"\n                ),\n                None,\n            )\n            use_multipliers = (\n                use_multipliers_arg.value.value\n                if use_multipliers_arg\n                and use_multipliers_arg.value\n                and isinstance(use_multipliers_arg.value, BooleanValueNode)\n                else True\n            )\n            multipliers = (\n                self.get_multipliers_from_list_node(\n                    cast(List[Node], multipliers_arg.value.values), field_args\n                )\n                if multipliers_arg\n                and multipliers_arg.value\n                and isinstance(multipliers_arg.value, ListValueNode)\n                else []\n            )\n            complexity = (\n                int(complexity_arg.value.value)\n                if complexity_arg\n                and complexity_arg.value\n                and isinstance(complexity_arg.value, IntValueNode)\n                else None\n            )\n            return {\n                \"complexity\": complexity,\n                \"multipliers\": multipliers,\n                \"use_multipliers\": use_multipliers,\n            }\n\n        return None\n\n    def get_multipliers_from_list_node(self, multipliers: List[Node], field_args):\n        multipliers = [\n            node.value  # type: ignore\n            for node in multipliers\n            if isinstance(node, StringValueNode)\n        ]\n        return self.get_multipliers_from_string(multipliers, field_args)  # type: ignore\n\n    def get_multipliers_from_string(self, multipliers: List[str], field_args):\n        accessors = [s.split(\".\") for s in multipliers]\n        multipliers = []\n        for accessor in accessors:\n            val = field_args\n            for key in accessor:\n                val = val.get(key)\n            try:\n                multipliers.append(int(val))  # type: ignore\n            except (ValueError, TypeError):\n                pass\n        multipliers = [\n            len(multiplier) if isinstance(multiplier, (list, tuple)) else multiplier\n            for multiplier in multipliers\n        ]\n        return [m for m in multipliers if m > 0]  # type: ignore\n\n    def get_cost_exceeded_error(self) -> GraphQLError:\n        return GraphQLError(\n            cost_analysis_message(self.maximum_cost, self.cost),\n            extensions={\n                \"cost\": {\n                    \"requestedQueryCost\": self.cost,\n                    \"maximumAvailable\": self.maximum_cost,\n                }\n            },\n        )\n\n\ndef validate_cost_map(cost_map: Dict[str, Dict[str, Any]], schema: GraphQLSchema):\n    for type_name, type_fields in cost_map.items():\n        if type_name not in schema.type_map:\n            raise GraphQLError(\n                \"The query cost could not be calculated because cost map specifies a type \"\n                f\"{type_name} that is not defined by the schema.\"\n            )\n\n        if not isinstance(schema.type_map[type_name], GraphQLObjectType):\n            raise GraphQLError(\n                \"The query cost could not be calculated because cost map specifies a type \"\n                f\"{type_name} that is defined by the schema, but is not an object type.\"\n            )\n\n        for field_name in type_fields:\n            graphql_type = cast(GraphQLObjectType, schema.type_map[type_name])\n            if field_name not in graphql_type.fields:\n                raise GraphQLError(\n                    \"The query cost could not be calculated because cost map contains \"\n                    f\"a field {field_name} not defined by the {type_name} type.\"\n                )\n\n\ndef report_error(context: ValidationContext, error: Exception):\n    context.report_error(GraphQLError(str(error), original_error=error))\n\n\ndef cost_analysis_message(maximum_cost: int, cost: int) -> str:\n    return \"The query exceeds the maximum cost of %d. Actual cost is %d\" % (\n        maximum_cost,\n        cost,\n    )\n\n\ndef cost_validator(\n    maximum_cost: int,\n    *,\n    default_cost: int = 0,\n    default_complexity: int = 1,\n    variables: Optional[Dict] = None,\n    cost_map: Optional[Dict[str, Dict[str, Any]]] = None,\n) -> Type[ASTValidationRule]:\n    class _CostValidator(CostValidator):\n        def __init__(self, context: ValidationContext) -> None:\n            super().__init__(\n                context,\n                maximum_cost=maximum_cost,\n                default_cost=default_cost,\n                default_complexity=default_complexity,\n                variables=variables,\n                cost_map=cost_map,\n            )\n\n    return cast(Type[ASTValidationRule], _CostValidator)\n", "path": "ariadne/validation/query_cost.py"}]}