rusty_gql/resolver/
mod.rs

1mod boolean;
2mod id;
3mod list;
4mod number;
5mod object;
6mod optional;
7mod string;
8
9use std::{collections::BTreeMap, sync::Arc};
10
11use async_trait::async_trait;
12use futures_util::{
13    future::{try_join_all, BoxFuture},
14    FutureExt,
15};
16use graphql_parser::query::{Selection, TypeCondition};
17
18use crate::{
19    context::{Context, SelectionSetContext},
20    GqlDirective, GqlError, GqlValue, ResolverResult, TypeDefinition,
21};
22
23#[async_trait]
24pub trait SelectionSetResolver: CollectFields {
25    async fn resolve_selection_set(
26        &self,
27        ctx: &SelectionSetContext<'_>,
28    ) -> ResolverResult<GqlValue>;
29}
30
31#[async_trait]
32pub trait CollectFields: FieldResolver {
33    fn introspection_type_name(&self) -> String {
34        Self::type_name()
35    }
36
37    fn collect_all_fields<'a, 'ctx: 'a>(
38        &'a self,
39        ctx: &SelectionSetContext<'ctx>,
40        fields: &mut Fields<'a>,
41    ) -> ResolverResult<()> {
42        fields.collect_fields(ctx, self)
43    }
44}
45
46#[async_trait]
47pub trait FieldResolver: Send + Sync {
48    async fn resolve_field(&self, ctx: &Context<'_>) -> ResolverResult<Option<GqlValue>>;
49
50    fn type_name() -> String;
51}
52
53#[async_trait::async_trait]
54impl<T: FieldResolver> FieldResolver for &T {
55    #[allow(clippy::trivially_copy_pass_by_ref)]
56    async fn resolve_field(&self, ctx: &Context<'_>) -> ResolverResult<Option<GqlValue>> {
57        T::resolve_field(*self, ctx).await
58    }
59    fn type_name() -> String {
60        T::type_name()
61    }
62}
63
64impl<T: FieldResolver> CollectFields for &T {}
65
66#[async_trait::async_trait]
67impl<T: FieldResolver> FieldResolver for Arc<T> {
68    #[allow(clippy::trivially_copy_pass_by_ref)]
69    async fn resolve_field(&self, ctx: &Context<'_>) -> ResolverResult<Option<GqlValue>> {
70        T::resolve_field(self, ctx).await
71    }
72    fn type_name() -> String {
73        T::type_name()
74    }
75}
76
77impl<T: FieldResolver> CollectFields for Arc<T> {}
78
79#[async_trait::async_trait]
80impl<T: FieldResolver> FieldResolver for Box<T> {
81    #[allow(clippy::trivially_copy_pass_by_ref)]
82    async fn resolve_field(&self, ctx: &Context<'_>) -> ResolverResult<Option<GqlValue>> {
83        T::resolve_field(self, ctx).await
84    }
85    fn type_name() -> String {
86        T::type_name()
87    }
88}
89
90impl<T: FieldResolver> CollectFields for Box<T> {}
91
92pub async fn resolve_selection_parallelly<'a, 'ctx: 'a, T: CollectFields + SelectionSetResolver>(
93    ctx: &SelectionSetContext<'ctx>,
94    root_type: &'a T,
95) -> ResolverResult<GqlValue> {
96    resolve_selection(ctx, root_type, true).await
97}
98
99pub async fn resolve_selection_serially<'a, 'ctx: 'a, T: CollectFields + SelectionSetResolver>(
100    ctx: &SelectionSetContext<'ctx>,
101    root_type: &'a T,
102) -> ResolverResult<GqlValue> {
103    resolve_selection(ctx, root_type, false).await
104}
105async fn resolve_selection<'a, 'ctx: 'a, T: CollectFields + SelectionSetResolver>(
106    ctx: &SelectionSetContext<'ctx>,
107    root_type: &'a T,
108    parallel: bool,
109) -> ResolverResult<GqlValue> {
110    let mut fields = Fields(Vec::new());
111    fields.collect_fields(ctx, root_type)?;
112
113    let res = if parallel {
114        try_join_all(fields.0).await?
115    } else {
116        let mut results = Vec::new();
117        for resolver in fields.0 {
118            results.push(resolver.await?);
119        }
120        results
121    };
122
123    let mut gql_obj_map = BTreeMap::new();
124
125    for value in res {
126        build_gql_object(&mut gql_obj_map, value);
127    }
128
129    Ok(GqlValue::Object(gql_obj_map))
130}
131
132fn build_gql_object(target_obj: &mut BTreeMap<String, GqlValue>, gql_value: (String, GqlValue)) {
133    let (field_name, value) = gql_value;
134    if let Some(prev_value) = target_obj.get_mut(&field_name) {
135        match prev_value {
136            GqlValue::List(target_list) => {
137                if let GqlValue::List(list) = value {
138                    for (index, v) in list.into_iter().enumerate() {
139                        if let Some(GqlValue::Object(prev_obj)) = target_list.get_mut(index) {
140                            if let GqlValue::Object(new_obj) = v {
141                                for (key, value) in new_obj.into_iter() {
142                                    build_gql_object(prev_obj, (key, value))
143                                }
144                            }
145                        }
146                    }
147                }
148            }
149            GqlValue::Object(prev_obj) => {
150                if let GqlValue::Object(obj) = value {
151                    for map in obj.into_iter() {
152                        build_gql_object(prev_obj, (map.0, map.1))
153                    }
154                }
155            }
156            _ => {}
157        }
158    } else {
159        target_obj.insert(field_name, value.clone());
160    }
161}
162
163pub type ResolveFieldFuture<'a> = BoxFuture<'a, ResolverResult<(String, GqlValue)>>;
164pub struct Fields<'a>(Vec<ResolveFieldFuture<'a>>);
165
166impl<'a> Fields<'a> {
167    pub fn collect_fields<'ctx: 'a, T: CollectFields + ?Sized>(
168        &mut self,
169        ctx: &SelectionSetContext<'ctx>,
170        root_type: &'a T,
171    ) -> ResolverResult<()> {
172        for item in &ctx.item.items {
173            match &item {
174                Selection::Field(field) => {
175                    if ctx.is_skip(&field.directives) {
176                        continue;
177                    }
178                    if field.name == "__typename" {
179                        ctx.with_field(field);
180                        let field_name = field.name.clone();
181                        let type_name = root_type.introspection_type_name();
182
183                        self.0.push(Box::pin(async move {
184                            Ok((field_name, GqlValue::String(type_name)))
185                        }));
186                        continue;
187                    }
188
189                    self.0.push(Box::pin({
190                        let ctx = ctx.clone();
191                        async move {
192                            let ctx_field = &ctx.with_field(field);
193                            let field_name = ctx_field.item.name.clone();
194                            let type_name = T::type_name();
195                            let empty_vec = vec![];
196
197                            let query_directives = &field.directives;
198                            let schema_ty_directives = ctx
199                                .schema
200                                .type_definitions
201                                .get(&type_name)
202                                .map(|ty_def| ty_def.directives())
203                                .unwrap_or_else(|| empty_vec.as_slice());
204                            let schema_field_directives = ctx
205                                .schema
206                                .type_definitions
207                                .get(&type_name)
208                                .map(|ty_def| ty_def.field_directives(&field_name))
209                                .unwrap_or_default();
210                            let schema_impl_interface_directives = ctx
211                                .schema
212                                .type_definitions
213                                .get(&type_name)
214                                .map(|ty_def| ty_def.impl_interface_directives(ctx.schema))
215                                .unwrap_or_default();
216                            let resolve_fut = root_type.resolve_field(ctx_field);
217
218                            if schema_ty_directives.is_empty()
219                                && schema_field_directives.is_empty()
220                                && schema_impl_interface_directives.is_empty()
221                                && query_directives.is_empty()
222                            {
223                                Ok((
224                                    field_name,
225                                    root_type
226                                        .resolve_field(ctx_field)
227                                        .await?
228                                        .unwrap_or_default(),
229                                ))
230                            } else {
231                                let mut resolve_fut = resolve_fut.boxed();
232
233                                for directive in query_directives {
234                                    if let Some(custom_dir) =
235                                        ctx.schema.custom_directives.get(directive.name.as_str())
236                                    {
237                                        resolve_fut = Box::pin({
238                                            let directive = GqlDirective::from(directive.clone());
239                                            let ctx = ctx_field.clone();
240                                            async move {
241                                                custom_dir
242                                                    .resolve_field(
243                                                        &ctx,
244                                                        &directive.arguments,
245                                                        &mut resolve_fut,
246                                                    )
247                                                    .await
248                                            }
249                                        })
250                                    }
251                                }
252
253                                for directive in schema_ty_directives {
254                                    if let Some(custom_dir) =
255                                        ctx.schema.custom_directives.get(directive.name.as_str())
256                                    {
257                                        resolve_fut = Box::pin({
258                                            let ctx = ctx_field.clone();
259                                            async move {
260                                                custom_dir
261                                                    .resolve_field(
262                                                        &ctx,
263                                                        &directive.arguments,
264                                                        &mut resolve_fut,
265                                                    )
266                                                    .await
267                                            }
268                                        })
269                                    }
270                                }
271
272                                for directive in &schema_field_directives {
273                                    if let Some(custom_dir) =
274                                        ctx.schema.custom_directives.get(directive.name.as_str())
275                                    {
276                                        resolve_fut = Box::pin({
277                                            let ctx = ctx_field.clone();
278                                            async move {
279                                                custom_dir
280                                                    .resolve_field(
281                                                        &ctx,
282                                                        &directive.arguments,
283                                                        &mut resolve_fut,
284                                                    )
285                                                    .await
286                                            }
287                                        })
288                                    }
289                                }
290
291                                for directive in &schema_impl_interface_directives {
292                                    if let Some(custom_dir) =
293                                        ctx.schema.custom_directives.get(directive.name.as_str())
294                                    {
295                                        resolve_fut = Box::pin({
296                                            let ctx = ctx_field.clone();
297                                            async move {
298                                                custom_dir
299                                                    .resolve_field(
300                                                        &ctx,
301                                                        &directive.arguments,
302                                                        &mut resolve_fut,
303                                                    )
304                                                    .await
305                                            }
306                                        })
307                                    }
308                                }
309                                Ok((field_name, resolve_fut.await?.unwrap_or_default()))
310                            }
311                        }
312                    }))
313                }
314                Selection::FragmentSpread(fragment_spread) => {
315                    let operation_fragment = ctx
316                        .operation
317                        .fragment_definitions
318                        .get(&fragment_spread.fragment_name);
319                    let fragment_def = match operation_fragment {
320                        Some(fragment) => fragment,
321                        None => {
322                            return Err(GqlError::new(
323                                format!("{:?} is not defined in query", fragment_spread),
324                                Some(fragment_spread.position),
325                            ))
326                        }
327                    };
328
329                    if is_fragment_condition(
330                        ctx,
331                        &root_type.introspection_type_name(),
332                        Some(&fragment_def.type_condition),
333                    ) {
334                        root_type.collect_all_fields(
335                            &ctx.with_selection_set(&fragment_def.selection_set),
336                            self,
337                        )?;
338                    }
339                }
340                Selection::InlineFragment(inline_fragment) => {
341                    if ctx.is_skip(&inline_fragment.directives) {
342                        continue;
343                    }
344
345                    if is_fragment_condition(
346                        ctx,
347                        &root_type.introspection_type_name(),
348                        inline_fragment.type_condition.as_ref(),
349                    ) {
350                        root_type.collect_all_fields(
351                            &ctx.with_selection_set(&inline_fragment.selection_set),
352                            self,
353                        )?;
354                    } else if inline_fragment.type_condition.is_none() {
355                        self.collect_fields(
356                            &ctx.with_selection_set(&inline_fragment.selection_set),
357                            root_type,
358                        )?;
359                    }
360                }
361            }
362        }
363        Ok(())
364    }
365}
366
367fn is_fragment_condition<'a, 'ctx: 'a>(
368    ctx: &SelectionSetContext<'ctx>,
369    type_name: &str,
370    ty_cond: Option<&TypeCondition<'a, String>>,
371) -> bool {
372    match ty_cond {
373        Some(cond) => {
374            let TypeCondition::On(on_type) = cond;
375            let is_on_type_name = on_type == type_name;
376            let is_impl_interface =
377                ctx.schema
378                    .type_definitions
379                    .get(type_name)
380                    .map_or(false, |ty_def| {
381                        if let TypeDefinition::Object(obj) = ty_def {
382                            obj.implements_interfaces.contains(on_type)
383                        } else {
384                            false
385                        }
386                    });
387            is_on_type_name || is_impl_interface
388        }
389        None => false,
390    }
391}