from typing import Any, Callable, Dict, List, Optional, Union, cast from ..error import GraphQLError from ..pyutils import ( Path, did_you_mean, inspect, is_iterable, print_path_list, suggestion_list, Undefined, ) from ..type import ( GraphQLInputObjectType, GraphQLInputType, GraphQLList, GraphQLScalarType, is_leaf_type, is_input_object_type, is_list_type, is_non_null_type, GraphQLNonNull, ) __all__ = ["coerce_input_value"] OnErrorCB = Callable[[List[Union[str, int]], Any, GraphQLError], None] def default_on_error( path: List[Union[str, int]], invalid_value: Any, error: GraphQLError ) -> None: error_prefix = "Invalid value " + inspect(invalid_value) if path: error_prefix += f" at 'value{print_path_list(path)}'" error.message = error_prefix + ": " + error.message raise error def coerce_input_value( input_value: Any, type_: GraphQLInputType, on_error: OnErrorCB = default_on_error, path: Optional[Path] = None, ) -> Any: """Coerce a Python value given a GraphQL Input Type.""" if is_non_null_type(type_): if input_value is not None and input_value is not Undefined: type_ = cast(GraphQLNonNull, type_) return coerce_input_value(input_value, type_.of_type, on_error, path) on_error( path.as_list() if path else [], input_value, GraphQLError( f"Expected non-nullable type '{inspect(type_)}' not to be None." ), ) return Undefined if input_value is None or input_value is Undefined: # Explicitly return the value null. return None if is_list_type(type_): type_ = cast(GraphQLList, type_) item_type = type_.of_type if is_iterable(input_value): coerced_list: List[Any] = [] append_item = coerced_list.append for index, item_value in enumerate(input_value): append_item( coerce_input_value( item_value, item_type, on_error, Path(path, index, None) ) ) return coerced_list # Lists accept a non-list value as a list of one. return [coerce_input_value(input_value, item_type, on_error, path)] if is_input_object_type(type_): type_ = cast(GraphQLInputObjectType, type_) if not isinstance(input_value, dict): on_error( path.as_list() if path else [], input_value, GraphQLError(f"Expected type '{type_.name}' to be a mapping."), ) return Undefined coerced_dict: Dict[str, Any] = {} fields = type_.fields for field_name, field in fields.items(): field_value = input_value.get(field_name, Undefined) if field_value is Undefined: if field.default_value is not Undefined: # Use out name as name if it exists (extension of GraphQL.js). coerced_dict[field.out_name or field_name] = field.default_value elif is_non_null_type(field.type): # pragma: no cover else type_str = inspect(field.type) on_error( path.as_list() if path else [], input_value, GraphQLError( f"Field '{field_name}' of required type '{type_str}'" " was not provided." ), ) continue coerced_dict[field.out_name or field_name] = coerce_input_value( field_value, field.type, on_error, Path(path, field_name, type_.name) ) # Ensure every provided field is defined. for field_name in input_value: if field_name not in fields: suggestions = suggestion_list(field_name, fields) on_error( path.as_list() if path else [], input_value, GraphQLError( f"Field '{field_name}' is not defined by type '{type_.name}'." + did_you_mean(suggestions) ), ) if type_.is_one_of: keys = list(coerced_dict) if len(keys) != 1: on_error( path.as_list() if path else [], input_value, GraphQLError( "Exactly one key must be specified" f" for OneOf type '{type_.name}'.", ), ) else: key = keys[0] value = coerced_dict[key] if value is None: on_error( (path.as_list() if path else []) + [key], value, GraphQLError( f"Field '{key}' must be non-null.", ), ) return type_.out_type(coerced_dict) if is_leaf_type(type_): # Scalars determine if a value is valid via `parse_value()`, which can throw to # indicate failure. If it throws, maintain a reference to the original error. type_ = cast(GraphQLScalarType, type_) try: parse_result = type_.parse_value(input_value) except GraphQLError as error: on_error(path.as_list() if path else [], input_value, error) return Undefined except Exception as error: on_error( path.as_list() if path else [], input_value, GraphQLError( f"Expected type '{type_.name}'. {error}", original_error=error ), ) return Undefined if parse_result is Undefined: on_error( path.as_list() if path else [], input_value, GraphQLError(f"Expected type '{type_.name}'."), ) return parse_result # Not reachable. All possible input types have been considered. raise TypeError(f"Unexpected input type: {inspect(type_)}.")