1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use crate::{ContextSelectionSet, ErrorWithPosition, GQLObject, QueryError, Result};
use graphql_parser::query::{Selection, TypeCondition};
use std::future::Future;
use std::pin::Pin;

struct Resolver<'a, T> {
    ctx: &'a ContextSelectionSet<'a>,
    obj: &'a T,
    result: &'a mut serde_json::Map<String, serde_json::Value>,
}

impl<'a, T: GQLObject + Send + Sync> Resolver<'a, T> {
    pub fn resolve(&'a mut self) -> Pin<Box<dyn Future<Output = Result<()>> + 'a + Send>> {
        Box::pin(async move {
            if self.ctx.items.is_empty() {
                anyhow::bail!(QueryError::MustHaveSubFields {
                    object: T::type_name().to_string(),
                }
                .with_position(self.ctx.span.0));
            }

            for selection in &self.ctx.item.items {
                match selection {
                    Selection::Field(field) => {
                        if self.ctx.is_skip(&field.directives)? {
                            continue;
                        }

                        let ctx_field = self.ctx.with_item(field);
                        if field.name.as_str() == "__typename" {
                            self.result
                                .insert(ctx_field.result_name(), T::type_name().to_string().into());
                            continue;
                        }

                        self.result.insert(
                            ctx_field.result_name(),
                            self.obj.resolve_field(&ctx_field, field).await?,
                        );
                    }
                    Selection::FragmentSpread(fragment_spread) => {
                        if self.ctx.is_skip(&fragment_spread.directives)? {
                            continue;
                        }

                        if let Some(fragment) =
                            self.ctx.fragments.get(&fragment_spread.fragment_name)
                        {
                            Resolver {
                                ctx: &self.ctx.with_item(&fragment.selection_set),
                                obj: self.obj,
                                result: self.result,
                            }
                            .resolve()
                            .await?;
                        } else {
                            return Err(QueryError::UnknownFragment {
                                name: fragment_spread.fragment_name.clone(),
                            }
                            .into());
                        }
                    }
                    Selection::InlineFragment(inline_fragment) => {
                        if self.ctx.is_skip(&inline_fragment.directives)? {
                            continue;
                        }

                        if let Some(TypeCondition::On(name)) = &inline_fragment.type_condition {
                            self.obj
                                .resolve_inline_fragment(
                                    &name,
                                    &self.ctx.with_item(&inline_fragment.selection_set),
                                    self.result,
                                )
                                .await?;
                        }
                    }
                }
            }

            Ok(())
        })
    }
}

pub async fn do_resolve<'a, T: GQLObject + Send + Sync>(
    ctx: &'a ContextSelectionSet<'a>,
    root: &'a T,
    result: &mut serde_json::Map<String, serde_json::Value>,
) -> Result<()> {
    Resolver {
        ctx,
        obj: root,
        result,
    }
    .resolve()
    .await?;
    Ok(())
}