1pub mod object;
7pub mod relationship;
8
9use crate::input::mutation::{is_deletable, is_insertable, is_updatable};
10use crate::schema::object::{to_camel_case, to_pascal_case, TableObjectType};
11use crate::schema::relationship::RelationshipField;
12use postrust_core::schema_cache::{SchemaCache, Table};
13use std::collections::HashMap;
14
15#[derive(Debug, Clone)]
17pub struct SchemaConfig {
18 pub exposed_schemas: Vec<String>,
20 pub enable_mutations: bool,
22 pub enable_subscriptions: bool,
24 pub query_prefix: Option<String>,
26 pub query_suffix: Option<String>,
28 pub use_camel_case: bool,
30 pub max_rows: Option<i64>,
35}
36
37impl Default for SchemaConfig {
38 fn default() -> Self {
39 Self {
40 exposed_schemas: vec!["public".to_string()],
41 enable_mutations: true,
42 enable_subscriptions: false,
43 query_prefix: None,
44 query_suffix: None,
45 use_camel_case: true,
46 max_rows: None,
47 }
48 }
49}
50
51impl SchemaConfig {
52 pub fn new() -> Self {
54 Self::default()
55 }
56
57 pub fn with_schemas(mut self, schemas: Vec<String>) -> Self {
59 self.exposed_schemas = schemas;
60 self
61 }
62
63 pub fn with_mutations(mut self, enable: bool) -> Self {
65 self.enable_mutations = enable;
66 self
67 }
68
69 pub fn with_subscriptions(mut self, enable: bool) -> Self {
71 self.enable_subscriptions = enable;
72 self
73 }
74
75 pub fn is_schema_exposed(&self, schema: &str) -> bool {
77 self.exposed_schemas.iter().any(|s| s == schema)
78 }
79
80 pub fn default_schema(&self) -> &str {
85 self.exposed_schemas
86 .first()
87 .map(|s| s.as_str())
88 .unwrap_or("public")
89 }
90}
91
92fn pk_columns_of(table: &Table) -> Vec<(String, String)> {
97 table
98 .pk_cols
99 .iter()
100 .map(|col_name| {
101 let pg_type = table
102 .get_column(col_name)
103 .map(|c| c.nominal_type.clone())
104 .unwrap_or_else(|| "text".to_string());
105 (col_name.clone(), pg_type)
106 })
107 .collect()
108}
109
110fn base_name_for(table: &Table, config: &SchemaConfig) -> String {
118 if table.schema == config.default_schema() {
119 table.name.clone()
120 } else {
121 format!("{}_{}", table.schema, table.name)
122 }
123}
124
125#[derive(Debug, Clone)]
127pub struct GeneratedSchema {
128 pub object_types: HashMap<String, TableObjectType>,
130 pub query_fields: Vec<QueryField>,
132 pub mutation_fields: Vec<MutationField>,
134 pub relationship_fields: HashMap<String, Vec<RelationshipField>>,
136}
137
138impl GeneratedSchema {
139 pub fn get_object_type(&self, name: &str) -> Option<&TableObjectType> {
141 self.object_types.get(name)
142 }
143
144 pub fn get_query_field(&self, table_name: &str) -> Option<&QueryField> {
146 self.query_fields
147 .iter()
148 .find(|f| f.table_name == table_name)
149 }
150
151 pub fn get_mutation_fields(&self, table_name: &str) -> Vec<&MutationField> {
153 self.mutation_fields
154 .iter()
155 .filter(|f| f.table_name == table_name)
156 .collect()
157 }
158
159 pub fn get_relationship_fields(&self, type_name: &str) -> Option<&Vec<RelationshipField>> {
161 self.relationship_fields.get(type_name)
162 }
163
164 pub fn table_names(&self) -> Vec<&str> {
166 self.object_types
167 .values()
168 .map(|t| t.table.name.as_str())
169 .collect()
170 }
171
172 pub fn type_names(&self) -> Vec<&str> {
174 self.object_types.keys().map(|s| s.as_str()).collect()
175 }
176}
177
178#[derive(Debug, Clone)]
180pub struct QueryField {
181 pub name: String,
183 pub table_name: String,
185 pub schema_name: String,
187 pub type_name: String,
189 pub return_type: String,
191 pub is_list: bool,
193 pub is_by_pk: bool,
195 pub pk_columns: Vec<(String, String)>,
201 pub description: Option<String>,
203}
204
205impl QueryField {
206 pub fn list(table: &Table, config: &SchemaConfig) -> Self {
208 Self::list_named(table, config, &table.name)
209 }
210
211 pub fn list_named(table: &Table, config: &SchemaConfig, base_name: &str) -> Self {
213 let type_name = to_pascal_case(base_name);
214 let field_name = if config.use_camel_case {
215 to_camel_case(base_name)
216 } else {
217 base_name.to_string()
218 };
219
220 let name = match (&config.query_prefix, &config.query_suffix) {
221 (Some(prefix), None) => format!("{}{}", prefix, to_pascal_case(&field_name)),
222 (None, Some(suffix)) => format!("{}{}", field_name, suffix),
223 (Some(prefix), Some(suffix)) => {
224 format!("{}{}{}", prefix, to_pascal_case(&field_name), suffix)
225 }
226 (None, None) => field_name,
227 };
228
229 Self {
230 name,
231 table_name: table.name.clone(),
232 schema_name: table.schema.clone(),
233 type_name: type_name.clone(),
234 return_type: format!("[{}!]!", type_name),
235 is_list: true,
236 is_by_pk: false,
237 pk_columns: Vec::new(),
238 description: Some(format!("Query {} records", table.name)),
239 }
240 }
241
242 pub fn by_pk(table: &Table, config: &SchemaConfig) -> Option<Self> {
244 Self::by_pk_named(table, config, &table.name)
245 }
246
247 pub fn by_pk_named(table: &Table, config: &SchemaConfig, base_name: &str) -> Option<Self> {
249 if table.pk_cols.is_empty() {
250 return None;
251 }
252
253 let type_name = to_pascal_case(base_name);
254 let singular = singularize(base_name);
255 let field_name = if config.use_camel_case {
256 format!("{}ByPk", to_camel_case(&singular))
257 } else {
258 format!("{}_by_pk", singular)
259 };
260
261 let pk_columns = pk_columns_of(table);
264
265 Some(Self {
266 name: field_name,
267 table_name: table.name.clone(),
268 schema_name: table.schema.clone(),
269 type_name: type_name.clone(),
270 return_type: type_name,
271 is_list: false,
272 is_by_pk: true,
273 pk_columns,
274 description: Some(format!("Get a single {} by primary key", singular)),
275 })
276 }
277}
278
279#[derive(Debug, Clone)]
281pub struct MutationField {
282 pub name: String,
284 pub table_name: String,
286 pub schema_name: String,
288 pub mutation_type: MutationType,
290 pub pk_columns: Vec<(String, String)>,
295 pub return_type: String,
297 pub description: Option<String>,
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq)]
303pub enum MutationType {
304 Insert,
306 InsertOne,
308 Update,
310 UpdateByPk,
312 Delete,
314 DeleteByPk,
316}
317
318impl MutationField {
319 pub fn insert_fields(table: &Table, config: &SchemaConfig) -> Vec<Self> {
321 Self::insert_fields_named(table, config, &table.name)
322 }
323
324 pub fn insert_fields_named(table: &Table, config: &SchemaConfig, base_name: &str) -> Vec<Self> {
327 if !is_insertable(table) {
328 return vec![];
329 }
330
331 let type_name = to_pascal_case(base_name);
332 let singular = singularize(base_name);
333
334 let mut fields = vec![];
335
336 let name = if config.use_camel_case {
338 format!("insert{}", to_pascal_case(base_name))
339 } else {
340 format!("insert_{}", base_name)
341 };
342 fields.push(Self {
343 name,
344 table_name: table.name.clone(),
345 schema_name: table.schema.clone(),
346 mutation_type: MutationType::Insert,
347 pk_columns: Vec::new(),
348 return_type: format!("[{}!]!", type_name),
349 description: Some(format!("Insert multiple {} records", table.name)),
350 });
351
352 let name = if config.use_camel_case {
354 format!("insert{}One", to_pascal_case(&singular))
355 } else {
356 format!("insert_{}_one", singular)
357 };
358 fields.push(Self {
359 name,
360 table_name: table.name.clone(),
361 schema_name: table.schema.clone(),
362 mutation_type: MutationType::InsertOne,
363 pk_columns: Vec::new(),
364 return_type: type_name.clone(),
365 description: Some(format!("Insert a single {} record", singular)),
366 });
367
368 fields
369 }
370
371 pub fn update_fields(table: &Table, config: &SchemaConfig) -> Vec<Self> {
373 Self::update_fields_named(table, config, &table.name)
374 }
375
376 pub fn update_fields_named(table: &Table, config: &SchemaConfig, base_name: &str) -> Vec<Self> {
379 if !is_updatable(table) {
380 return vec![];
381 }
382
383 let type_name = to_pascal_case(base_name);
384 let singular = singularize(base_name);
385
386 let mut fields = vec![];
387
388 let name = if config.use_camel_case {
390 format!("update{}", to_pascal_case(base_name))
391 } else {
392 format!("update_{}", base_name)
393 };
394 fields.push(Self {
395 name,
396 table_name: table.name.clone(),
397 schema_name: table.schema.clone(),
398 mutation_type: MutationType::Update,
399 pk_columns: Vec::new(),
400 return_type: format!("[{}!]!", type_name),
401 description: Some(format!("Update {} records", table.name)),
402 });
403
404 if !table.pk_cols.is_empty() {
406 let name = if config.use_camel_case {
407 format!("update{}ByPk", to_pascal_case(&singular))
408 } else {
409 format!("update_{}_by_pk", singular)
410 };
411 fields.push(Self {
412 name,
413 table_name: table.name.clone(),
414 schema_name: table.schema.clone(),
415 mutation_type: MutationType::UpdateByPk,
416 pk_columns: pk_columns_of(table),
417 return_type: type_name,
418 description: Some(format!("Update a single {} by primary key", singular)),
419 });
420 }
421
422 fields
423 }
424
425 pub fn delete_fields(table: &Table, config: &SchemaConfig) -> Vec<Self> {
427 Self::delete_fields_named(table, config, &table.name)
428 }
429
430 pub fn delete_fields_named(table: &Table, config: &SchemaConfig, base_name: &str) -> Vec<Self> {
433 if !is_deletable(table) {
434 return vec![];
435 }
436
437 let type_name = to_pascal_case(base_name);
438 let singular = singularize(base_name);
439
440 let mut fields = vec![];
441
442 let name = if config.use_camel_case {
444 format!("delete{}", to_pascal_case(base_name))
445 } else {
446 format!("delete_{}", base_name)
447 };
448 fields.push(Self {
449 name,
450 table_name: table.name.clone(),
451 schema_name: table.schema.clone(),
452 mutation_type: MutationType::Delete,
453 pk_columns: Vec::new(),
454 return_type: format!("[{}!]!", type_name),
455 description: Some(format!("Delete {} records", table.name)),
456 });
457
458 if !table.pk_cols.is_empty() {
460 let name = if config.use_camel_case {
461 format!("delete{}ByPk", to_pascal_case(&singular))
462 } else {
463 format!("delete_{}_by_pk", singular)
464 };
465 fields.push(Self {
466 name,
467 table_name: table.name.clone(),
468 schema_name: table.schema.clone(),
469 mutation_type: MutationType::DeleteByPk,
470 pk_columns: pk_columns_of(table),
471 return_type: type_name,
472 description: Some(format!("Delete a single {} by primary key", singular)),
473 });
474 }
475
476 fields
477 }
478}
479
480pub fn build_schema(schema_cache: &SchemaCache, config: &SchemaConfig) -> GeneratedSchema {
482 let mut object_types = HashMap::new();
483 let mut query_fields = Vec::new();
484 let mut mutation_fields = Vec::new();
485 let mut relationship_fields = HashMap::new();
486
487 let mut tables: Vec<&Table> = schema_cache
490 .tables
491 .values()
492 .filter(|table| config.is_schema_exposed(&table.schema))
493 .collect();
494 tables.sort_by(|a, b| (&a.schema, &a.name).cmp(&(&b.schema, &b.name)));
495
496 let mut used_base_names: HashMap<String, u32> = HashMap::new();
501 let mut base_names: HashMap<(String, String), String> = HashMap::new();
504
505 for table in &tables {
506 let preferred = base_name_for(table, config);
507 let base_name = match used_base_names.get_mut(&preferred) {
508 None => {
509 used_base_names.insert(preferred.clone(), 1);
510 preferred
511 }
512 Some(count) => {
513 *count += 1;
514 let disambiguated = format!("{}_{}", preferred, count);
515 tracing::warn!(
516 "GraphQL name collision: {}.{} would generate the same names as \
517 an earlier table; exposing it as \"{}\" instead",
518 table.schema,
519 table.name,
520 disambiguated
521 );
522 disambiguated
523 }
524 };
525
526 base_names.insert(
527 (table.schema.clone(), table.name.clone()),
528 base_name.clone(),
529 );
530 }
531
532 for table in tables {
533 let base_name = base_names
534 .get(&(table.schema.clone(), table.name.clone()))
535 .expect("every visited table has a resolved base name")
536 .clone();
537
538 let obj_type = TableObjectType::from_table_named(table, &base_name);
540 let type_name = obj_type.name.clone();
541
542 query_fields.push(QueryField::list_named(table, config, &base_name));
544 if let Some(by_pk) = QueryField::by_pk_named(table, config, &base_name) {
545 query_fields.push(by_pk);
546 }
547
548 if config.enable_mutations {
550 mutation_fields.extend(MutationField::insert_fields_named(
551 table, config, &base_name,
552 ));
553 mutation_fields.extend(MutationField::update_fields_named(
554 table, config, &base_name,
555 ));
556 mutation_fields.extend(MutationField::delete_fields_named(
557 table, config, &base_name,
558 ));
559 }
560
561 let rels: Vec<RelationshipField> = schema_cache
563 .get_relationships(&table.qualified_identifier(), &table.schema)
564 .map(|relationships| {
565 relationships
566 .iter()
567 .filter_map(|rel| {
568 let foreign = rel.foreign_table();
571 let target_base =
572 base_names.get(&(foreign.schema.clone(), foreign.name.clone()))?;
573 Some(RelationshipField::from_relationship_named(rel, target_base))
574 })
575 .collect()
576 })
577 .unwrap_or_default();
578
579 if !rels.is_empty() {
580 relationship_fields.insert(type_name.clone(), rels);
581 }
582
583 object_types.insert(type_name, obj_type);
584 }
585
586 GeneratedSchema {
587 object_types,
588 query_fields,
589 mutation_fields,
590 relationship_fields,
591 }
592}
593
594fn singularize(s: &str) -> String {
596 if let Some(stem) = s.strip_suffix("ies") {
597 format!("{}y", stem)
598 } else if s.ends_with("ses") || s.ends_with("xes") || s.ends_with("ches") || s.ends_with("shes")
599 {
600 s.strip_suffix("es").unwrap_or(s).to_string()
601 } else if s.ends_with('s') && !s.ends_with("ss") {
602 s.strip_suffix('s').unwrap_or(s).to_string()
603 } else {
604 s.to_string()
605 }
606}
607
608#[cfg(test)]
609mod tests {
610 use super::*;
611 use indexmap::IndexMap;
612 use postrust_core::schema_cache::Column;
613 use pretty_assertions::assert_eq;
614
615 fn create_test_table(name: &str, insertable: bool, updatable: bool, deletable: bool) -> Table {
616 let mut columns = IndexMap::new();
617 columns.insert(
618 "id".into(),
619 Column {
620 name: "id".into(),
621 description: None,
622 nullable: false,
623 data_type: "integer".into(),
624 nominal_type: "int4".into(),
625 max_len: None,
626 default: Some("nextval('id_seq')".into()),
627 enum_values: vec![],
628 is_pk: true,
629 position: 1,
630 },
631 );
632 columns.insert(
633 "name".into(),
634 Column {
635 name: "name".into(),
636 description: None,
637 nullable: false,
638 data_type: "text".into(),
639 nominal_type: "text".into(),
640 max_len: None,
641 default: None,
642 enum_values: vec![],
643 is_pk: false,
644 position: 2,
645 },
646 );
647
648 Table {
649 schema: "public".into(),
650 name: name.into(),
651 description: None,
652 is_view: false,
653 insertable,
654 updatable,
655 deletable,
656 pk_cols: vec!["id".into()],
657 columns,
658 }
659 }
660
661 fn create_test_schema_cache() -> SchemaCache {
662 use std::collections::{HashMap, HashSet};
663
664 let mut tables = HashMap::new();
665
666 let users = create_test_table("users", true, true, true);
667 let posts = create_test_table("posts", true, true, true);
668 let comments = create_test_table("comments", true, false, false);
669
670 tables.insert(users.qualified_identifier(), users);
671 tables.insert(posts.qualified_identifier(), posts);
672 tables.insert(comments.qualified_identifier(), comments);
673
674 SchemaCache {
675 tables,
676 relationships: HashMap::new(),
677 routines: HashMap::new(),
678 timezones: HashSet::new(),
679 pg_version: 150000,
680 }
681 }
682
683 #[test]
688 fn test_schema_config_default() {
689 let config = SchemaConfig::default();
690 assert!(config.is_schema_exposed("public"));
691 assert!(!config.is_schema_exposed("private"));
692 assert!(config.enable_mutations);
693 assert!(!config.enable_subscriptions);
694 }
695
696 #[test]
697 fn test_schema_config_with_schemas() {
698 let config =
699 SchemaConfig::new().with_schemas(vec!["api".to_string(), "public".to_string()]);
700 assert!(config.is_schema_exposed("api"));
701 assert!(config.is_schema_exposed("public"));
702 assert!(!config.is_schema_exposed("private"));
703 }
704
705 #[test]
706 fn test_schema_config_mutations_disabled() {
707 let config = SchemaConfig::new().with_mutations(false);
708 assert!(!config.enable_mutations);
709 }
710
711 #[test]
716 fn test_query_field_list() {
717 let table = create_test_table("users", true, true, true);
718 let config = SchemaConfig::default();
719 let field = QueryField::list(&table, &config);
720
721 assert_eq!(field.name, "users");
722 assert_eq!(field.return_type, "[Users!]!");
723 assert!(field.is_list);
724 assert!(!field.is_by_pk);
725 }
726
727 #[test]
728 fn test_query_field_list_with_prefix() {
729 let table = create_test_table("users", true, true, true);
730 let config = SchemaConfig {
731 query_prefix: Some("all".to_string()),
732 ..Default::default()
733 };
734 let field = QueryField::list(&table, &config);
735
736 assert_eq!(field.name, "allUsers");
737 }
738
739 #[test]
740 fn test_query_field_list_with_suffix() {
741 let table = create_test_table("users", true, true, true);
742 let config = SchemaConfig {
743 query_suffix: Some("Collection".to_string()),
744 ..Default::default()
745 };
746 let field = QueryField::list(&table, &config);
747
748 assert_eq!(field.name, "usersCollection");
749 }
750
751 #[test]
752 fn test_query_field_by_pk() {
753 let table = create_test_table("users", true, true, true);
754 let config = SchemaConfig::default();
755 let field = QueryField::by_pk(&table, &config).unwrap();
756
757 assert_eq!(field.name, "userByPk");
758 assert_eq!(field.return_type, "Users");
759 assert!(!field.is_list);
760 assert!(field.is_by_pk);
761 }
762
763 #[test]
764 fn test_query_field_by_pk_no_pk() {
765 let mut table = create_test_table("users", true, true, true);
766 table.pk_cols = vec![];
767 let config = SchemaConfig::default();
768 let field = QueryField::by_pk(&table, &config);
769
770 assert!(field.is_none());
771 }
772
773 #[test]
778 fn test_mutation_field_insert() {
779 let table = create_test_table("users", true, true, true);
780 let config = SchemaConfig::default();
781 let fields = MutationField::insert_fields(&table, &config);
782
783 assert_eq!(fields.len(), 2);
784 assert_eq!(fields[0].name, "insertUsers");
785 assert_eq!(fields[0].mutation_type, MutationType::Insert);
786 assert_eq!(fields[1].name, "insertUserOne");
787 assert_eq!(fields[1].mutation_type, MutationType::InsertOne);
788 }
789
790 #[test]
791 fn test_mutation_field_insert_not_insertable() {
792 let table = create_test_table("users", false, true, true);
793 let config = SchemaConfig::default();
794 let fields = MutationField::insert_fields(&table, &config);
795
796 assert!(fields.is_empty());
797 }
798
799 #[test]
800 fn test_mutation_field_update() {
801 let table = create_test_table("users", true, true, true);
802 let config = SchemaConfig::default();
803 let fields = MutationField::update_fields(&table, &config);
804
805 assert_eq!(fields.len(), 2);
806 assert_eq!(fields[0].name, "updateUsers");
807 assert_eq!(fields[0].mutation_type, MutationType::Update);
808 assert_eq!(fields[1].name, "updateUserByPk");
809 assert_eq!(fields[1].mutation_type, MutationType::UpdateByPk);
810 }
811
812 #[test]
813 fn test_mutation_field_update_not_updatable() {
814 let table = create_test_table("users", true, false, true);
815 let config = SchemaConfig::default();
816 let fields = MutationField::update_fields(&table, &config);
817
818 assert!(fields.is_empty());
819 }
820
821 #[test]
822 fn test_mutation_field_delete() {
823 let table = create_test_table("users", true, true, true);
824 let config = SchemaConfig::default();
825 let fields = MutationField::delete_fields(&table, &config);
826
827 assert_eq!(fields.len(), 2);
828 assert_eq!(fields[0].name, "deleteUsers");
829 assert_eq!(fields[0].mutation_type, MutationType::Delete);
830 assert_eq!(fields[1].name, "deleteUserByPk");
831 assert_eq!(fields[1].mutation_type, MutationType::DeleteByPk);
832 }
833
834 #[test]
835 fn test_mutation_field_delete_not_deletable() {
836 let table = create_test_table("users", true, true, false);
837 let config = SchemaConfig::default();
838 let fields = MutationField::delete_fields(&table, &config);
839
840 assert!(fields.is_empty());
841 }
842
843 #[test]
848 fn test_singularize() {
849 assert_eq!(singularize("users"), "user");
850 assert_eq!(singularize("posts"), "post");
851 assert_eq!(singularize("categories"), "category");
852 assert_eq!(singularize("boxes"), "box");
853 assert_eq!(singularize("matches"), "match");
854 assert_eq!(singularize("class"), "class");
855 }
856
857 #[test]
862 fn test_build_schema_object_types() {
863 let cache = create_test_schema_cache();
864 let config = SchemaConfig::default();
865 let schema = build_schema(&cache, &config);
866
867 assert_eq!(schema.object_types.len(), 3);
868 assert!(schema.get_object_type("Users").is_some());
869 assert!(schema.get_object_type("Posts").is_some());
870 assert!(schema.get_object_type("Comments").is_some());
871 }
872
873 #[test]
874 fn test_build_schema_query_fields() {
875 let cache = create_test_schema_cache();
876 let config = SchemaConfig::default();
877 let schema = build_schema(&cache, &config);
878
879 assert_eq!(schema.query_fields.len(), 6);
881
882 let users_field = schema.get_query_field("users").unwrap();
884 assert_eq!(users_field.name, "users");
885 assert!(users_field.is_list);
886 }
887
888 #[test]
889 fn test_build_schema_mutation_fields() {
890 let cache = create_test_schema_cache();
891 let config = SchemaConfig::default();
892 let schema = build_schema(&cache, &config);
893
894 assert_eq!(schema.mutation_fields.len(), 14);
899
900 let users_mutations = schema.get_mutation_fields("users");
901 assert_eq!(users_mutations.len(), 6);
902 }
903
904 #[test]
905 fn test_build_schema_mutations_disabled() {
906 let cache = create_test_schema_cache();
907 let config = SchemaConfig::new().with_mutations(false);
908 let schema = build_schema(&cache, &config);
909
910 assert!(schema.mutation_fields.is_empty());
911 }
912
913 #[test]
914 fn test_build_schema_table_names() {
915 let cache = create_test_schema_cache();
916 let config = SchemaConfig::default();
917 let schema = build_schema(&cache, &config);
918
919 let names = schema.table_names();
920 assert_eq!(names.len(), 3);
921 assert!(names.contains(&"users"));
922 assert!(names.contains(&"posts"));
923 assert!(names.contains(&"comments"));
924 }
925
926 #[test]
927 fn test_build_schema_type_names() {
928 let cache = create_test_schema_cache();
929 let config = SchemaConfig::default();
930 let schema = build_schema(&cache, &config);
931
932 let names = schema.type_names();
933 assert_eq!(names.len(), 3);
934 assert!(names.contains(&"Users"));
935 assert!(names.contains(&"Posts"));
936 assert!(names.contains(&"Comments"));
937 }
938
939 #[test]
940 fn test_build_schema_exposed_schemas() {
941 let mut cache = create_test_schema_cache();
942
943 let private_table = Table {
945 schema: "private".into(),
946 name: "secrets".into(),
947 description: None,
948 is_view: false,
949 insertable: true,
950 updatable: true,
951 deletable: true,
952 pk_cols: vec!["id".into()],
953 columns: indexmap::IndexMap::new(),
954 };
955 cache
956 .tables
957 .insert(private_table.qualified_identifier(), private_table);
958
959 let config = SchemaConfig::default(); let schema = build_schema(&cache, &config);
961
962 assert_eq!(schema.object_types.len(), 3);
964 assert!(schema.get_object_type("Secrets").is_none());
965 }
966
967 #[test]
972 fn test_generated_schema_get_object_type() {
973 let cache = create_test_schema_cache();
974 let config = SchemaConfig::default();
975 let schema = build_schema(&cache, &config);
976
977 let users = schema.get_object_type("Users").unwrap();
978 assert_eq!(users.table.name, "users");
979 }
980
981 #[test]
982 fn test_generated_schema_get_query_field() {
983 let cache = create_test_schema_cache();
984 let config = SchemaConfig::default();
985 let schema = build_schema(&cache, &config);
986
987 let field = schema.get_query_field("posts").unwrap();
988 assert_eq!(field.table_name, "posts");
989 }
990
991 #[test]
992 fn test_generated_schema_get_mutation_fields() {
993 let cache = create_test_schema_cache();
994 let config = SchemaConfig::default();
995 let schema = build_schema(&cache, &config);
996
997 let fields = schema.get_mutation_fields("comments");
998 assert_eq!(fields.len(), 2); }
1001}