async_graphql/resolver_utils/
container.rs

1use std::{future::Future, pin::Pin, sync::Arc};
2
3use futures_util::FutureExt;
4use indexmap::IndexMap;
5
6use crate::{
7    extensions::ResolveInfo, parser::types::Selection, Context, ContextBase, ContextSelectionSet,
8    Error, Name, OutputType, ServerError, ServerResult, Value,
9};
10
11/// Represents a GraphQL container object.
12///
13/// This helper trait allows the type to call `resolve_container` on itself in
14/// its `OutputType::resolve` implementation.
15#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
16pub trait ContainerType: OutputType {
17    /// This function returns true of type `EmptyMutation` only.
18    #[doc(hidden)]
19    fn is_empty() -> bool {
20        false
21    }
22
23    /// Resolves a field value and outputs it as a json value
24    /// `async_graphql::Value`.
25    ///
26    /// If the field was not found returns None.
27    #[cfg(feature = "boxed-trait")]
28    async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>>;
29
30    /// Resolves a field value and outputs it as a json value
31    /// `async_graphql::Value`.
32    ///
33    /// If the field was not found returns None.
34    #[cfg(not(feature = "boxed-trait"))]
35    fn resolve_field(
36        &self,
37        ctx: &Context<'_>,
38    ) -> impl Future<Output = ServerResult<Option<Value>>> + Send;
39
40    /// Collect all the fields of the container that are queried in the
41    /// selection set.
42    ///
43    /// Objects do not have to override this, but interfaces and unions must
44    /// call it on their internal type.
45    fn collect_all_fields<'a>(
46        &'a self,
47        ctx: &ContextSelectionSet<'a>,
48        fields: &mut Fields<'a>,
49    ) -> ServerResult<()>
50    where
51        Self: Send + Sync,
52    {
53        fields.add_set(ctx, self)
54    }
55
56    /// Find the GraphQL entity with the given name from the parameter.
57    ///
58    /// Objects should override this in case they are the query root.
59    #[cfg(feature = "boxed-trait")]
60    async fn find_entity(&self, _: &Context<'_>, _: &Value) -> ServerResult<Option<Value>> {
61        Ok(None)
62    }
63
64    /// Find the GraphQL entity with the given name from the parameter.
65    ///
66    /// Objects should override this in case they are the query root.
67    #[cfg(not(feature = "boxed-trait"))]
68    fn find_entity(
69        &self,
70        _: &Context<'_>,
71        _params: &Value,
72    ) -> impl Future<Output = ServerResult<Option<Value>>> + Send {
73        async { Ok(None) }
74    }
75}
76
77#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
78impl<T: ContainerType + ?Sized> ContainerType for &T {
79    async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
80        T::resolve_field(*self, ctx).await
81    }
82
83    async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
84        T::find_entity(*self, ctx, params).await
85    }
86}
87
88#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
89impl<T: ContainerType + ?Sized> ContainerType for Arc<T> {
90    async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
91        T::resolve_field(self, ctx).await
92    }
93
94    async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
95        T::find_entity(self, ctx, params).await
96    }
97}
98
99#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
100impl<T: ContainerType + ?Sized> ContainerType for Box<T> {
101    async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
102        T::resolve_field(self, ctx).await
103    }
104
105    async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
106        T::find_entity(self, ctx, params).await
107    }
108}
109
110#[cfg_attr(feature = "boxed-trait", async_trait::async_trait)]
111impl<T: ContainerType, E: Into<Error> + Send + Sync + Clone> ContainerType for Result<T, E> {
112    async fn resolve_field(&self, ctx: &Context<'_>) -> ServerResult<Option<Value>> {
113        match self {
114            Ok(value) => T::resolve_field(value, ctx).await,
115            Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))),
116        }
117    }
118
119    async fn find_entity(&self, ctx: &Context<'_>, params: &Value) -> ServerResult<Option<Value>> {
120        match self {
121            Ok(value) => T::find_entity(value, ctx, params).await,
122            Err(err) => Err(ctx.set_error_path(err.clone().into().into_server_error(ctx.item.pos))),
123        }
124    }
125}
126
127/// Resolve an container by executing each of the fields concurrently.
128pub async fn resolve_container<'a, T: ContainerType + ?Sized>(
129    ctx: &ContextSelectionSet<'a>,
130    root: &'a T,
131) -> ServerResult<Value> {
132    resolve_container_inner(ctx, root, true).await
133}
134
135/// Resolve an container by executing each of the fields serially.
136pub async fn resolve_container_serial<'a, T: ContainerType + ?Sized>(
137    ctx: &ContextSelectionSet<'a>,
138    root: &'a T,
139) -> ServerResult<Value> {
140    resolve_container_inner(ctx, root, false).await
141}
142
143pub(crate) fn create_value_object(values: Vec<(Name, Value)>) -> Value {
144    let mut map = IndexMap::new();
145    for (name, value) in values {
146        insert_value(&mut map, name, value);
147    }
148    Value::Object(map)
149}
150
151fn insert_value(target: &mut IndexMap<Name, Value>, name: Name, value: Value) {
152    if let Some(prev_value) = target.get_mut(&name) {
153        if let Value::Object(target_map) = prev_value {
154            if let Value::Object(obj) = value {
155                for (key, value) in obj.into_iter() {
156                    insert_value(target_map, key, value);
157                }
158            }
159        } else if let Value::List(target_list) = prev_value {
160            if let Value::List(list) = value {
161                for (idx, value) in list.into_iter().enumerate() {
162                    if let Some(Value::Object(target_map)) = target_list.get_mut(idx) {
163                        if let Value::Object(obj) = value {
164                            for (key, value) in obj.into_iter() {
165                                insert_value(target_map, key, value);
166                            }
167                        }
168                    }
169                }
170            }
171        }
172    } else {
173        target.insert(name, value);
174    }
175}
176
177async fn resolve_container_inner<'a, T: ContainerType + ?Sized>(
178    ctx: &ContextSelectionSet<'a>,
179    root: &'a T,
180    parallel: bool,
181) -> ServerResult<Value> {
182    let mut fields = Fields(Vec::new());
183    fields.add_set(ctx, root)?;
184
185    let res = if parallel {
186        futures_util::future::try_join_all(fields.0).await?
187    } else {
188        let mut results = Vec::with_capacity(fields.0.len());
189        for field in fields.0 {
190            results.push(field.await?);
191        }
192        results
193    };
194
195    Ok(create_value_object(res))
196}
197
198type BoxFieldFuture<'a> = Pin<Box<dyn Future<Output = ServerResult<(Name, Value)>> + 'a + Send>>;
199
200/// A set of fields on an container that are being selected.
201pub struct Fields<'a>(Vec<BoxFieldFuture<'a>>);
202
203impl<'a> Fields<'a> {
204    /// Add another set of fields to this set of fields using the given
205    /// container.
206    pub fn add_set<T: ContainerType + ?Sized>(
207        &mut self,
208        ctx: &ContextSelectionSet<'a>,
209        root: &'a T,
210    ) -> ServerResult<()> {
211        for selection in &ctx.item.node.items {
212            match &selection.node {
213                Selection::Field(field) => {
214                    if field.node.name.node == "__typename" {
215                        // Get the typename
216                        let ctx_field = ctx.with_field(field);
217                        let field_name = ctx_field.item.node.response_key().node.clone();
218                        let typename = root.introspection_type_name().into_owned();
219
220                        self.0.push(Box::pin(async move {
221                            Ok((field_name, Value::String(typename)))
222                        }));
223                        continue;
224                    }
225
226                    let resolve_fut = Box::pin({
227                        let ctx = ctx.clone();
228                        async move {
229                            let ctx_field = ctx.with_field(field);
230                            let field_name = ctx_field.item.node.response_key().node.clone();
231                            let extensions = &ctx.query_env.extensions;
232
233                            if extensions.is_empty() && field.node.directives.is_empty() {
234                                Ok((
235                                    field_name,
236                                    root.resolve_field(&ctx_field).await?.unwrap_or_default(),
237                                ))
238                            } else {
239                                let type_name = T::type_name();
240                                let resolve_info = ResolveInfo {
241                                    path_node: ctx_field.path_node.as_ref().unwrap(),
242                                    parent_type: &type_name,
243                                    return_type: match ctx_field
244                                        .schema_env
245                                        .registry
246                                        .types
247                                        .get(type_name.as_ref())
248                                        .and_then(|ty| {
249                                            ty.field_by_name(field.node.name.node.as_str())
250                                        })
251                                        .map(|field| &field.ty)
252                                    {
253                                        Some(ty) => &ty,
254                                        None => {
255                                            return Err(ServerError::new(
256                                                format!(
257                                                    r#"Cannot query field "{}" on type "{}"."#,
258                                                    field_name, type_name
259                                                ),
260                                                Some(ctx_field.item.pos),
261                                            ));
262                                        }
263                                    },
264                                    name: field.node.name.node.as_str(),
265                                    alias: field
266                                        .node
267                                        .alias
268                                        .as_ref()
269                                        .map(|alias| alias.node.as_str()),
270                                    is_for_introspection: ctx_field.is_for_introspection,
271                                    field: &field.node,
272                                };
273
274                                let resolve_fut = root.resolve_field(&ctx_field);
275
276                                if field.node.directives.is_empty() {
277                                    futures_util::pin_mut!(resolve_fut);
278                                    Ok((
279                                        field_name,
280                                        extensions
281                                            .resolve(resolve_info, &mut resolve_fut)
282                                            .await?
283                                            .unwrap_or_default(),
284                                    ))
285                                } else {
286                                    let mut resolve_fut = resolve_fut.boxed();
287
288                                    for directive in &field.node.directives {
289                                        if let Some(directive_factory) = ctx
290                                            .schema_env
291                                            .custom_directives
292                                            .get(directive.node.name.node.as_str())
293                                        {
294                                            let ctx_directive = ContextBase {
295                                                path_node: ctx_field.path_node,
296                                                is_for_introspection: false,
297                                                item: directive,
298                                                schema_env: ctx_field.schema_env,
299                                                query_env: ctx_field.query_env,
300                                                execute_data: ctx_field.execute_data,
301                                            };
302                                            let directive_instance = directive_factory
303                                                .create(&ctx_directive, &directive.node)?;
304                                            resolve_fut = Box::pin({
305                                                let ctx_field = ctx_field.clone();
306                                                async move {
307                                                    directive_instance
308                                                        .resolve_field(&ctx_field, &mut resolve_fut)
309                                                        .await
310                                                }
311                                            });
312                                        }
313                                    }
314
315                                    Ok((
316                                        field_name,
317                                        extensions
318                                            .resolve(resolve_info, &mut resolve_fut)
319                                            .await?
320                                            .unwrap_or_default(),
321                                    ))
322                                }
323                            }
324                        }
325                    });
326
327                    self.0.push(resolve_fut);
328                }
329                selection => {
330                    let (type_condition, selection_set) = match selection {
331                        Selection::Field(_) => unreachable!(),
332                        Selection::FragmentSpread(spread) => {
333                            let fragment =
334                                ctx.query_env.fragments.get(&spread.node.fragment_name.node);
335                            let fragment = match fragment {
336                                Some(fragment) => fragment,
337                                None => {
338                                    return Err(ServerError::new(
339                                        format!(
340                                            r#"Unknown fragment "{}"."#,
341                                            spread.node.fragment_name.node
342                                        ),
343                                        Some(spread.pos),
344                                    ));
345                                }
346                            };
347                            (
348                                Some(&fragment.node.type_condition),
349                                &fragment.node.selection_set,
350                            )
351                        }
352                        Selection::InlineFragment(fragment) => (
353                            fragment.node.type_condition.as_ref(),
354                            &fragment.node.selection_set,
355                        ),
356                    };
357                    let type_condition =
358                        type_condition.map(|condition| condition.node.on.node.as_str());
359
360                    let introspection_type_name = root.introspection_type_name();
361
362                    let applies_concrete_object = type_condition.is_some_and(|condition| {
363                        introspection_type_name == condition
364                            || ctx
365                                .schema_env
366                                .registry
367                                .implements
368                                .get(&*introspection_type_name)
369                                .is_some_and(|interfaces| interfaces.contains(condition))
370                    });
371                    if applies_concrete_object {
372                        root.collect_all_fields(&ctx.with_selection_set(selection_set), self)?;
373                    } else if type_condition.is_none_or(|condition| T::type_name() == condition) {
374                        // The fragment applies to an interface type.
375                        self.add_set(&ctx.with_selection_set(selection_set), root)?;
376                    }
377                }
378            }
379        }
380        Ok(())
381    }
382}