Skip to main content

mongo_graphql/schema/
builder.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::Arc;
3
4use async_graphql::dynamic::{
5    Field, FieldFuture, FieldValue, InputObject, InputValue, Object, Schema,
6    SchemaBuilder as AgSchemaBuilder, TypeRef,
7};
8use async_graphql::extensions::{Extension, ExtensionFactory};
9use mongodb::{Client, Database};
10
11use crate::error::GraphQLError;
12use crate::helpers::serialization::json_to_field_value;
13use crate::resolvers::{mutation, query};
14use crate::schema::definition::{CollectionDef, EnumDef, FieldType, RelationKind, SchemaDefinition};
15use crate::types::scalars;
16
17#[derive(Debug, Clone)]
18pub struct RuntimeConfig {
19    pub max_page_size: usize,
20}
21
22enum Operation {
23    Get,
24    List,
25    Create,
26    Update,
27    Delete,
28}
29struct BoxedExtensionFactory(Box<dyn ExtensionFactory>);
30
31impl ExtensionFactory for BoxedExtensionFactory {
32    fn create(&self) -> Arc<dyn Extension> {
33        self.0.create()
34    }
35}
36
37pub struct SchemaBuilder<'a> {
38    config: &'a RuntimeConfig,
39    definition: &'a SchemaDefinition,
40    registered_enums: HashSet<String>,
41    registered_nested_inputs: HashSet<String>,
42}
43
44impl<'a> SchemaBuilder<'a> {
45    pub fn new(config: &'a RuntimeConfig, definition: &'a SchemaDefinition) -> Self {
46        Self {
47            config,
48            definition,
49            registered_enums: HashSet::new(),
50            registered_nested_inputs: HashSet::new(),
51        }
52    }
53
54    pub async fn build(
55        mut self,
56        client: Client,
57        db: Database,
58        extensions: Option<Vec<Box<dyn ExtensionFactory>>>,
59    ) -> Result<Schema, GraphQLError> {
60        let mut ping_cmd = mongodb::bson::Document::new();
61        ping_cmd.insert("ping", 1);
62        db.run_command(ping_cmd)
63            .await
64            .map_err(|err| GraphQLError::Database(format!("Database ping failed: {}", err)))?;
65
66        let mut builder = Schema::build("Query", Some("Mutation"), None);
67        for extension in extensions.into_iter().flatten() {
68            builder = builder.extension(BoxedExtensionFactory(extension));
69        }
70        builder = scalars::register_all(builder);
71        builder = self.register_page_info(builder);
72        builder = self.register_delete_result(builder);
73        builder = self.register_filter_types(builder);
74
75        let mut query_root = Object::new("Query");
76        let mut mutation_root = Object::new("Mutation");
77
78        for collection in &self.definition.collections {
79            let where_unique = InputObject::new(format!("{}WhereUniqueInput", collection.type_name()))
80                .field(InputValue::new("id", TypeRef::named_nn("ID")));
81            builder = builder.register(where_unique);
82        }
83
84        for collection in &self.definition.collections {
85            (builder, query_root, mutation_root) = self.register_collection(
86                builder, query_root, mutation_root, collection, &client, &db,
87            )?;
88        }
89
90        builder = builder.register(query_root);
91        builder = builder.register(mutation_root);
92
93        let schema = builder
94            .data(client)
95            .data(db)
96            .data(self.config.clone())
97            .data(self.definition.clone())
98            .finish()
99            .map_err(|err| GraphQLError::SchemaBuild(format!("Failed to build schema: {}", err)))?;
100
101        Ok(schema)
102    }
103
104    fn register_collection(
105        &mut self,
106        mut builder: AgSchemaBuilder,
107        mut query_root: Object,
108        mut mutation_root: Object,
109        collection: &CollectionDef,
110        client: &Client,
111        db: &Database,
112    ) -> Result<(AgSchemaBuilder, Object, Object), GraphQLError> {
113        let type_name = collection.type_name();
114
115        for field in &collection.fields {
116            if let Some(enum_def) = &field.r#enum {
117                builder = self.register_enum(builder, enum_def);
118            }
119        }
120
121        let collection_map: HashMap<&str, &CollectionDef> = self
122            .definition
123            .collections
124            .iter()
125            .map(|collection| (collection.collection.as_str(), collection))
126            .collect();
127
128        // Collect reverse relations: other collections with OneToMany or OneToOne
129        // pointing to THIS collection get a reverse field on this collection's inputs.
130        let (reverse_to_many, reverse_to_one) = self.collect_reverse_relations(collection);
131
132        let mut obj = Object::new(type_name.clone());
133        for field in &collection.fields {
134            if matches!(field.field_type, FieldType::Relation(_)) {
135                continue;
136            }
137            let field_type = scalars::type_ref(&field.field_type);
138            let field_name = field.graphql_name();
139            obj = obj.field(Field::new(
140                field_name.clone(),
141                field_type,
142                move |ctx| {
143                    let name = field_name.clone();
144                    FieldFuture::new(async move {
145                        let value = ctx
146                            .parent_value
147                            .as_value()
148                            .and_then(|parent| extract_nested(parent, &name));
149                        match value {
150                            Some(val) => Ok(Some(FieldValue::value(val))),
151                            None => Ok(None),
152                        }
153                    })
154                },
155            ));
156        }
157
158        for field in &collection.fields {
159            let relation = match &field.field_type {
160                FieldType::Relation(relation) => relation,
161                _ => continue,
162            };
163            let target_def = match collection_map.get(relation.collection.as_str()) {
164                Some(collection_def) => (*collection_def).clone(),
165                None => continue,
166            };
167            let target_type = target_def.type_name();
168            let db_for_rel = db.clone();
169
170            match relation.kind {
171                RelationKind::OneToMany | RelationKind::OneToOne => {
172                    let target_coll_name = relation.collection.clone();
173                    let fk_mongo_name = field.name.clone();
174                    let target_def_for_closure = target_def.clone();
175                    obj = obj.field(Field::new(
176                        field.graphql_name(),
177                        TypeRef::named(target_type),
178                        move |ctx| {
179                            let db = db_for_rel.clone();
180                            let target_coll = target_coll_name.clone();
181                            let target_def = target_def_for_closure.clone();
182                            let fk_name = fk_mongo_name.clone();
183                            FieldFuture::new(async move {
184                                let result = crate::resolvers::query_relation::resolve_forward_to_one(
185                                    &ctx, &db, &target_coll, &target_def, &fk_name,
186                                )
187                                .await;
188                                resolve_to_field_value(result)
189                            })
190                        },
191                    ));
192                }
193                RelationKind::ManyToMany => {
194                    let junction = match &relation.junction {
195                        Some(junction_def) => junction_def.clone(),
196                        None => continue,
197                    };
198                    let target_def_for_closure = target_def.clone();
199                    obj = obj.field(Field::new(
200                        field.graphql_name(),
201                        TypeRef::named_nn_list(&target_type),
202                        move |ctx| {
203                            let db = db_for_rel.clone();
204                            let junction = junction.clone();
205                            let target_def = target_def_for_closure.clone();
206                            FieldFuture::new(async move {
207                                let result = crate::resolvers::query_relation::resolve_forward_many_to_many(
208                                    &ctx, &db, &junction, &target_def,
209                                )
210                                .await;
211                                resolve_to_field_value(result)
212                            })
213                        },
214                    ));
215                }
216            }
217        }
218
219        for (reverse_name, target_coll_name, fk_field) in &reverse_to_many {
220            let target_def = match collection_map.get(target_coll_name.as_str()) {
221                Some(collection_def) => (*collection_def).clone(),
222                None => continue,
223            };
224            let target_type = target_def.type_name();
225            let fk_field_for_closure = fk_field.clone();
226            let target_def_for_closure = target_def.clone();
227            let db_for_rel = db.clone();
228            obj = obj.field(Field::new(
229                reverse_name.clone(),
230                TypeRef::named_nn_list(target_type),
231                move |ctx| {
232                    let db = db_for_rel.clone();
233                    let target_def = target_def_for_closure.clone();
234                    let fk_field = fk_field_for_closure.clone();
235                    FieldFuture::new(async move {
236                        let result = crate::resolvers::query_relation::resolve_reverse_to_many(
237                            &ctx, &db, &target_def, &fk_field,
238                        )
239                        .await;
240                        resolve_to_field_value(result)
241                    })
242                },
243            ));
244        }
245
246        for (reverse_name, target_coll_name, fk_field) in &reverse_to_one {
247            let target_def = match collection_map.get(target_coll_name.as_str()) {
248                Some(collection_def) => (*collection_def).clone(),
249                None => continue,
250            };
251            let target_type = target_def.type_name();
252            let fk_field_for_closure = fk_field.clone();
253            let target_def_for_closure = target_def.clone();
254            let db_for_rel = db.clone();
255            obj = obj.field(Field::new(
256                reverse_name.clone(),
257                TypeRef::named(target_type),
258                move |ctx| {
259                    let db = db_for_rel.clone();
260                    let target_def = target_def_for_closure.clone();
261                    let fk_field = fk_field_for_closure.clone();
262                    FieldFuture::new(async move {
263                        let result = crate::resolvers::query_relation::resolve_reverse_to_one(
264                            &ctx, &db, &target_def, &fk_field,
265                        )
266                        .await;
267                        resolve_to_field_value(result)
268                    })
269                },
270            ));
271        }
272
273        builder = builder.register(obj);
274
275        for field in &collection.fields {
276            if let FieldType::Relation(_rel) = &field.field_type {
277                builder = self.register_nested_inputs_for_field(builder, collection, field);
278            }
279        }
280
281        for (_, target_coll_name, _) in &reverse_to_many {
282            if let Some(target_def) = collection_map.get(target_coll_name.as_str()) {
283                (builder, _) = self.register_reverse_inputs(
284                    builder, collection, target_def, RelationKind::OneToMany,
285                );
286            }
287        }
288        for (_, target_coll_name, _) in &reverse_to_one {
289            if let Some(target_def) = collection_map.get(target_coll_name.as_str()) {
290                (builder, _) = self.register_reverse_inputs(
291                    builder, collection, target_def, RelationKind::OneToOne,
292                );
293            }
294        }
295
296        let mut create_input = InputObject::new(format!("{}CreateInput", type_name));
297        create_input = self.build_input_fields(create_input, collection, &collection_map, "Create");
298        create_input = self.add_reverse_fields_to_input(create_input, &reverse_to_many, &reverse_to_one, &collection_map, &type_name, "Create");
299        builder = builder.register(create_input);
300
301        let mut update_input = InputObject::new(format!("{}UpdateInput", type_name));
302        update_input = self.build_input_fields(update_input, collection, &collection_map, "Update");
303        update_input = self.add_reverse_fields_to_input(update_input, &reverse_to_many, &reverse_to_one, &collection_map, &type_name, "Update");
304        builder = builder.register(update_input);
305
306        let mut where_input =
307            InputObject::new(format!("{}WhereInput", type_name))
308                .field(InputValue::new("id", TypeRef::named("IdFilter")));
309        for field in &collection.fields {
310            if field.name == "id" || field.name == "_id" {
311                continue;
312            }
313            if matches!(field.field_type, FieldType::Relation(_)) {
314                continue;
315            }
316            let filter_type = filter_type_name(&field.field_type);
317            where_input =
318                where_input.field(InputValue::new(field.graphql_name(), TypeRef::named(filter_type)));
319        }
320        builder = builder.register(where_input);
321
322        let mut sort_input = InputObject::new(format!("{}SortInput", type_name));
323        for field in &collection.fields {
324            if field.name == "id" || field.name == "_id" {
325                continue;
326            }
327            if matches!(field.field_type, FieldType::Relation(_) | FieldType::Json | FieldType::List(_)) {
328                continue;
329            }
330            sort_input =
331                sort_input.field(InputValue::new(field.graphql_name(), TypeRef::named("SortDirection")));
332        }
333        builder = builder.register(sort_input);
334
335        let object_ref = TypeRef::named(type_name.clone());
336
337        let conn_name = format!("{}Connection", type_name);
338        let conn_obj = Object::new(conn_name.clone())
339            .field(nested_field("edges", TypeRef::named_nn_list(type_name.clone())))
340            .field(nested_field("pageInfo", TypeRef::named_nn("PageInfo")))
341            .field(nested_field("totalCount", TypeRef::named_nn("Int")));
342        builder = builder.register(conn_obj);
343
344        let coll_for_get = collection.clone();
345        let db_for_get = db.clone();
346        query_root = query_root.field(Self::apply_directives(
347            Field::new(collection.singular_name(), object_ref.clone(), move |ctx| {
348                let coll_def = coll_for_get.clone();
349                let db = db_for_get.clone();
350                FieldFuture::new(async move {
351                    let result = query::resolve_get(ctx, &coll_def, &db).await;
352                    (match result {
353                        Ok(Some(value)) => json_to_field_value(value).map(Some),
354                        Ok(None) => Ok(None),
355                        Err(err) => Err(err),
356                    })
357                    .map_err(|err| err.into_graphql_error())
358                })
359            })
360            .argument(InputValue::new(
361                "where",
362                TypeRef::named_nn(format!("{}WhereUniqueInput", type_name)),
363            )),
364            &collection.directives,
365            Operation::Get,
366        ));
367
368        let coll_for_list = collection.clone();
369        let db_for_list = db.clone();
370        let page_size = self.config.max_page_size;
371        query_root = query_root.field(Self::apply_directives(
372            Field::new(
373                collection.plural_name(),
374                TypeRef::named_nn(conn_name.clone()),
375                move |ctx| {
376                    let coll_def = coll_for_list.clone();
377                    let db = db_for_list.clone();
378                    FieldFuture::new(async move {
379                        let result = query::resolve_list(ctx, &coll_def, &db, page_size).await;
380                        (match result {
381                            Ok(value) => json_to_field_value(value).map(Some),
382                            Err(err) => Err(err),
383                        })
384                        .map_err(|err| err.into_graphql_error())
385                    })
386                },
387            )
388            .argument(InputValue::new("first", TypeRef::named("Int")))
389            .argument(InputValue::new("after", TypeRef::named("String")))
390            .argument(InputValue::new("last", TypeRef::named("Int")))
391            .argument(InputValue::new("before", TypeRef::named("String")))
392            .argument(InputValue::new("where", TypeRef::named(format!("{}WhereInput", type_name))))
393            .argument(InputValue::new("sort", TypeRef::named(format!("{}SortInput", type_name)))),
394            &collection.directives,
395            Operation::List,
396        ));
397
398        let coll_for_create = collection.clone();
399        let client_for_create = client.clone();
400        let db_for_create = db.clone();
401        mutation_root = mutation_root.field(Self::apply_directives(
402            Field::new(
403                format!("create{}", type_name),
404                object_ref.clone(),
405                move |ctx| {
406                    let coll_def = coll_for_create.clone();
407                    let client = client_for_create.clone();
408                    let db = db_for_create.clone();
409                    FieldFuture::new(async move {
410                        let result = mutation::resolve_create(ctx, &coll_def, &client, &db).await;
411                        resolve_to_field_value(result)
412                    })
413                },
414            )
415            .argument(InputValue::new(
416                "input",
417                TypeRef::named_nn(format!("{}CreateInput", type_name)),
418            )),
419            &collection.directives,
420            Operation::Create,
421        ));
422
423        let coll_for_update = collection.clone();
424        let client_for_update = client.clone();
425        let db_for_update = db.clone();
426        mutation_root = mutation_root.field(Self::apply_directives(
427            Field::new(
428                format!("update{}", type_name),
429                object_ref.clone(),
430                move |ctx| {
431                    let coll_def = coll_for_update.clone();
432                    let client = client_for_update.clone();
433                    let db = db_for_update.clone();
434                    FieldFuture::new(async move {
435                        let result = mutation::resolve_update(ctx, &coll_def, &client, &db).await;
436                        resolve_to_field_value(result)
437                    })
438                },
439            )
440            .argument(InputValue::new(
441                "where",
442                TypeRef::named_nn(format!("{}WhereUniqueInput", type_name)),
443            ))
444            .argument(InputValue::new(
445                "input",
446                TypeRef::named_nn(format!("{}UpdateInput", type_name)),
447            )),
448            &collection.directives,
449            Operation::Update,
450        ));
451
452        let coll_for_delete = collection.clone();
453        let client_for_delete = client.clone();
454        let db_for_delete = db.clone();
455        mutation_root = mutation_root.field(Self::apply_directives(
456            Field::new(
457                format!("delete{}", type_name),
458                TypeRef::named_nn("DeleteResult"),
459                move |ctx| {
460                    let coll_def = coll_for_delete.clone();
461                    let client = client_for_delete.clone();
462                    let db = db_for_delete.clone();
463                    FieldFuture::new(async move {
464                        let result = mutation::resolve_delete(ctx, &coll_def, &client, &db).await;
465                        resolve_to_field_value(result)
466                    })
467                },
468            )
469            .argument(InputValue::new(
470                "where",
471                TypeRef::named_nn(format!("{}WhereUniqueInput", type_name)),
472            )),
473            &collection.directives,
474            Operation::Delete,
475        ));
476
477        Ok((builder, query_root, mutation_root))
478    }
479
480    fn apply_directives(
481        field: Field,
482        directive_defs: &crate::schema::definition::FieldDirectives,
483        op: Operation,
484    ) -> Field {
485        let directives = match op {
486            Operation::Get => &directive_defs.get,
487            Operation::List => &directive_defs.list,
488            Operation::Create => &directive_defs.create,
489            Operation::Update => &directive_defs.update,
490            Operation::Delete => &directive_defs.delete,
491        };
492        if directives.is_empty() {
493            return field;
494        }
495        let mut built_field = field;
496        for directive_def in directives {
497            let mut graphql_directive =
498                async_graphql::dynamic::Directive::new(directive_def.name.clone());
499            for (arg_name, arg_value) in &directive_def.args {
500                let graphql_value = async_graphql::Value::from_json(arg_value.clone())
501                    .unwrap_or(async_graphql::Value::Null);
502                graphql_directive = graphql_directive.argument(arg_name.as_str(), graphql_value);
503            }
504            built_field = built_field.directive(graphql_directive);
505        }
506        built_field
507    }
508
509    fn collect_reverse_relations(
510        &self,
511        collection: &CollectionDef,
512    ) -> (Vec<(String, String, String)>, Vec<(String, String, String)>) {
513        let mut to_many = Vec::new();
514        let mut to_one = Vec::new();
515        for other in &self.definition.collections {
516            if other.collection == collection.collection {
517                continue;
518            }
519            for field in &other.fields {
520                if let FieldType::Relation(relation) = &field.field_type {
521                    if relation.collection != collection.collection {
522                        continue;
523                    }
524                    match relation.kind {
525                        RelationKind::OneToMany => {
526                            let name = relation
527                                .reverse_name
528                                .clone()
529                                .unwrap_or_else(|| other.plural_name());
530                            to_many.push((name, other.collection.clone(), field.name.clone()));
531                        }
532                        RelationKind::OneToOne => {
533                            let name = relation
534                                .reverse_name
535                                .clone()
536                                .unwrap_or_else(|| other.singular_name());
537                            to_one.push((name, other.collection.clone(), field.name.clone()));
538                        }
539                        _ => {}
540                    }
541                }
542            }
543        }
544        (to_many, to_one)
545    }
546
547    /// Build scalar + relation fields for CreateInput or UpdateInput.
548    fn build_input_fields(
549        &self,
550        mut input: InputObject,
551        collection: &CollectionDef,
552        collection_map: &HashMap<&str, &CollectionDef>,
553        mutation: &str,
554    ) -> InputObject {
555        for field in &collection.fields {
556            if field.name == "id" || field.name == "_id" {
557                continue;
558            }
559            if let FieldType::Relation(relation) = &field.field_type {
560                let target_type = collection_map
561                    .get(relation.collection.as_str())
562                    .map(|collection| collection.type_name())
563                    .unwrap_or_else(|| relation.collection.clone());
564                let input_name = Self::relation_nested_input_name(relation.kind, &target_type, &collection.type_name(), mutation);
565                input = input.field(InputValue::new(field.graphql_name(), TypeRef::named(&input_name)));
566                continue;
567            }
568            let field_type = scalars::type_ref(&field.field_type);
569            let input_type = if field.required && mutation == "Create" {
570                TypeRef::named_nn(field_type.type_name())
571            } else {
572                field_type
573            };
574            input = input.field(InputValue::new(field.graphql_name(), input_type));
575        }
576        input
577    }
578
579    /// Add reverse to-many and to-one fields to a CreateInput or UpdateInput.
580    fn add_reverse_fields_to_input(
581        &self,
582        mut input: InputObject,
583        reverse_to_many: &[(String, String, String)],
584        reverse_to_one: &[(String, String, String)],
585        collection_map: &HashMap<&str, &CollectionDef>,
586        source_type: &str,
587        mutation: &str,
588    ) -> InputObject {
589        for (reverse_name, target_coll_name, _) in reverse_to_many {
590            if let Some(target_def) = collection_map.get(target_coll_name.as_str()) {
591                let input_name = Self::relation_nested_input_name(
592                    RelationKind::ManyToMany, &target_def.type_name(), source_type, mutation,
593                );
594                input = input.field(InputValue::new(reverse_name.clone(), TypeRef::named(&input_name)));
595            }
596        }
597        for (reverse_name, target_coll_name, _) in reverse_to_one {
598            if let Some(target_def) = collection_map.get(target_coll_name.as_str()) {
599                let input_name = Self::relation_nested_input_name(
600                    RelationKind::OneToOne, &target_def.type_name(), source_type, mutation,
601                );
602                input = input.field(InputValue::new(reverse_name.clone(), TypeRef::named(&input_name)));
603            }
604        }
605        input
606    }
607
608    /// Build the scalar + forward relation fields for a `CreateWithout` or
609    /// `UpdateWithout` input. Skips id/_id and relations pointing back to
610    /// `source_coll`. For `mutation == "Create"`, required scalars are NN.
611    fn build_without_fields(
612        &self,
613        mut without_input: InputObject,
614        target_coll: &CollectionDef,
615        source_coll: &CollectionDef,
616        mutation: &str,
617    ) -> InputObject {
618        for field in &target_coll.fields {
619            if field.name == "id" || field.name == "_id" {
620                continue;
621            }
622            if let FieldType::Relation(relation) = &field.field_type {
623                if relation.collection == source_coll.collection {
624                    continue;
625                }
626                let target_type = self
627                    .definition
628                    .collection_by_name(&relation.collection)
629                    .map(|collection| collection.type_name())
630                    .unwrap_or_else(|| relation.collection.clone());
631                let input_name = Self::relation_nested_input_name(
632                    relation.kind, &target_type, &target_coll.type_name(), mutation,
633                );
634                without_input =
635                    without_input.field(InputValue::new(field.graphql_name(), TypeRef::named(&input_name)));
636                continue;
637            }
638            let field_type = scalars::type_ref(&field.field_type);
639            let input_type = if field.required && mutation == "Create" {
640                TypeRef::named_nn(field_type.type_name())
641            } else {
642                field_type
643            };
644            without_input =
645                without_input.field(InputValue::new(field.graphql_name(), input_type));
646        }
647        without_input
648    }
649
650    /// Ensure a CreateWithout or UpdateWithout input type is registered for
651    /// target_coll with source_coll omitted. Returns the type name.
652    fn ensure_without_type(
653        &mut self,
654        mut builder: AgSchemaBuilder,
655        target_coll: &CollectionDef,
656        source_coll: &CollectionDef,
657        mutation: &str,
658    ) -> (AgSchemaBuilder, String) {
659        let target_type = target_coll.type_name();
660        let source_type = source_coll.type_name();
661        let without_name = match mutation {
662            "Create" => format!("{}CreateWithout{}Input", target_type, source_type),
663            "Update" => format!("{}UpdateWithout{}Input", target_type, source_type),
664            _ => panic!("unknown mutation: {}", mutation),
665        };
666        if !self.registered_nested_inputs.contains(&without_name) {
667            let base = self.build_without_fields(
668                InputObject::new(&without_name), target_coll, source_coll, mutation,
669            );
670            let with_reverse;
671            (builder, with_reverse) = self.add_reverse_fields_to_without(
672                builder, base, target_coll, source_coll,
673            );
674            builder = builder.register(with_reverse);
675            self.registered_nested_inputs.insert(without_name.clone());
676        }
677        (builder, without_name)
678    }
679
680    fn register_nested_inputs_for_field(
681        &mut self,
682        mut builder: AgSchemaBuilder,
683        source_coll: &CollectionDef,
684        field: &crate::schema::definition::FieldDef,
685    ) -> AgSchemaBuilder {
686        let relation = match &field.field_type {
687            FieldType::Relation(relation) => relation,
688            _ => return builder,
689        };
690        let target_def = match self.definition.collection_by_name(&relation.collection) {
691            Some(collection_def) => collection_def,
692            None => return builder,
693        };
694        let target_type = target_def.type_name();
695        let source_type = source_coll.type_name();
696
697        let create_without;
698        (builder, create_without) = self.ensure_without_type(builder, target_def, source_coll, "Create");
699        let update_without;
700        (builder, update_without) = self.ensure_without_type(builder, target_def, source_coll, "Update");
701
702        let is_to_many = matches!(relation.kind, RelationKind::ManyToMany);
703        builder = self.register_one_or_many_inputs(
704            builder, &target_type, &source_type, &create_without, &update_without, is_to_many,
705        );
706
707        builder
708    }
709
710    /// Register CreateNested{One/Many}Without{Source}Input and
711    /// Update{One/Many}Without{Source}NestedInput.
712    fn register_one_or_many_inputs(
713        &mut self,
714        mut builder: AgSchemaBuilder,
715        target_type: &str,
716        source_type: &str,
717        create_without_name: &str,
718        update_without_name: &str,
719        is_to_many: bool,
720    ) -> AgSchemaBuilder {
721        let where_unique = format!("{}WhereUniqueInput", target_type);
722        let (cardinality, create_ref, connect_ref, disconnect_delete_ref) = if is_to_many {
723            ("Many", TypeRef::named_nn_list(create_without_name), TypeRef::named_nn_list(&where_unique), TypeRef::named_nn_list(&where_unique))
724        } else {
725            ("One", TypeRef::named(create_without_name), TypeRef::named(&where_unique), TypeRef::named("Boolean"))
726        };
727
728        let create_name = format!("{}CreateNested{}Without{}Input", target_type, cardinality, source_type);
729        if !self.registered_nested_inputs.contains(&create_name) {
730            let input = InputObject::new(&create_name)
731                .field(InputValue::new("create", create_ref.clone()))
732                .field(InputValue::new("connect", connect_ref.clone()));
733            builder = builder.register(input);
734            self.registered_nested_inputs.insert(create_name);
735        }
736
737        // Build update field ref: to-one is nullable named, to-many needs WithWhere wrapper
738        let update_ref = if is_to_many {
739            let with_where_name = format!("{}UpdateWithWhereUniqueWithout{}Input", target_type, source_type);
740            if !self.registered_nested_inputs.contains(&with_where_name) {
741                let with_where = InputObject::new(&with_where_name)
742                    .field(InputValue::new("where", TypeRef::named_nn(&where_unique)))
743                    .field(InputValue::new("data", TypeRef::named_nn(update_without_name)));
744                builder = builder.register(with_where);
745                self.registered_nested_inputs.insert(with_where_name.clone());
746            }
747            TypeRef::named_nn_list(&with_where_name)
748        } else {
749            TypeRef::named(update_without_name)
750        };
751
752        let update_name = format!("{}Update{}Without{}NestedInput", target_type, cardinality, source_type);
753        if !self.registered_nested_inputs.contains(&update_name) {
754            let input = InputObject::new(&update_name)
755                .field(InputValue::new("create", create_ref))
756                .field(InputValue::new("connect", connect_ref))
757                .field(InputValue::new("disconnect", disconnect_delete_ref.clone()))
758                .field(InputValue::new("delete", disconnect_delete_ref))
759                .field(InputValue::new("update", update_ref));
760            builder = builder.register(input);
761            self.registered_nested_inputs.insert(update_name);
762        }
763
764        builder
765    }
766
767    /// Unified reverse input registration. Used for both OneToMany and OneToOne
768    /// reverse fields. Threads `builder` through for recursive 3-level support.
769    fn register_reverse_inputs(
770        &mut self,
771        mut builder: AgSchemaBuilder,
772        source_coll: &CollectionDef,
773        target_coll: &CollectionDef,
774        kind: RelationKind,
775    ) -> (AgSchemaBuilder, String) {
776        let target_type = target_coll.type_name();
777        let source_type = source_coll.type_name();
778        let is_to_many = matches!(kind, RelationKind::OneToMany);
779
780        let create_without;
781        (builder, create_without) = self.ensure_without_type(builder, target_coll, source_coll, "Create");
782        let update_without;
783        (builder, update_without) = self.ensure_without_type(builder, target_coll, source_coll, "Update");
784
785        builder = self.register_one_or_many_inputs(
786            builder, &target_type, &source_type, &create_without, &update_without, is_to_many,
787        );
788
789        let nested_name = if is_to_many {
790            format!("{}CreateNestedManyWithout{}Input", target_type, source_type)
791        } else {
792            format!("{}CreateNestedOneWithout{}Input", target_type, source_type)
793        };
794
795        (builder, nested_name)
796    }
797
798    /// Add reverse fields (OneToMany and OneToOne) to a CreateWithout or
799    /// UpdateWithout input for 3-level nesting support.
800    fn add_reverse_fields_to_without(
801        &mut self,
802        mut builder: AgSchemaBuilder,
803        mut without_input: InputObject,
804        target_coll: &CollectionDef,
805        source_coll: &CollectionDef,
806    ) -> (AgSchemaBuilder, InputObject) {
807        for other_coll in &self.definition.collections {
808            if other_coll.collection == target_coll.collection {
809                continue;
810            }
811            for other_field in &other_coll.fields {
812                let relation = match &other_field.field_type {
813                    FieldType::Relation(relation) if relation.collection == target_coll.collection => relation,
814                    _ => continue,
815                };
816                // Skip reverse fields originating from the source collection —
817                // they would create a circular type reference back to source.
818                if other_coll.collection == source_coll.collection {
819                    continue;
820                }
821                let (reverse_name, kind) = match relation.kind {
822                    RelationKind::OneToMany => (
823                        relation.reverse_name.clone().unwrap_or_else(|| other_coll.plural_name()),
824                        RelationKind::OneToMany,
825                    ),
826                    RelationKind::OneToOne => (
827                        relation.reverse_name.clone().unwrap_or_else(|| other_coll.singular_name()),
828                        RelationKind::OneToOne,
829                    ),
830                    _ => continue,
831                };
832                let type_name;
833                (builder, type_name) = self.register_reverse_inputs(
834                    builder, target_coll, other_coll, kind,
835                );
836                without_input = without_input.field(InputValue::new(
837                    reverse_name,
838                    TypeRef::named(&type_name),
839                ));
840            }
841        }
842        (builder, without_input)
843    }
844
845    fn relation_nested_input_name(kind: RelationKind, target_type: &str, source_type: &str, mutation: &str) -> String {
846        let cardinality = if matches!(kind, RelationKind::ManyToMany) { "Many" } else { "One" };
847        match mutation {
848            "Create" => format!("{}CreateNested{}Without{}Input", target_type, cardinality, source_type),
849            "Update" => format!("{}Update{}Without{}NestedInput", target_type, cardinality, source_type),
850            _ => panic!("unknown mutation: {}", mutation),
851        }
852    }
853
854    fn register_enum(
855        &mut self,
856        mut builder: AgSchemaBuilder,
857        enum_def: &EnumDef,
858    ) -> AgSchemaBuilder {
859        if self.registered_enums.contains(&enum_def.name) {
860            return builder;
861        }
862        let items: Vec<async_graphql::dynamic::EnumItem> = enum_def
863            .values
864            .iter()
865            .map(|value| async_graphql::dynamic::EnumItem::new(value.clone()))
866            .collect();
867        builder = builder.register(async_graphql::dynamic::Enum::new(
868            enum_def.name.clone(),
869        ).items(items));
870        self.registered_enums.insert(enum_def.name.clone());
871        builder
872    }
873
874    fn register_page_info(&self, builder: AgSchemaBuilder) -> AgSchemaBuilder {
875        let fields = [
876            ("hasNextPage", "Boolean", false),
877            ("hasPreviousPage", "Boolean", false),
878            ("startCursor", "String", false),
879            ("endCursor", "String", false),
880        ];
881        let mut page_info = Object::new("PageInfo");
882        for (field_name, type_name, non_null) in fields {
883            let type_ref = if non_null { TypeRef::named_nn(type_name) } else { TypeRef::named(type_name) };
884            page_info = page_info.field(nested_field(field_name, type_ref));
885        }
886        builder.register(page_info)
887    }
888
889    fn register_delete_result(&self, builder: AgSchemaBuilder) -> AgSchemaBuilder {
890        let mut delete_result = Object::new("DeleteResult");
891        delete_result = delete_result
892            .field(nested_field("success", TypeRef::named_nn("Boolean")))
893            .field(nested_field("deletedId", TypeRef::named_nn("ID")));
894        builder.register(delete_result)
895    }
896
897    fn register_filter_types(&self, builder: AgSchemaBuilder) -> AgSchemaBuilder {
898        builder
899            .register(
900                async_graphql::dynamic::Enum::new("SortDirection")
901                    .item(async_graphql::dynamic::EnumItem::new("ASC"))
902                    .item(async_graphql::dynamic::EnumItem::new("DESC")),
903            )
904            .register(InputObject::new("IdFilter")
905                .field(InputValue::new("eq", TypeRef::named("ID")))
906                .field(InputValue::new("ne", TypeRef::named("ID"))))
907            .register(InputObject::new("StringFilter")
908                .field(InputValue::new("eq", TypeRef::named("String")))
909                .field(InputValue::new("ne", TypeRef::named("String")))
910                .field(InputValue::new("contains", TypeRef::named("String")))
911                .field(InputValue::new("startsWith", TypeRef::named("String")))
912                .field(InputValue::new("endsWith", TypeRef::named("String"))))
913            .register(InputObject::new("IntFilter")
914                .field(InputValue::new("eq", TypeRef::named("Int")))
915                .field(InputValue::new("ne", TypeRef::named("Int")))
916                .field(InputValue::new("gt", TypeRef::named("Int")))
917                .field(InputValue::new("gte", TypeRef::named("Int")))
918                .field(InputValue::new("lt", TypeRef::named("Int")))
919                .field(InputValue::new("lte", TypeRef::named("Int"))))
920            .register(InputObject::new("FloatFilter")
921                .field(InputValue::new("eq", TypeRef::named("Float")))
922                .field(InputValue::new("ne", TypeRef::named("Float")))
923                .field(InputValue::new("gt", TypeRef::named("Float")))
924                .field(InputValue::new("gte", TypeRef::named("Float")))
925                .field(InputValue::new("lt", TypeRef::named("Float")))
926                .field(InputValue::new("lte", TypeRef::named("Float"))))
927            .register(InputObject::new("BooleanFilter")
928                .field(InputValue::new("eq", TypeRef::named("Boolean"))))
929            .register(InputObject::new("DateTimeFilter")
930                .field(InputValue::new("eq", TypeRef::named("DateTime")))
931                .field(InputValue::new("ne", TypeRef::named("DateTime")))
932                .field(InputValue::new("gt", TypeRef::named("DateTime")))
933                .field(InputValue::new("gte", TypeRef::named("DateTime")))
934                .field(InputValue::new("lt", TypeRef::named("DateTime")))
935                .field(InputValue::new("lte", TypeRef::named("DateTime"))))
936    }
937}
938
939fn extract_nested(parent: &async_graphql::Value, field_name: &str) -> Option<async_graphql::Value> {
940    match parent {
941        async_graphql::Value::Object(map) => map.get(field_name).cloned(),
942        _ => None,
943    }
944}
945
946fn filter_type_name(field_type: &FieldType) -> &'static str {
947    match field_type {
948        FieldType::ID => "IdFilter",
949        FieldType::String => "StringFilter",
950        FieldType::Int => "IntFilter",
951        FieldType::Float => "FloatFilter",
952        FieldType::Boolean => "BooleanFilter",
953        FieldType::DateTime => "DateTimeFilter",
954        FieldType::Json => "StringFilter",
955        FieldType::List(_) => "StringFilter",
956        FieldType::Relation(_) => "IdFilter",
957    }
958}
959
960fn nested_field(name: &str, type_ref: TypeRef) -> Field {
961    let field_name = name.to_string();
962    Field::new(name, type_ref, move |ctx| {
963        let name = field_name.clone();
964        FieldFuture::new(async move {
965            let value = ctx
966                .parent_value
967                .as_value()
968                .and_then(|parent| extract_nested(parent, &name));
969            match value {
970                Some(val) => Ok(Some(FieldValue::value(val))),
971                None => Ok(None),
972            }
973        })
974    })
975}
976
977fn resolve_to_field_value(
978    result: Result<Option<serde_json::Value>, GraphQLError>,
979) -> Result<Option<FieldValue<'static>>, async_graphql::Error> {
980    match result {
981        Ok(Some(value)) => json_to_field_value(value).map(Some),
982        Ok(None) => Ok(None),
983        Err(err) => Err(err),
984    }
985    .map_err(|err| err.into_graphql_error())
986}