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