1
1
use crate :: ast:: Value ;
2
+ use crate :: collections:: HashMap ;
2
3
use crate :: collections:: HashSet ;
3
4
use crate :: collections:: IndexMap ;
4
5
use crate :: executable:: Field ;
5
6
use crate :: executable:: Selection ;
6
7
use crate :: execution:: input_coercion:: coerce_argument_values;
7
8
use crate :: execution:: resolver:: ObjectValue ;
8
- use crate :: execution:: resolver:: ResolverError ;
9
+ use crate :: execution:: resolver:: ResolveError ;
10
+ use crate :: execution:: resolver:: ResolvedValue ;
9
11
use crate :: execution:: result_coercion:: complete_value;
12
+ use crate :: introspection:: resolvers:: MaybeLazy ;
13
+ use crate :: introspection:: resolvers:: SchemaWithImplementersMap ;
10
14
use crate :: parser:: SourceMap ;
11
15
use crate :: parser:: SourceSpan ;
12
16
use crate :: response:: GraphQLError ;
@@ -15,6 +19,7 @@ use crate::response::JsonValue;
15
19
use crate :: response:: ResponseDataPathSegment ;
16
20
use crate :: schema:: ExtendedType ;
17
21
use crate :: schema:: FieldDefinition ;
22
+ use crate :: schema:: Implementers ;
18
23
use crate :: schema:: ObjectType ;
19
24
use crate :: schema:: Type ;
20
25
use crate :: validation:: SuspectedValidationBug ;
@@ -26,7 +31,7 @@ use crate::Schema;
26
31
/// <https://spec.graphql.org/October2021/#sec-Normal-and-Serial-Execution>
27
32
#[ derive( Debug , Copy , Clone ) ]
28
33
pub ( crate ) enum ExecutionMode {
29
- /// Allowed to resolve fields in any order, including in parellel
34
+ /// Allowed to resolve fields in any order, including in parallel
30
35
Normal ,
31
36
/// Top-level fields of a mutation operation must be executed in order
32
37
#[ allow( unused) ]
@@ -46,134 +51,118 @@ pub(crate) struct LinkedPathElement<'a> {
46
51
pub ( crate ) next : LinkedPath < ' a > ,
47
52
}
48
53
54
+ pub ( crate ) struct ExecutionContext < ' a > {
55
+ pub ( crate ) schema : & ' a Valid < Schema > ,
56
+ pub ( crate ) document : & ' a Valid < ExecutableDocument > ,
57
+ pub ( crate ) variable_values : & ' a Valid < JsonMap > ,
58
+ pub ( crate ) errors : & ' a mut Vec < GraphQLError > ,
59
+ pub ( crate ) implementers_map : MaybeLazy < ' a , HashMap < Name , Implementers > > ,
60
+ }
61
+
49
62
/// <https://spec.graphql.org/October2021/#ExecuteSelectionSet()>
50
- #[ allow( clippy:: too_many_arguments) ] // yes it’s not a nice API but it’s internal
63
+ ///
64
+ /// `object_value: None` is a special case for top-level of `introspection::partial_execute`
51
65
pub ( crate ) fn execute_selection_set < ' a > (
52
- schema : & Valid < Schema > ,
53
- document : & ' a Valid < ExecutableDocument > ,
54
- variable_values : & Valid < JsonMap > ,
55
- errors : & mut Vec < GraphQLError > ,
66
+ ctx : & mut ExecutionContext < ' a > ,
56
67
path : LinkedPath < ' _ > ,
57
68
mode : ExecutionMode ,
58
69
object_type : & ObjectType ,
59
- object_value : & ObjectValue < ' _ > ,
70
+ object_value : Option < & dyn ObjectValue > ,
60
71
selections : impl IntoIterator < Item = & ' a Selection > ,
61
72
) -> Result < JsonMap , PropagateNull > {
62
73
let mut grouped_field_set = IndexMap :: default ( ) ;
63
74
collect_fields (
64
- schema,
65
- document,
66
- variable_values,
75
+ ctx,
67
76
object_type,
68
- object_value,
69
77
selections,
70
78
& mut HashSet :: default ( ) ,
71
79
& mut grouped_field_set,
72
80
) ;
73
81
74
82
match mode {
75
- ExecutionMode :: Normal => { }
76
- ExecutionMode :: Sequential => {
77
- // If we want parallelism, use `futures::future::join_all` (async)
83
+ ExecutionMode :: Normal => {
84
+ // If we want parallelism, use `StreamExt::buffer_unordered` (async)
78
85
// or Rayon’s `par_iter` (sync) here.
79
86
}
87
+ ExecutionMode :: Sequential => { }
80
88
}
81
89
82
90
let mut response_map = JsonMap :: with_capacity ( grouped_field_set. len ( ) ) ;
83
91
for ( & response_key, fields) in & grouped_field_set {
84
92
// Indexing should not panic: `collect_fields` only creates a `Vec` to push to it
85
93
let field_name = & fields[ 0 ] . name ;
86
- let Ok ( field_def) = schema. type_field ( & object_type. name , field_name) else {
94
+ let Ok ( field_def) = ctx . schema . type_field ( & object_type. name , field_name) else {
87
95
// TODO: Return a `validation_bug`` field error here?
88
96
// The spec specifically has a “If fieldType is defined” condition,
89
97
// but it being undefined would make the request invalid, right?
90
98
continue ;
91
99
} ;
92
- let value = if field_name == "__typename" {
93
- JsonValue :: from ( object_type. name . as_str ( ) )
94
- } else {
95
- let field_path = LinkedPathElement {
96
- element : ResponseDataPathSegment :: Field ( response_key. clone ( ) ) ,
97
- next : path,
98
- } ;
99
- execute_field (
100
- schema,
101
- document,
102
- variable_values,
103
- errors,
104
- Some ( & field_path) ,
105
- mode,
106
- object_value,
107
- field_def,
108
- fields,
109
- ) ?
100
+ let field_path = LinkedPathElement {
101
+ element : ResponseDataPathSegment :: Field ( response_key. clone ( ) ) ,
102
+ next : path,
110
103
} ;
111
- response_map. insert ( response_key. as_str ( ) , value) ;
104
+ if let Some ( value) = execute_field (
105
+ ctx,
106
+ Some ( & field_path) ,
107
+ mode,
108
+ object_type,
109
+ object_value,
110
+ field_def,
111
+ fields,
112
+ ) ? {
113
+ response_map. insert ( response_key. as_str ( ) , value) ;
114
+ }
112
115
}
113
116
Ok ( response_map)
114
117
}
115
118
116
119
/// <https://spec.graphql.org/October2021/#CollectFields()>
117
- #[ allow( clippy:: too_many_arguments) ] // yes it’s not a nice API but it’s internal
118
120
fn collect_fields < ' a > (
119
- schema : & Schema ,
120
- document : & ' a ExecutableDocument ,
121
- variable_values : & Valid < JsonMap > ,
121
+ ctx : & mut ExecutionContext < ' a > ,
122
122
object_type : & ObjectType ,
123
- object_value : & ObjectValue < ' _ > ,
124
123
selections : impl IntoIterator < Item = & ' a Selection > ,
125
124
visited_fragments : & mut HashSet < & ' a Name > ,
126
125
grouped_fields : & mut IndexMap < & ' a Name , Vec < & ' a Field > > ,
127
126
) {
128
127
for selection in selections {
129
- if eval_if_arg ( selection, "skip" , variable_values) . unwrap_or ( false )
130
- || !eval_if_arg ( selection, "include" , variable_values) . unwrap_or ( true )
128
+ if eval_if_arg ( selection, "skip" , ctx . variable_values ) . unwrap_or ( false )
129
+ || !eval_if_arg ( selection, "include" , ctx . variable_values ) . unwrap_or ( true )
131
130
{
132
131
continue ;
133
132
}
134
133
match selection {
135
- Selection :: Field ( field) => {
136
- if !object_value. skip_field ( & field. name ) {
137
- grouped_fields
138
- . entry ( field. response_key ( ) )
139
- . or_default ( )
140
- . push ( field. as_ref ( ) )
141
- }
142
- }
134
+ Selection :: Field ( field) => grouped_fields
135
+ . entry ( field. response_key ( ) )
136
+ . or_default ( )
137
+ . push ( field. as_ref ( ) ) ,
143
138
Selection :: FragmentSpread ( spread) => {
144
139
let new = visited_fragments. insert ( & spread. fragment_name ) ;
145
140
if !new {
146
141
continue ;
147
142
}
148
- let Some ( fragment) = document. fragments . get ( & spread. fragment_name ) else {
143
+ let Some ( fragment) = ctx . document . fragments . get ( & spread. fragment_name ) else {
149
144
continue ;
150
145
} ;
151
- if !does_fragment_type_apply ( schema, object_type, fragment. type_condition ( ) ) {
146
+ if !does_fragment_type_apply ( ctx . schema , object_type, fragment. type_condition ( ) ) {
152
147
continue ;
153
148
}
154
149
collect_fields (
155
- schema,
156
- document,
157
- variable_values,
150
+ ctx,
158
151
object_type,
159
- object_value,
160
152
& fragment. selection_set . selections ,
161
153
visited_fragments,
162
154
grouped_fields,
163
155
)
164
156
}
165
157
Selection :: InlineFragment ( inline) => {
166
158
if let Some ( condition) = & inline. type_condition {
167
- if !does_fragment_type_apply ( schema, object_type, condition) {
159
+ if !does_fragment_type_apply ( ctx . schema , object_type, condition) {
168
160
continue ;
169
161
}
170
162
}
171
163
collect_fields (
172
- schema,
173
- document,
174
- variable_values,
164
+ ctx,
175
165
object_type,
176
- object_value,
177
166
& inline. selection_set . selections ,
178
167
visited_fragments,
179
168
grouped_fields,
@@ -218,55 +207,79 @@ fn eval_if_arg(
218
207
}
219
208
220
209
/// <https://spec.graphql.org/October2021/#ExecuteField()>
221
- # [ allow ( clippy :: too_many_arguments ) ] // yes it’s not a nice API but it’s internal
222
- fn execute_field (
223
- schema : & Valid < Schema > ,
224
- document : & Valid < ExecutableDocument > ,
225
- variable_values : & Valid < JsonMap > ,
226
- errors : & mut Vec < GraphQLError > ,
210
+ ///
211
+ /// `object_value: None` is a special case for top-level of `introspection::partial_execute`
212
+ ///
213
+ /// Return `Ok(None)` for silently skipping that field.
214
+ fn execute_field < ' a > (
215
+ ctx : & mut ExecutionContext < ' a > ,
227
216
path : LinkedPath < ' _ > ,
228
217
mode : ExecutionMode ,
229
- object_value : & ObjectValue < ' _ > ,
218
+ object_type : & ObjectType ,
219
+ object_value : Option < & dyn ObjectValue > ,
230
220
field_def : & FieldDefinition ,
231
- fields : & [ & Field ] ,
232
- ) -> Result < JsonValue , PropagateNull > {
221
+ fields : & [ & ' a Field ] ,
222
+ ) -> Result < Option < JsonValue > , PropagateNull > {
233
223
let field = fields[ 0 ] ;
234
- let argument_values = match coerce_argument_values (
235
- schema,
236
- document,
237
- variable_values,
238
- errors,
239
- path,
240
- field_def,
241
- field,
242
- ) {
224
+ let argument_values = match coerce_argument_values ( ctx, path, field_def, field) {
243
225
Ok ( argument_values) => argument_values,
244
- Err ( PropagateNull ) => return try_nullify ( & field_def. ty , Err ( PropagateNull ) ) ,
226
+ Err ( PropagateNull ) if field_def. ty . is_non_null ( ) => return Err ( PropagateNull ) ,
227
+ Err ( PropagateNull ) => return Ok ( Some ( JsonValue :: Null ) ) ,
228
+ } ;
229
+ let is_field_of_root_query = || {
230
+ ctx. schema
231
+ . schema_definition
232
+ . query
233
+ . as_ref ( )
234
+ . is_some_and ( |q| q. name == object_type. name )
235
+ } ;
236
+ let resolved_result = match field. name . as_str ( ) {
237
+ "__typename" => Ok ( ResolvedValue :: leaf ( object_type. name . as_str ( ) ) ) ,
238
+ "__schema" if is_field_of_root_query ( ) => {
239
+ let schema = SchemaWithImplementersMap {
240
+ schema : ctx. schema ,
241
+ implementers_map : ctx. implementers_map ,
242
+ } ;
243
+ Ok ( ResolvedValue :: object ( schema) )
244
+ }
245
+ "__type" if is_field_of_root_query ( ) => {
246
+ let schema = SchemaWithImplementersMap {
247
+ schema : ctx. schema ,
248
+ implementers_map : ctx. implementers_map ,
249
+ } ;
250
+ if let Some ( name) = argument_values. get ( "name" ) . and_then ( |v| v. as_str ( ) ) {
251
+ Ok ( crate :: introspection:: resolvers:: type_def ( schema, name) )
252
+ } else {
253
+ // This should never happen: `coerce_argument_values()` returns a map that conforms
254
+ // to the `__type(name: String!): __Type` definition
255
+ // Still, in case of a bug prefer returning an error than panicking
256
+ Err ( ResolveError {
257
+ message : "expected string argument `name`" . into ( ) ,
258
+ } )
259
+ }
260
+ }
261
+ _ => {
262
+ if let Some ( obj) = object_value {
263
+ obj. resolve_field ( field, & argument_values)
264
+ } else {
265
+ // Skip non-introspection root fields for `introspection::partial_execute`
266
+ return Ok ( None ) ;
267
+ }
268
+ }
245
269
} ;
246
- let resolved_result = object_value. resolve_field ( & field. name , & argument_values) ;
247
270
let completed_result = match resolved_result {
248
- Ok ( resolved) => complete_value (
249
- schema,
250
- document,
251
- variable_values,
252
- errors,
253
- path,
254
- mode,
255
- field. ty ( ) ,
256
- resolved,
257
- fields,
258
- ) ,
259
- Err ( ResolverError { message } ) => {
260
- errors. push ( GraphQLError :: field_error (
271
+ Ok ( resolved) => complete_value ( ctx, path, mode, field. ty ( ) , resolved, fields) ,
272
+ Err ( ResolveError { message } ) => {
273
+ ctx. errors . push ( GraphQLError :: field_error (
261
274
format ! ( "resolver error: {message}" ) ,
262
275
path,
263
276
field. name . location ( ) ,
264
- & document. sources ,
277
+ & ctx . document . sources ,
265
278
) ) ;
266
279
Err ( PropagateNull )
267
280
}
268
281
} ;
269
- try_nullify ( & field_def. ty , completed_result)
282
+ try_nullify ( & field_def. ty , completed_result) . map ( Some )
270
283
}
271
284
272
285
/// Try to insert a propagated null if possible, or keep propagating it.
0 commit comments