2025-12-01
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
from typing import Any, Callable, Collection, Dict, List, Optional, Union, cast
|
||||
|
||||
from ..error import GraphQLError
|
||||
from ..language import (
|
||||
DirectiveNode,
|
||||
EnumValueDefinitionNode,
|
||||
ExecutableDefinitionNode,
|
||||
FieldDefinitionNode,
|
||||
FieldNode,
|
||||
InputValueDefinitionNode,
|
||||
NullValueNode,
|
||||
SchemaDefinitionNode,
|
||||
SelectionNode,
|
||||
TypeDefinitionNode,
|
||||
TypeExtensionNode,
|
||||
VariableDefinitionNode,
|
||||
VariableNode,
|
||||
print_ast,
|
||||
)
|
||||
from ..pyutils import Undefined, inspect, print_path_list
|
||||
from ..type import (
|
||||
GraphQLDirective,
|
||||
GraphQLField,
|
||||
GraphQLInputType,
|
||||
GraphQLSchema,
|
||||
is_input_object_type,
|
||||
is_input_type,
|
||||
is_non_null_type,
|
||||
)
|
||||
from ..utilities.coerce_input_value import coerce_input_value
|
||||
from ..utilities.type_from_ast import type_from_ast
|
||||
from ..utilities.value_from_ast import value_from_ast
|
||||
|
||||
__all__ = ["get_argument_values", "get_directive_values", "get_variable_values"]
|
||||
|
||||
|
||||
CoercedVariableValues = Union[List[GraphQLError], Dict[str, Any]]
|
||||
|
||||
|
||||
def get_variable_values(
|
||||
schema: GraphQLSchema,
|
||||
var_def_nodes: Collection[VariableDefinitionNode],
|
||||
inputs: Dict[str, Any],
|
||||
max_errors: Optional[int] = None,
|
||||
) -> CoercedVariableValues:
|
||||
"""Get coerced variable values based on provided definitions.
|
||||
|
||||
Prepares a dict of variable values of the correct type based on the provided
|
||||
variable definitions and arbitrary input. If the input cannot be parsed to match
|
||||
the variable definitions, a GraphQLError will be raised.
|
||||
"""
|
||||
errors: List[GraphQLError] = []
|
||||
|
||||
def on_error(error: GraphQLError) -> None:
|
||||
if max_errors is not None and len(errors) >= max_errors:
|
||||
raise GraphQLError(
|
||||
"Too many errors processing variables,"
|
||||
" error limit reached. Execution aborted."
|
||||
)
|
||||
errors.append(error)
|
||||
|
||||
try:
|
||||
coerced = coerce_variable_values(schema, var_def_nodes, inputs, on_error)
|
||||
if not errors:
|
||||
return coerced
|
||||
except GraphQLError as e:
|
||||
errors.append(e)
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def coerce_variable_values(
|
||||
schema: GraphQLSchema,
|
||||
var_def_nodes: Collection[VariableDefinitionNode],
|
||||
inputs: Dict[str, Any],
|
||||
on_error: Callable[[GraphQLError], None],
|
||||
) -> Dict[str, Any]:
|
||||
coerced_values: Dict[str, Any] = {}
|
||||
for var_def_node in var_def_nodes:
|
||||
var_name = var_def_node.variable.name.value
|
||||
var_type = type_from_ast(schema, var_def_node.type)
|
||||
if not is_input_type(var_type):
|
||||
# Must use input types for variables. This should be caught during
|
||||
# validation, however is checked again here for safety.
|
||||
var_type_str = print_ast(var_def_node.type)
|
||||
on_error(
|
||||
GraphQLError(
|
||||
f"Variable '${var_name}' expected value of type '{var_type_str}'"
|
||||
" which cannot be used as an input type.",
|
||||
var_def_node.type,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
var_type = cast(GraphQLInputType, var_type)
|
||||
if var_name not in inputs:
|
||||
if var_def_node.default_value:
|
||||
coerced_values[var_name] = value_from_ast(
|
||||
var_def_node.default_value, var_type
|
||||
)
|
||||
elif is_non_null_type(var_type): # pragma: no cover else
|
||||
var_type_str = inspect(var_type)
|
||||
on_error(
|
||||
GraphQLError(
|
||||
f"Variable '${var_name}' of required type '{var_type_str}'"
|
||||
" was not provided.",
|
||||
var_def_node,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
value = inputs[var_name]
|
||||
if value is None and is_non_null_type(var_type):
|
||||
var_type_str = inspect(var_type)
|
||||
on_error(
|
||||
GraphQLError(
|
||||
f"Variable '${var_name}' of non-null type '{var_type_str}'"
|
||||
" must not be null.",
|
||||
var_def_node,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
def on_input_value_error(
|
||||
path: List[Union[str, int]], invalid_value: Any, error: GraphQLError
|
||||
) -> None:
|
||||
invalid_str = inspect(invalid_value)
|
||||
prefix = f"Variable '${var_name}' got invalid value {invalid_str}"
|
||||
if path:
|
||||
prefix += f" at '{var_name}{print_path_list(path)}'"
|
||||
on_error(
|
||||
GraphQLError(
|
||||
prefix + "; " + error.message,
|
||||
var_def_node,
|
||||
original_error=error,
|
||||
)
|
||||
)
|
||||
|
||||
coerced_values[var_name] = coerce_input_value(
|
||||
value, var_type, on_input_value_error
|
||||
)
|
||||
|
||||
return coerced_values
|
||||
|
||||
|
||||
def get_argument_values(
|
||||
type_def: Union[GraphQLField, GraphQLDirective],
|
||||
node: Union[FieldNode, DirectiveNode],
|
||||
variable_values: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Get coerced argument values based on provided definitions and nodes.
|
||||
|
||||
Prepares a dict of argument values given a list of argument definitions and list
|
||||
of argument AST nodes.
|
||||
"""
|
||||
coerced_values: Dict[str, Any] = {}
|
||||
arg_node_map = {arg.name.value: arg for arg in node.arguments or []}
|
||||
|
||||
for name, arg_def in type_def.args.items():
|
||||
arg_type = arg_def.type
|
||||
argument_node = arg_node_map.get(name)
|
||||
|
||||
if argument_node is None:
|
||||
value = arg_def.default_value
|
||||
if value is not Undefined:
|
||||
if is_input_object_type(arg_def.type):
|
||||
# coerce input value so that out_names are used
|
||||
value = coerce_input_value(value, arg_def.type)
|
||||
|
||||
coerced_values[arg_def.out_name or name] = value
|
||||
elif is_non_null_type(arg_type): # pragma: no cover else
|
||||
raise GraphQLError(
|
||||
f"Argument '{name}' of required type '{arg_type}'"
|
||||
" was not provided.",
|
||||
node,
|
||||
)
|
||||
continue # pragma: no cover
|
||||
|
||||
value_node = argument_node.value
|
||||
is_null = isinstance(argument_node.value, NullValueNode)
|
||||
|
||||
if isinstance(value_node, VariableNode):
|
||||
variable_name = value_node.name.value
|
||||
if variable_values is None or variable_name not in variable_values:
|
||||
value = arg_def.default_value
|
||||
if value is not Undefined:
|
||||
if is_input_object_type(arg_def.type):
|
||||
# coerce input value so that out_names are used
|
||||
value = coerce_input_value(value, arg_def.type)
|
||||
coerced_values[arg_def.out_name or name] = value
|
||||
elif is_non_null_type(arg_type): # pragma: no cover else
|
||||
raise GraphQLError(
|
||||
f"Argument '{name}' of required type '{arg_type}'"
|
||||
f" was provided the variable '${variable_name}'"
|
||||
" which was not provided a runtime value.",
|
||||
value_node,
|
||||
)
|
||||
continue # pragma: no cover
|
||||
is_null = variable_values[variable_name] is None
|
||||
|
||||
if is_null and is_non_null_type(arg_type):
|
||||
raise GraphQLError(
|
||||
f"Argument '{name}' of non-null type '{arg_type}' must not be null.",
|
||||
value_node,
|
||||
)
|
||||
|
||||
coerced_value = value_from_ast(value_node, arg_type, variable_values)
|
||||
if coerced_value is Undefined:
|
||||
# Note: `values_of_correct_type` validation should catch this before
|
||||
# execution. This is a runtime check to ensure execution does not
|
||||
# continue with an invalid argument value.
|
||||
raise GraphQLError(
|
||||
f"Argument '{name}' has invalid value {print_ast(value_node)}.",
|
||||
value_node,
|
||||
)
|
||||
coerced_values[arg_def.out_name or name] = coerced_value
|
||||
|
||||
return coerced_values
|
||||
|
||||
|
||||
NodeWithDirective = Union[
|
||||
EnumValueDefinitionNode,
|
||||
ExecutableDefinitionNode,
|
||||
FieldDefinitionNode,
|
||||
InputValueDefinitionNode,
|
||||
SelectionNode,
|
||||
SchemaDefinitionNode,
|
||||
TypeDefinitionNode,
|
||||
TypeExtensionNode,
|
||||
]
|
||||
|
||||
|
||||
def get_directive_values(
|
||||
directive_def: GraphQLDirective,
|
||||
node: NodeWithDirective,
|
||||
variable_values: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""Get coerced argument values based on provided nodes.
|
||||
|
||||
Prepares a dict of argument values given a directive definition and an AST node
|
||||
which may contain directives. Optionally also accepts a dict of variable values.
|
||||
|
||||
If the directive does not exist on the node, returns None.
|
||||
"""
|
||||
directives = node.directives
|
||||
if directives:
|
||||
directive_name = directive_def.name
|
||||
for directive in directives:
|
||||
if directive.name.value == directive_name:
|
||||
return get_argument_values(directive_def, directive, variable_values)
|
||||
return None
|
||||
Reference in New Issue
Block a user