graphql_starter/graphql/
queried_fields.rs

1use async_graphql::{Context, SelectionField};
2
3use crate::queried_fields::QueriedFields;
4
5/// Trait to convert to a [QueriedFields]
6pub trait ContextQueriedFields {
7    /// Extracts the [QueriedFields] from the given context, skipping the current top-level field.
8    ///
9    /// ## Examples
10    ///
11    /// Given the following query:
12    ///
13    /// ```graphql
14    /// query {
15    ///   foo {
16    ///     a
17    ///     b
18    ///     bar {
19    ///       c
20    ///       d
21    ///     }
22    ///   }
23    /// }
24    /// ```
25    ///
26    /// The [QueriedFields] would contain different fields depending where it's called:
27    ///
28    /// - In the `foo` resolver, `["a", "b", "bar.c", "bar.d"]`
29    /// - In the `bar` resolver within `foo`, `["c", "d"]`
30    fn queried_fields(&self) -> QueriedFields;
31}
32
33impl ContextQueriedFields for &Context<'_> {
34    fn queried_fields(&self) -> QueriedFields {
35        let ctx = self.look_ahead();
36        let mut fields = Vec::new();
37        // There's always just one, the top field being queried, but we iter just in case future updates include more
38        for top_field in ctx.selection_fields() {
39            // Iterate first-level fields and extract all of their inner fields
40            for field in top_field.selection_set() {
41                push_fiend_and_extract_inner(field, None, &mut fields)
42            }
43        }
44        QueriedFields::Fields(fields)
45    }
46}
47
48/// Pushes the given [SelectionField] to the vec and extracts the inner queried fields recursively.
49///
50/// The fields will be nested using dots (`.`)
51fn push_fiend_and_extract_inner(field: SelectionField, parent: Option<&str>, fields: &mut Vec<String>) {
52    // Build the full qualified name for the field, including the parent
53    let full_name = match parent {
54        Some(parent) => format!("{parent}.{}", field.name()),
55        None => field.name().to_string(),
56    };
57    // Recursively push inner fields
58    for inner_field in field.selection_set() {
59        push_fiend_and_extract_inner(inner_field, Some(&full_name), fields);
60    }
61    // Push the field
62    fields.push(full_name);
63}