Skip to content
This repository was archived by the owner on Jun 13, 2025. It is now read-only.

Commit c196e67

Browse files
committed
GQL: Handle internal server error from missing variables
1 parent 4a2bb9b commit c196e67

File tree

2 files changed

+68
-4
lines changed

2 files changed

+68
-4
lines changed

graphql_api/validation.py

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,59 @@
1-
from typing import Any, Type
1+
from typing import Any, Dict, Type
22

33
from graphql import GraphQLError, ValidationRule
4-
from graphql.language.ast import DocumentNode, FieldNode, OperationDefinitionNode
4+
from graphql.language.ast import (
5+
DocumentNode,
6+
FieldNode,
7+
OperationDefinitionNode,
8+
VariableDefinitionNode,
9+
)
510
from graphql.validation import ValidationContext
611

712

13+
class MissingVariablesError(Exception):
14+
"""
15+
Custom error class to represent errors where required variables defined in the query does
16+
not have a matching definition in the variables part of the request. Normally when this
17+
scenario occurs it would raise a GraphQLError type but that would cause a uncaught
18+
exception for some reason. The aim of this is to surface the error in the response clearly
19+
and to prevent internal server errors when it occurs.
20+
"""
21+
22+
pass
23+
24+
25+
def create_required_variables_rule(variables: Dict) -> Type[ValidationRule]:
26+
class RequiredVariablesValidationRule(ValidationRule):
27+
def __init__(self, context: ValidationContext) -> None:
28+
super().__init__(context)
29+
self.variables = variables
30+
31+
def enter_operation_definition(
32+
self, node: OperationDefinitionNode, *_args: Any
33+
) -> None:
34+
# Get variable definitions
35+
variable_definitions = node.variable_definitions or []
36+
37+
# Extract variables marked as Non Null
38+
required_variables = [
39+
var_def.variable.name.value
40+
for var_def in variable_definitions
41+
if isinstance(var_def, VariableDefinitionNode)
42+
and var_def.type.kind == "non_null_type"
43+
]
44+
45+
# Check if these required variables are provided
46+
missing_variables = [
47+
var for var in required_variables if var not in self.variables
48+
]
49+
if missing_variables:
50+
raise MissingVariablesError(
51+
f"Missing required variables: {', '.join(missing_variables)}",
52+
)
53+
54+
return RequiredVariablesValidationRule
55+
56+
857
def create_max_depth_rule(max_depth: int) -> Type[ValidationRule]:
958
class MaxDepthRule(ValidationRule):
1059
def __init__(self, context: ValidationContext) -> None:

graphql_api/views.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@
2828
from services.redis_configuration import get_redis_connection
2929

3030
from .schema import schema
31-
from .validation import create_max_aliases_rule, create_max_depth_rule
31+
from .validation import (
32+
MissingVariablesError,
33+
create_max_aliases_rule,
34+
create_max_depth_rule,
35+
create_required_variables_rule,
36+
)
3237

3338
log = logging.getLogger(__name__)
3439

@@ -198,6 +203,7 @@ def get_validation_rules(
198203
data: dict,
199204
) -> Optional[Collection]:
200205
return [
206+
create_required_variables_rule(variables=data.get("variables")),
201207
create_max_aliases_rule(max_aliases=settings.GRAPHQL_MAX_ALIASES),
202208
create_max_depth_rule(max_depth=settings.GRAPHQL_MAX_DEPTH),
203209
cost_validator(
@@ -259,7 +265,16 @@ async def post(self, request, *args, **kwargs):
259265
)
260266

261267
with RequestFinalizer(request):
262-
response = await super().post(request, *args, **kwargs)
268+
try:
269+
response = await super().post(request, *args, **kwargs)
270+
except MissingVariablesError as e:
271+
return JsonResponse(
272+
data={
273+
"status": 400,
274+
"detail": str(e),
275+
},
276+
status=400,
277+
)
263278

264279
content = response.content.decode("utf-8")
265280
data = json.loads(content)

0 commit comments

Comments
 (0)