apollo_compiler/resolvers/mod.rs
1//! GraphQL [execution](https://spec.graphql.org/draft/#sec-Execution)
2//! based on callbacks resolving one field at a time.
3//!
4//! Start with [`Execution::new`],
5//! then use builder-pattern methods to configure,
6//! then use either the [`execute_sync`][Execution::execute_sync]
7//! or [`execute_async`][Execution::execute_async] method.
8//! They take an initial object value
9//! (implementing the [`ObjectValue`] or [`AsyncObjectValue`] trait respectively)
10//! that represents an instance of the root operation type (such as `Query`).
11//! Trait methods are called as needed to resolve object fields,
12//! which may in turn return more objects.
13//!
14//! How to implement the trait is up to the user:
15//! there could be a separate Rust struct per GraphQL object type,
16//! or a single Rust enum with a variants per GraphQL object type,
17//! or some other strategy.
18//!
19//! # Example
20//!
21//! ```
22#![doc = include_str!("../../examples/async_resolvers.rs")]
23//! ```
24
25use crate::collections::HashMap;
26use crate::executable;
27use crate::executable::Operation;
28#[cfg(doc)]
29use crate::introspection;
30use crate::request::coerce_variable_values;
31use crate::request::RequestError;
32use crate::resolvers::execution::execute_selection_set;
33use crate::resolvers::execution::ExecutionContext;
34use crate::resolvers::execution::ExecutionMode;
35use crate::resolvers::execution::MaybeLazy;
36use crate::resolvers::execution::PropagateNull;
37use crate::response::ExecutionResponse;
38use crate::response::JsonMap;
39use crate::response::JsonValue;
40use crate::schema;
41use crate::schema::Implementers;
42use crate::validation::Valid;
43use crate::ExecutableDocument;
44use crate::Name;
45use crate::Schema;
46use futures::future::BoxFuture;
47use futures::stream::BoxStream;
48use futures::FutureExt as _;
49use std::sync::OnceLock;
50
51mod execution;
52pub(crate) mod input_coercion;
53mod result_coercion;
54
55/// Builder for configuring GraphQL execution
56///
57/// See [module-level documentation][self].
58pub struct Execution<'a> {
59 schema: &'a Valid<Schema>,
60 document: &'a Valid<ExecutableDocument>,
61 operation: Option<&'a Operation>,
62 implementers_map: Option<&'a HashMap<Name, Implementers>>,
63 variable_values: Option<VariableValues<'a>>,
64 enable_schema_introspection: Option<bool>,
65}
66
67/// Default to disabled:
68/// https://www.apollographql.com/blog/why-you-should-disable-graphql-introspection-in-production/
69const DEFAULT_ENABLE_SCHEMA_INTROSPECTION: bool = false;
70
71enum VariableValues<'a> {
72 Raw(&'a JsonMap),
73 Coerced(&'a Valid<JsonMap>),
74}
75
76#[derive(Clone, Copy)]
77pub(crate) enum MaybeAsync<A, S> {
78 Async(A),
79 Sync(S),
80}
81
82pub(crate) type MaybeAsyncObject<'a> = MaybeAsync<&'a dyn AsyncObjectValue, &'a dyn ObjectValue>;
83
84pub(crate) type MaybeAsyncResolved<'a> = MaybeAsync<AsyncResolvedValue<'a>, ResolvedValue<'a>>;
85
86/// Information passed to [`ObjectValue::resolve_field`] or [`AsyncObjectValue::resolve_field`].
87pub struct ResolveInfo<'a> {
88 pub(crate) schema: &'a Valid<Schema>,
89 pub(crate) implementers_map: MaybeLazy<'a, HashMap<Name, Implementers>>,
90 pub(crate) document: &'a Valid<ExecutableDocument>,
91 pub(crate) fields: &'a [&'a executable::Field],
92 pub(crate) arguments: &'a JsonMap,
93}
94
95/// An error returned by [`ObjectValue::resolve_field`] or [`AsyncObjectValue::resolve_field`],
96/// which will become a [field error](https://spec.graphql.org/draft/#sec-Errors.Field-Errors)
97/// (a.k.a. execution error) in the GraphQL response,
98/// with path and locations filled in.
99pub struct FieldError {
100 pub message: String,
101}
102
103/// A concrete GraphQL object whose fields can be resolved during execution.
104pub trait ObjectValue {
105 /// Returns the name of the concrete object type
106 ///
107 /// That name expected to be that of an object type defined in the schema.
108 fn type_name(&self) -> &str;
109
110 /// Resolves a concrete field of this object
111 ///
112 /// The resolved value is expected to match the type of the corresponding field definition
113 /// in the schema.
114 ///
115 /// This is _not_ called for [introspection](https://spec.graphql.org/draft/#sec-Introspection)
116 /// meta-fields `__typename`, `__type`, or `__schema`: those are handled separately.
117 ///
118 /// A typical implementation might look like:
119 ///
120 /// ```ignore
121 /// match info.field_name() {
122 /// "field1" => Ok(ResolvedValue::leaf(self.resolve_field1())),
123 /// "field2" => Ok(ResolvedValue::list(self.resolve_field2())),
124 /// _ => Err(self.unknown_field_error(info)),
125 /// }
126 /// ```
127 fn resolve_field<'a>(
128 &'a self,
129 info: &'a ResolveInfo<'a>,
130 ) -> Result<ResolvedValue<'a>, FieldError>;
131
132 /// Generate a resolve error for `resolve_field` to return in case of an unexpected field name.
133 ///
134 /// In many cases this should never happen,
135 /// such as when writing resolvers for a fixed, known schema.
136 /// Still, this method generates a GraphQL field error without Rust panick in case of a bug.
137 fn unknown_field_error(&self, info: &ResolveInfo<'_>) -> FieldError {
138 FieldError::unknown_field(info.field_name(), self.type_name())
139 }
140}
141
142/// A concrete GraphQL object whose fields can be resolved asynchronously during execution.
143pub trait AsyncObjectValue: Send {
144 /// Returns the name of the concrete object type
145 ///
146 /// That name expected to be that of an object type defined in the schema.
147 fn type_name(&self) -> &str;
148
149 /// Resolves a concrete field of this object
150 ///
151 /// The resolved value is expected to match the type of the corresponding field definition
152 /// in the schema.
153 ///
154 /// This is _not_ called for [introspection](https://spec.graphql.org/draft/#sec-Introspection)
155 /// meta-fields `__typename`, `__type`, or `__schema`: those are handled separately.
156 ///
157 /// A typical implementation might look like:
158 ///
159 /// ```ignore
160 /// Box::pin(async move {
161 /// match info.field_name() {
162 /// "field1" => Ok(AsyncResolvedValue::leaf(self.resolve_field1().await)),
163 /// "field2" => Ok(AsyncResolvedValue::list(self.resolve_field2().await)),
164 /// _ => Err(self.unknown_field_error(info)),
165 /// }
166 /// })
167 /// ```
168 fn resolve_field<'a>(
169 &'a self,
170 info: &'a ResolveInfo<'a>,
171 ) -> BoxFuture<'a, Result<AsyncResolvedValue<'a>, FieldError>>;
172
173 /// Generate a resolve error for `resolve_field` to return in case of an unexpected field name.
174 ///
175 /// In many cases this should never happen,
176 /// such as when writing resolvers for a fixed, known schema.
177 /// Still, this method generates a GraphQL field error without Rust panick in case of a bug.
178 fn unknown_field_error(&self, info: &ResolveInfo<'_>) -> FieldError {
179 FieldError::unknown_field(info.field_name(), self.type_name())
180 }
181}
182
183/// The successful return type of [`ObjectValue::resolve_field`].
184pub enum ResolvedValue<'a> {
185 /// * JSON null represents GraphQL null
186 /// * A GraphQL enum value is represented as a JSON string
187 /// * GraphQL built-in scalars are coerced according to their respective *Result Coercion* spec
188 /// * For custom scalars, any JSON value is passed through as-is (including array or object)
189 Leaf(JsonValue),
190
191 /// Expected where the GraphQL type is an object, interface, or union type
192 Object(Box<dyn ObjectValue + 'a>),
193
194 /// Expected for GraphQL list types
195 List(Box<dyn Iterator<Item = Result<Self, FieldError>> + 'a>),
196
197 /// Skip this field as if the selection had `@skip(if: true)`:
198 /// do not insert null nor emit an error.
199 ///
200 /// This causes the eventual response data to be incomplete.
201 /// It can be useful to have some fields executed with per-field resolvers by this API
202 /// and other fields with some other execution model such as Apollo Federation,
203 /// with the two response `data` maps merged before sending the response.
204 ///
205 /// This is used by [`introspection::partial_execute`].
206 SkipForPartialExecution,
207}
208
209/// The successful return type of [`AsyncObjectValue::resolve_field`].
210pub enum AsyncResolvedValue<'a> {
211 /// * JSON null represents GraphQL null
212 /// * A GraphQL enum value is represented as a JSON string
213 /// * GraphQL built-in scalars are coerced according to their respective *Result Coercion* spec
214 /// * For custom scalars, any JSON value is passed through as-is (including array or object)
215 Leaf(JsonValue),
216
217 /// Expected where the GraphQL type is an object, interface, or union type
218 Object(Box<dyn AsyncObjectValue + 'a>),
219
220 /// Expected for GraphQL list types
221 List(BoxStream<'a, Result<Self, FieldError>>),
222
223 /// Skip this field as if the selection had `@skip(if: true)`:
224 /// do not insert null nor emit an error.
225 ///
226 /// This causes the eventual response data to be incomplete.
227 /// This can be useful to have some fields executed with per-field resolvers by this API
228 /// and other fields with some other execution model such as Apollo Federation,
229 /// with the two response `data` maps merged before sending the response.
230 ///
231 /// This is used by [`introspection::partial_execute`].
232 SkipForPartialExecution,
233}
234
235impl<'a> Execution<'a> {
236 /// Create a new builder for configuring GraphQL execution
237 ///
238 /// See [module-level documentation][self].
239 pub fn new(schema: &'a Valid<Schema>, document: &'a Valid<ExecutableDocument>) -> Self {
240 Self {
241 schema,
242 document,
243 operation: None,
244 implementers_map: None,
245 variable_values: None,
246 enable_schema_introspection: None,
247 }
248 }
249
250 /// Sets the operation to execute.
251 ///
252 /// Mutually exclusive with [`operation_name`][Self::operation_name].
253 pub fn operation(mut self, operation: &'a Operation) -> Self {
254 assert!(
255 self.operation.is_none(),
256 "operation to execute already provided"
257 );
258 self.operation = Some(operation);
259 self
260 }
261
262 /// Sets the operation to execute.
263 ///
264 /// Mutually exclusive with [`operation`][Self::operation].
265 ///
266 /// If neither is called or if `None` is passed here,
267 /// the document is expected to contain exactly one operation.
268 /// See [`document.operations.get()``][executable::OperationMap::get].
269 pub fn operation_name(mut self, operation_name: Option<&str>) -> Result<Self, RequestError> {
270 assert!(
271 self.operation.is_none(),
272 "operation to execute already provided"
273 );
274 self.operation = Some(self.document.operations.get(operation_name)?);
275 Ok(self)
276 }
277
278 /// Provide a pre-computed result of [`Schema::implementers_map`].
279 ///
280 /// If not provided here, it will be computed lazily on demand
281 /// and cached for the duration of execution.
282 pub fn implementers_map(mut self, implementers_map: &'a HashMap<Name, Implementers>) -> Self {
283 assert!(
284 self.implementers_map.is_none(),
285 "implementers map already provided"
286 );
287 self.implementers_map = Some(implementers_map);
288 self
289 }
290
291 /// Provide values of the request’s variables,
292 /// having already gone through [`coerce_variable_values`].
293 ///
294 /// Mutually exclusive with [`raw_variable_values`][Self::raw_variable_values].
295 ///
296 /// If neither is used, an empty map is assumed.
297 pub fn coerced_variable_values(mut self, variable_values: &'a Valid<JsonMap>) -> Self {
298 assert!(
299 self.variable_values.is_none(),
300 "variable values already provided"
301 );
302 self.variable_values = Some(VariableValues::Coerced(variable_values));
303 self
304 }
305
306 /// Provide values of the request’s variables.
307 ///
308 /// Mutually exclusive with [`coerced_variable_values`][Self::coerced_variable_values].
309 ///
310 /// If neither is used, an empty map is assumed.
311 pub fn raw_variable_values(mut self, variable_values: &'a JsonMap) -> Self {
312 assert!(
313 self.variable_values.is_none(),
314 "variable values already provided"
315 );
316 self.variable_values = Some(VariableValues::Raw(variable_values));
317 self
318 }
319
320 /// By default, schema introspection is _disabled_ per the [recommendation] to do so in production:
321 /// the meta-field `__schema` and `__type` return a field error.
322 /// (`__typename` is not affected, as it is always available.)
323 ///
324 /// Setting this configuration to `true` makes execution
325 /// generate the appropriate response data for those fields.
326 ///
327 /// [`ObjectValue::resolve_field`] or [`AsyncObjectValue::resolve_field`] is never called
328 /// for meta-fields `__typename`, `__schema`, or `__type`.
329 /// They are always handled implicitly.
330 ///
331 /// [recommendation]: https://www.apollographql.com/blog/why-you-should-disable-graphql-introspection-in-production/
332 pub fn enable_schema_introspection(mut self, enable_schema_introspection: bool) -> Self {
333 assert!(
334 self.enable_schema_introspection.is_none(),
335 "schema introspection already configured"
336 );
337 self.enable_schema_introspection = Some(enable_schema_introspection);
338 self
339 }
340
341 /// Perform execution with synchronous resolvers
342 pub fn execute_sync(
343 &self,
344 initial_value: &dyn ObjectValue,
345 ) -> Result<ExecutionResponse, RequestError> {
346 // To avoid code duplication, we call the same `async fn`s here as in `execute_async`.
347 let future = self.execute_common(MaybeAsync::Sync(initial_value));
348
349 // An `async fn` returns a future whose `poll` method returns:
350 //
351 // * `Poll::Ready(R)` when the function returns
352 // * `Poll::Pending` when it `.await`s an inner future that returns `Poll::Pending`
353 //
354 // When we use `MaybeAsync::Sync`, there are no manually-written implementations
355 // of the `Future` trait involved at all, only `async fn`s that call each other.
356 // Therefore we expect `Poll::Pending` to never be generated.
357 // Instead all futures should be immediately ready,
358 // and this `expect` should therefore never panic.
359 future
360 .now_or_never()
361 .expect("expected async fn with sync resolvers to never be pending")
362 }
363
364 /// Perform execution with asynchronous resolvers
365 pub async fn execute_async(
366 &self,
367 initial_value: &dyn AsyncObjectValue,
368 ) -> Result<ExecutionResponse, RequestError> {
369 self.execute_common(MaybeAsync::Async(initial_value)).await
370 }
371
372 async fn execute_common(
373 &self,
374 initial_value: MaybeAsyncObject<'_>,
375 ) -> Result<ExecutionResponse, RequestError> {
376 let operation = if let Some(op) = self.operation {
377 op
378 } else {
379 self.document.operations.get(None)?
380 };
381
382 let object_type_name = operation.object_type();
383 let Some(root_operation_object_type_def) = self.schema.get_object(object_type_name) else {
384 return Err(RequestError {
385 message: "Undefined root operation type".to_owned(),
386 location: object_type_name.location(),
387 is_suspected_validation_bug: true,
388 });
389 };
390
391 let map;
392 let variable_values = match self.variable_values {
393 None => {
394 map = Valid::assume_valid(JsonMap::new());
395 &map
396 }
397 Some(VariableValues::Raw(v)) => {
398 map = coerce_variable_values(self.schema, operation, v)?;
399 &map
400 }
401 Some(VariableValues::Coerced(v)) => v,
402 };
403 let lock;
404 let implementers_map = match self.implementers_map {
405 None => {
406 lock = OnceLock::new();
407 MaybeLazy::Lazy(&lock)
408 }
409 Some(map) => MaybeLazy::Eager(map),
410 };
411 let enable_schema_introspection = self
412 .enable_schema_introspection
413 .unwrap_or(DEFAULT_ENABLE_SCHEMA_INTROSPECTION);
414 let mut errors = Vec::new();
415 let mut context = ExecutionContext {
416 schema: self.schema,
417 document: self.document,
418 variable_values,
419 errors: &mut errors,
420 implementers_map,
421 enable_schema_introspection,
422 };
423 let mode = match operation.operation_type {
424 executable::OperationType::Query | executable::OperationType::Subscription => {
425 ExecutionMode::Normal
426 }
427 executable::OperationType::Mutation => ExecutionMode::Sequential,
428 };
429 let result = execute_selection_set(
430 &mut context,
431 None,
432 mode,
433 root_operation_object_type_def,
434 initial_value,
435 &operation.selection_set.selections,
436 )
437 .await;
438 let data = result
439 // If `Result::ok` converts an error to `None` that’s a field error on a non-null,
440 // field propagated all the way to the root,
441 // so that the JSON response should contain `"data": null`.
442 //
443 // No-op to witness the error type:
444 .inspect_err(|_: &PropagateNull| {})
445 .ok();
446 Ok(ExecutionResponse { data, errors })
447 }
448}
449
450impl<'a> ResolveInfo<'a> {
451 // https://github.com/graphql/graphql-js/blob/v16.11.0/src/type/definition.ts#L980-L991
452
453 /// The schema originally passed to [`Execution::new`]
454 pub fn schema(&self) -> &'a Valid<Schema> {
455 self.schema
456 }
457
458 pub fn implementers_map(&self) -> &'a HashMap<Name, Implementers> {
459 match self.implementers_map {
460 MaybeLazy::Eager(map) => map,
461 MaybeLazy::Lazy(cell) => cell.get_or_init(|| self.schema.implementers_map()),
462 }
463 }
464
465 /// The executable document originally passed to [`Execution::new`]
466 pub fn document(&self) -> &'a Valid<ExecutableDocument> {
467 self.document
468 }
469
470 /// The name of the field being resolved
471 pub fn field_name(&self) -> &'a str {
472 &self.fields[0].name
473 }
474
475 /// The field definition in the schema
476 pub fn field_definition(&self) -> &'a schema::FieldDefinition {
477 &self.fields[0].definition
478 }
479
480 /// The field selections being resolved.
481 ///
482 /// There is always at least one, but there may be more in case of
483 /// [field merging](https://spec.graphql.org/draft/#sec-Field-Selection-Merging).
484 pub fn field_selections(&self) -> &'a [&'a executable::Field] {
485 self.fields
486 }
487
488 /// The arguments passed to this field, after
489 /// [`CoerceArgumentValues()`](https://spec.graphql.org/draft/#sec-Coercing-Field-Arguments`):
490 /// this matches the argument definitions in the schema.
491 pub fn arguments(&self) -> &'a JsonMap {
492 self.arguments
493 }
494}
495
496impl<'a> ResolvedValue<'a> {
497 /// Construct a null leaf resolved value
498 pub fn null() -> Self {
499 Self::Leaf(JsonValue::Null)
500 }
501
502 /// Construct a leaf resolved value from something that is convertible to JSON
503 pub fn leaf(json: impl Into<JsonValue>) -> Self {
504 Self::Leaf(json.into())
505 }
506
507 /// Construct an object resolved value
508 pub fn object(object: impl ObjectValue + 'a) -> Self {
509 Self::Object(Box::new(object))
510 }
511
512 /// Construct an object resolved value or null
513 pub fn nullable_object(opt_object: Option<impl ObjectValue + 'a>) -> Self {
514 match opt_object {
515 Some(object) => Self::Object(Box::new(object)),
516 None => Self::null(),
517 }
518 }
519
520 /// Construct a list resolved value from an iterator
521 ///
522 /// If errors can happen during iteration,
523 /// construct the [`ResolvedValue::List`] enum variant directly instead.
524 pub fn list<I>(iter: I) -> Self
525 where
526 I: IntoIterator<Item = Self>,
527 I::IntoIter: 'a,
528 {
529 Self::List(Box::new(iter.into_iter().map(Ok)))
530 }
531}
532
533impl<'a> AsyncResolvedValue<'a> {
534 /// Construct a null leaf resolved value
535 pub fn null() -> Self {
536 Self::Leaf(JsonValue::Null)
537 }
538
539 /// Construct a leaf resolved value from something that is convertible to JSON
540 pub fn leaf(json: impl Into<JsonValue>) -> Self {
541 Self::Leaf(json.into())
542 }
543
544 /// Construct an object resolved value
545 pub fn object(object: impl AsyncObjectValue + 'a) -> Self {
546 Self::Object(Box::new(object))
547 }
548
549 /// Construct an object resolved value or null
550 pub fn nullable_object(opt_object: Option<impl AsyncObjectValue + 'a>) -> Self {
551 match opt_object {
552 Some(object) => Self::Object(Box::new(object)),
553 None => Self::null(),
554 }
555 }
556
557 /// Construct a list resolved value from an iterator
558 ///
559 /// If errors can happen during iteration,
560 /// construct the [`ResolvedValue::List`] enum variant directly instead.
561 pub fn list<I>(iter: I) -> Self
562 where
563 I: IntoIterator<Item = Self>,
564 I::IntoIter: 'a + Send,
565 {
566 Self::List(Box::pin(futures::stream::iter(iter.into_iter().map(Ok))))
567 }
568}
569
570impl MaybeAsync<Box<dyn AsyncObjectValue + '_>, Box<dyn ObjectValue + '_>> {
571 pub(crate) fn type_name(&self) -> &str {
572 match self {
573 MaybeAsync::Async(obj) => obj.type_name(),
574 MaybeAsync::Sync(obj) => obj.type_name(),
575 }
576 }
577}
578
579impl FieldError {
580 fn unknown_field(field_name: &str, type_name: &str) -> Self {
581 Self {
582 message: format!("unexpected field name: {field_name} in type {type_name}"),
583 }
584 }
585}