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}