1from math import isfinite
2from typing import Any
3
4from ..error import GraphQLError
5from ..pyutils import inspect, is_finite, is_integer, FrozenDict
6from ..language.ast import (
7    BooleanValueNode,
8    FloatValueNode,
9    IntValueNode,
10    StringValueNode,
11    ValueNode,
12)
13from ..language.printer import print_ast
14from .definition import GraphQLNamedType, GraphQLScalarType
15
16__all__ = [
17    "is_specified_scalar_type",
18    "specified_scalar_types",
19    "GraphQLInt",
20    "GraphQLFloat",
21    "GraphQLString",
22    "GraphQLBoolean",
23    "GraphQLID",
24]
25
26
27# As per the GraphQL Spec, Integers are only treated as valid when a valid
28# 32-bit signed integer, providing the broadest support across platforms.
29#
30# n.b. JavaScript's integers are safe between -(2^53 - 1) and 2^53 - 1 because
31# they are internally represented as IEEE 754 doubles,
32# while Python's integers may be arbitrarily large.
33MAX_INT = 2_147_483_647
34MIN_INT = -2_147_483_648
35
36
37def serialize_int(output_value: Any) -> int:
38    if isinstance(output_value, bool):
39        return 1 if output_value else 0
40    try:
41        if isinstance(output_value, int):
42            num = output_value
43        elif isinstance(output_value, float):
44            num = int(output_value)
45            if num != output_value:
46                raise ValueError
47        elif not output_value and isinstance(output_value, str):
48            output_value = ""
49            raise ValueError
50        else:
51            num = int(output_value)  # raises ValueError if not an integer
52    except (OverflowError, ValueError, TypeError):
53        raise GraphQLError(
54            "Int cannot represent non-integer value: " + inspect(output_value)
55        )
56    if not MIN_INT <= num <= MAX_INT:
57        raise GraphQLError(
58            "Int cannot represent non 32-bit signed integer value: "
59            + inspect(output_value)
60        )
61    return num
62
63
64def coerce_int(input_value: Any) -> int:
65    if not is_integer(input_value):
66        raise GraphQLError(
67            "Int cannot represent non-integer value: " + inspect(input_value)
68        )
69    if not MIN_INT <= input_value <= MAX_INT:
70        raise GraphQLError(
71            "Int cannot represent non 32-bit signed integer value: "
72            + inspect(input_value)
73        )
74    return int(input_value)
75
76
77def parse_int_literal(value_node: ValueNode, _variables: Any = None) -> int:
78    """Parse an integer value node in the AST."""
79    if not isinstance(value_node, IntValueNode):
80        raise GraphQLError(
81            "Int cannot represent non-integer value: " + print_ast(value_node),
82            value_node,
83        )
84    num = int(value_node.value)
85    if not MIN_INT <= num <= MAX_INT:
86        raise GraphQLError(
87            "Int cannot represent non 32-bit signed integer value: "
88            + print_ast(value_node),
89            value_node,
90        )
91    return num
92
93
94GraphQLInt = GraphQLScalarType(
95    name="Int",
96    description="The `Int` scalar type represents"
97    " non-fractional signed whole numeric values."
98    " Int can represent values between -(2^31) and 2^31 - 1.",
99    serialize=serialize_int,
100    parse_value=coerce_int,
101    parse_literal=parse_int_literal,
102)
103
104
105def serialize_float(output_value: Any) -> float:
106    if isinstance(output_value, bool):
107        return 1 if output_value else 0
108    try:
109        if not output_value and isinstance(output_value, str):
110            output_value = ""
111            raise ValueError
112        num = output_value if isinstance(output_value, float) else float(output_value)
113        if not isfinite(num):
114            raise ValueError
115    except (ValueError, TypeError):
116        raise GraphQLError(
117            "Float cannot represent non numeric value: " + inspect(output_value)
118        )
119    return num
120
121
122def coerce_float(input_value: Any) -> float:
123    if not is_finite(input_value):
124        raise GraphQLError(
125            "Float cannot represent non numeric value: " + inspect(input_value)
126        )
127    return float(input_value)
128
129
130def parse_float_literal(value_node: ValueNode, _variables: Any = None) -> float:
131    """Parse a float value node in the AST."""
132    if not isinstance(value_node, (FloatValueNode, IntValueNode)):
133        raise GraphQLError(
134            "Float cannot represent non numeric value: " + print_ast(value_node),
135            value_node,
136        )
137    return float(value_node.value)
138
139
140GraphQLFloat = GraphQLScalarType(
141    name="Float",
142    description="The `Float` scalar type represents"
143    " signed double-precision fractional values"
144    " as specified by [IEEE 754]"
145    "(https://en.wikipedia.org/wiki/IEEE_floating_point).",
146    serialize=serialize_float,
147    parse_value=coerce_float,
148    parse_literal=parse_float_literal,
149)
150
151
152def serialize_string(output_value: Any) -> str:
153    if isinstance(output_value, str):
154        return output_value
155    if isinstance(output_value, bool):
156        return "true" if output_value else "false"
157    if is_finite(output_value):
158        return str(output_value)
159    # do not serialize builtin types as strings, but allow serialization of custom
160    # types via their `__str__` method
161    if type(output_value).__module__ == "builtins":
162        raise GraphQLError("String cannot represent value: " + inspect(output_value))
163    return str(output_value)
164
165
166def coerce_string(input_value: Any) -> str:
167    if not isinstance(input_value, str):
168        raise GraphQLError(
169            "String cannot represent a non string value: " + inspect(input_value)
170        )
171    return input_value
172
173
174def parse_string_literal(value_node: ValueNode, _variables: Any = None) -> str:
175    """Parse a string value node in the AST."""
176    if not isinstance(value_node, StringValueNode):
177        raise GraphQLError(
178            "String cannot represent a non string value: " + print_ast(value_node),
179            value_node,
180        )
181    return value_node.value
182
183
184GraphQLString = GraphQLScalarType(
185    name="String",
186    description="The `String` scalar type represents textual data,"
187    " represented as UTF-8 character sequences."
188    " The String type is most often used by GraphQL"
189    " to represent free-form human-readable text.",
190    serialize=serialize_string,
191    parse_value=coerce_string,
192    parse_literal=parse_string_literal,
193)
194
195
196def serialize_boolean(output_value: Any) -> bool:
197    if isinstance(output_value, bool):
198        return output_value
199    if is_finite(output_value):
200        return bool(output_value)
201    raise GraphQLError(
202        "Boolean cannot represent a non boolean value: " + inspect(output_value)
203    )
204
205
206def coerce_boolean(input_value: Any) -> bool:
207    if not isinstance(input_value, bool):
208        raise GraphQLError(
209            "Boolean cannot represent a non boolean value: " + inspect(input_value)
210        )
211    return input_value
212
213
214def parse_boolean_literal(value_node: ValueNode, _variables: Any = None) -> bool:
215    """Parse a boolean value node in the AST."""
216    if not isinstance(value_node, BooleanValueNode):
217        raise GraphQLError(
218            "Boolean cannot represent a non boolean value: " + print_ast(value_node),
219            value_node,
220        )
221    return value_node.value
222
223
224GraphQLBoolean = GraphQLScalarType(
225    name="Boolean",
226    description="The `Boolean` scalar type represents `true` or `false`.",
227    serialize=serialize_boolean,
228    parse_value=coerce_boolean,
229    parse_literal=parse_boolean_literal,
230)
231
232
233def serialize_id(output_value: Any) -> str:
234    if isinstance(output_value, str):
235        return output_value
236    if is_integer(output_value):
237        return str(int(output_value))
238    # do not serialize builtin types as IDs, but allow serialization of custom types
239    # via their `__str__` method
240    if type(output_value).__module__ == "builtins":
241        raise GraphQLError("ID cannot represent value: " + inspect(output_value))
242    return str(output_value)
243
244
245def coerce_id(input_value: Any) -> str:
246    if isinstance(input_value, str):
247        return input_value
248    if is_integer(input_value):
249        return str(input_value)
250    raise GraphQLError("ID cannot represent value: " + inspect(input_value))
251
252
253def parse_id_literal(value_node: ValueNode, _variables: Any = None) -> str:
254    """Parse an ID value node in the AST."""
255    if not isinstance(value_node, (StringValueNode, IntValueNode)):
256        raise GraphQLError(
257            "ID cannot represent a non-string and non-integer value: "
258            + print_ast(value_node),
259            value_node,
260        )
261    return value_node.value
262
263
264GraphQLID = GraphQLScalarType(
265    name="ID",
266    description="The `ID` scalar type represents a unique identifier,"
267    " often used to refetch an object or as key for a cache."
268    " The ID type appears in a JSON response as a String; however,"
269    " it is not intended to be human-readable. When expected as an"
270    ' input type, any string (such as `"4"`) or integer (such as'
271    " `4`) input value will be accepted as an ID.",
272    serialize=serialize_id,
273    parse_value=coerce_id,
274    parse_literal=parse_id_literal,
275)
276
277
278specified_scalar_types: FrozenDict[str, GraphQLScalarType] = FrozenDict(
279    {
280        type_.name: type_
281        for type_ in (
282            GraphQLString,
283            GraphQLInt,
284            GraphQLFloat,
285            GraphQLBoolean,
286            GraphQLID,
287        )
288    }
289)
290
291
292def is_specified_scalar_type(type_: GraphQLNamedType) -> bool:
293    """Check whether the given named GraphQL type is a specified scalar type."""
294    return type_.name in specified_scalar_types
295