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}