Skip to content

Commit f59e425

Browse files
committed
Implement validation infrastructure
1 parent e028188 commit f59e425

File tree

13 files changed

+509
-45
lines changed

13 files changed

+509
-45
lines changed

graphql/core/__init__.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
from .executor import execute
1+
from .executor.executor import execute, ExecutionResult
22
from .language.source import Source
33
from .language.parser import parse
4+
from .validation import validate
45

56

67
def graphql(schema, request='', root=None, vars=None, operation_name=None):
78
source = Source(request, 'GraphQL request')
89
ast = parse(source)
9-
# TODO: validate document
10+
validation_errors = validate(schema, ast)
11+
if validation_errors:
12+
return ExecutionResult(
13+
data=None,
14+
errors=validation_errors,
15+
)
1016
return execute(
1117
schema,
1218
root or object(),

graphql/core/error.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,43 @@
1+
from .language.location import get_location
2+
3+
14
class Error(Exception):
25
pass
36

47

58
class GraphQLError(Error):
6-
def __init__(self, message, nodes=None, cause=None):
9+
def __init__(self, message, nodes=None, stack=None, source=None, positions=None):
710
super(GraphQLError, self).__init__(message)
11+
self.nodes = nodes
12+
self.stack = stack or message
13+
self._source = source
14+
self._positions = positions
15+
16+
@property
17+
def source(self):
18+
if self._source:
19+
return self._source
20+
if self.nodes:
21+
node = self.nodes[0]
22+
return node and node.loc and node.loc['source']
23+
24+
@property
25+
def positions(self):
26+
if self._positions:
27+
return self._positions
28+
if self.nodes is not None:
29+
node_positions = [node.loc and node.loc['start'] for node in self.nodes]
30+
if any(node_positions):
31+
return node_positions
32+
33+
@property
34+
def locations(self):
35+
if self.positions and self.source:
36+
return [get_location(self.source, pos) for pos in self.positions]
837

938

1039
def format_error(error):
11-
return error
40+
return {
41+
'message': error.message,
42+
'locations': error.locations,
43+
}

graphql/core/language/location.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
__all__ = ['get_location']
1+
from collections import namedtuple
22

3+
__all__ = ['get_location', 'SourceLocation']
34

4-
class SourceLocation(object):
5-
def __init__(self, line, column):
6-
self.line = line
7-
self.column = column
5+
SourceLocation = namedtuple('SourceLocation', 'line column')
86

97

108
def get_location(source, position):

graphql/core/language/visitor.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,14 @@ def is_node(maybe_node):
151151

152152
class Visitor(object):
153153
def enter(self, node, key, parent, path, ancestors):
154-
self._call_kind_specific_visitor('enter_', node, key, parent, path, ancestors)
154+
return self._call_kind_specific_visitor('enter_', node, key, parent, path, ancestors)
155155

156156
def leave(self, node, key, parent, path, ancestors):
157-
self._call_kind_specific_visitor('leave_', node, key, parent, path, ancestors)
157+
return self._call_kind_specific_visitor('leave_', node, key, parent, path, ancestors)
158158

159159
def _call_kind_specific_visitor(self, prefix, node, key, parent, path, ancestors):
160160
node_kind = type(node).__name__
161161
method_name = prefix + node_kind
162162
method = getattr(self, method_name, None)
163163
if method:
164-
method(node, key, parent, path, ancestors)
164+
return method(node, key, parent, path, ancestors)

graphql/core/type/definition.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,6 @@
5656
);
5757
}
5858
59-
/**
60-
* These types may describe the parent context of a selection set.
61-
*/
62-
export type GraphQLCompositeType =
63-
GraphQLObjectType |
64-
GraphQLInterfaceType |
65-
GraphQLUnionType;
66-
67-
export function isCompositeType(type: ?GraphQLType): boolean {
68-
return (
69-
type instanceof GraphQLObjectType ||
70-
type instanceof GraphQLInterfaceType ||
71-
type instanceof GraphQLUnionType
72-
);
73-
}
74-
7559
/**
7660
* These types may describe the parent context of a selection set.
7761
*/
@@ -113,13 +97,27 @@ def is_input_type(type):
11397
))
11498

11599

100+
def is_composite_type(type):
101+
return isinstance(type, (
102+
GraphQLObjectType,
103+
GraphQLInterfaceType,
104+
GraphQLUnionType,
105+
))
106+
107+
116108
def get_named_type(type):
117109
unmodified_type = type
118110
while isinstance(unmodified_type, (GraphQLList, GraphQLNonNull)):
119111
unmodified_type = unmodified_type.of_type
120112
return unmodified_type
121113

122114

115+
def get_nullable_type(type):
116+
if isinstance(type, GraphQLNonNull):
117+
return type.of_type
118+
return type
119+
120+
123121
class GraphQLType(object):
124122
def __str__(self):
125123
return self.name

graphql/core/utils.py

Lines changed: 151 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,169 @@
1-
from .language.ast import ListType, NonNullType, NamedType
2-
from .type.definition import GraphQLList, GraphQLNonNull
1+
from .language import ast
2+
from .type.definition import (
3+
GraphQLInputObjectType,
4+
GraphQLObjectType,
5+
GraphQLInterfaceType,
6+
GraphQLUnionType,
7+
GraphQLList,
8+
GraphQLNonNull,
9+
get_named_type,
10+
get_nullable_type,
11+
is_composite_type,
12+
)
13+
from .type.introspection import SchemaMetaFieldDef, TypeMetaFieldDef, TypeNameMetaFieldDef
314

415

516
def type_from_ast(schema, input_type_ast):
6-
if isinstance(input_type_ast, ListType):
17+
if isinstance(input_type_ast, ast.ListType):
718
inner_type = type_from_ast(schema, input_type_ast.type)
819
if inner_type:
920
return GraphQLList(inner_type)
1021
else:
1122
return None
12-
if isinstance(input_type_ast, NonNullType):
23+
if isinstance(input_type_ast, ast.NonNullType):
1324
inner_type = type_from_ast(schema, input_type_ast.type)
1425
if inner_type:
1526
return GraphQLNonNull(inner_type)
1627
else:
1728
return None
18-
assert isinstance(input_type_ast, NamedType), 'Must be a type name.'
29+
assert isinstance(input_type_ast, ast.NamedType), 'Must be a type name.'
1930
return schema.get_type(input_type_ast.name.value)
2031

2132

2233
def is_nullish(value):
2334
return value is None or value != value
35+
36+
37+
def pop(lst):
38+
if lst:
39+
lst.pop()
40+
41+
42+
class TypeInfo(object):
43+
def __init__(self, schema):
44+
self._schema = schema
45+
self._type_stack = []
46+
self._parent_type_stack = []
47+
self._input_type_stack = []
48+
self._field_def_stack = []
49+
self._directive = None
50+
self._argument = None
51+
52+
def get_type(self):
53+
if self._type_stack:
54+
return self._type_stack[-1]
55+
56+
def get_parent_type(self):
57+
if self._parent_type_stack:
58+
return self._parent_type_stack[-1]
59+
60+
def get_input_type(self):
61+
if self._input_type_stack:
62+
return self._input_type_stack[-1]
63+
64+
def get_field_def(self):
65+
if self._field_def_stack:
66+
return self._field_def_stack[-1]
67+
68+
def get_directive(self):
69+
return self._directive
70+
71+
def get_argument(self):
72+
return self._argument
73+
74+
def enter(self, node):
75+
schema = self._schema
76+
type = None
77+
if isinstance(node, ast.SelectionSet):
78+
named_type = get_named_type(self.get_type())
79+
composite_type = None
80+
if is_composite_type(named_type):
81+
composite_type = named_type
82+
self._parent_type_stack.append(composite_type)
83+
elif isinstance(node, ast.Field):
84+
parent_type = self.get_parent_type()
85+
field_def = None
86+
if parent_type:
87+
field_def = get_field_def(schema, parent_type, node)
88+
self._field_def_stack.append(field_def)
89+
self._type_stack.append(field_def and field_def.type)
90+
elif isinstance(node, ast.Directive):
91+
self._directive = schema.get_directive(node.name.value)
92+
elif isinstance(node, ast.OperationDefinition):
93+
if node.operation == 'query':
94+
type = schema.get_query_type()
95+
elif node.operation == 'mutation':
96+
type = schema.get_mutation_type()
97+
self._type_stack.append(type)
98+
elif isinstance(node, (ast.InlineFragment, ast.FragmentDefinition)):
99+
type = type_from_ast(schema, node.type_condition)
100+
self._type_stack.append(type)
101+
elif isinstance(node, ast.VariableDefinition):
102+
self._input_type_stack.append(type_from_ast(schema, node.type))
103+
elif isinstance(node, ast.Argument):
104+
arg_def = None
105+
arg_type = None
106+
field_or_directive = self.get_directive() or self.get_field_def()
107+
if field_or_directive:
108+
arg_def = filter(lambda arg: arg.name == node.name.value, field_or_directive.args)
109+
if arg_def:
110+
arg_def = arg_def[0]
111+
arg_type = arg_def.type
112+
else:
113+
arg_def = None
114+
self._argument = arg_def
115+
self._input_type_stack.append(arg_type)
116+
elif isinstance(node, ast.ListType):
117+
list_type = get_nullable_type(self.get_input_type())
118+
self._input_type_stack.append(
119+
list_type.of_type if isinstance(list_type, GraphQLList) else None
120+
)
121+
elif isinstance(node, ast.ObjectField):
122+
object_type = get_named_type(self.get_input_type())
123+
field_type = None
124+
if isinstance(object_type, GraphQLInputObjectType):
125+
input_field = object_type.get_fields().get(node.name.value)
126+
field_type = input_field.type if input_field else None
127+
self._input_type_stack.append(field_type)
128+
129+
def leave(self, node):
130+
if isinstance(node, ast.SelectionSet):
131+
pop(self._parent_type_stack)
132+
elif isinstance(node, ast.Field):
133+
pop(self._field_def_stack)
134+
pop(self._type_stack)
135+
elif isinstance(node, ast.Directive):
136+
self._directive = None
137+
elif isinstance(node, (
138+
ast.OperationDefinition,
139+
ast.InlineFragment,
140+
ast.FragmentDefinition,
141+
)):
142+
pop(self._type_stack)
143+
elif isinstance(node, ast.VariableDefinition):
144+
pop(self._input_type_stack)
145+
elif isinstance(node, ast.Argument):
146+
self._argument = None
147+
pop(self._input_type_stack)
148+
elif isinstance(node, (ast.ListType, ast.ObjectField)):
149+
pop(self._input_type_stack)
150+
151+
152+
def get_field_def(schema, parent_type, field_ast):
153+
"""Not exactly the same as the executor's definition of get_field_def, in this
154+
statically evaluated environment we do not always have an Object type,
155+
and need to handle Interface and Union types."""
156+
name = field_ast.name.value
157+
if name == SchemaMetaFieldDef.name and schema.get_query_type() == parent_type:
158+
return SchemaMetaFieldDef
159+
elif name == TypeMetaFieldDef.name and schema.get_query_type() == parent_type:
160+
return TypeMetaFieldDef
161+
elif name == TypeNameMetaFieldDef.name and \
162+
isinstance(parent_type, (
163+
GraphQLObjectType,
164+
GraphQLInterfaceType,
165+
GraphQLUnionType,
166+
)):
167+
return TypeNameMetaFieldDef
168+
elif isinstance(parent_type, (GraphQLObjectType, GraphQLInterfaceType)):
169+
return parent_type.get_fields().get(name)

0 commit comments

Comments
 (0)