diff --git a/src/registry/mod.rs b/src/registry/mod.rs index c01d02e2..f4b198d0 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -922,6 +922,19 @@ impl Registry { traverse_type(ctx, &self.types, &mut visible_types, ty.name()); } + for ty in self.types.values() { + if let MetaType::Interface { possible_types, .. } = ty { + if ty.is_visible(ctx) && !visible_types.contains(ty.name()) { + for type_name in possible_types.iter() { + if visible_types.contains(type_name.as_str()) { + traverse_type(ctx, &self.types, &mut visible_types, ty.name()); + break; + } + } + } + } + } + self.types .values() .filter_map(|ty| { diff --git a/tests/interface_exporting.rs b/tests/interface_exporting.rs new file mode 100644 index 00000000..fd313b56 --- /dev/null +++ b/tests/interface_exporting.rs @@ -0,0 +1,203 @@ +use async_graphql::*; +use serde::Deserialize; + +#[derive(SimpleObject)] +struct ObjectA { + id: i32, + title: String, +} + +#[derive(SimpleObject)] +struct ObjectB { + id: i32, + title: String, +} + +#[derive(Interface)] +#[graphql(field(name = "id", type = "&i32"))] +enum ImplicitInterface { + ObjectA(ObjectA), + ObjectB(ObjectB), +} + +#[derive(Interface)] +#[graphql(field(name = "title", type = "String"))] +enum ExplicitInterface { + ObjectA(ObjectA), + ObjectB(ObjectB), +} + +#[derive(Interface)] +#[graphql(visible = false)] +#[graphql(field(name = "title", type = "String"))] +enum InvisibleInterface { + ObjectA(ObjectA), + ObjectB(ObjectB), +} + +#[derive(SimpleObject)] +struct ObjectC { + id: i32, + title: String, +} + +#[derive(Interface)] +#[graphql(field(name = "id", type = "&i32"))] +enum UnreferencedInterface { + ObjectC(ObjectC), +} + + +#[derive(Union)] +enum ObjectUnion { + ObjectA(ObjectA), + ObjectB(ObjectB), +} + +struct Query; + +#[Object] +impl Query { + async fn implicit(&self) -> ObjectUnion { + ObjectA { + id: 33, + title: "haha".to_string(), + }.into() + } + + async fn explicit(&self) -> ExplicitInterface { + ObjectA { + id: 40, + title: "explicit".to_string(), + }.into() + } +} + +fn build_schema() -> Schema { + Schema::build(Query, EmptyMutation, EmptySubscription) + .register_output_type::() + .register_output_type::() + .register_output_type::() + .finish() +} + +#[tokio::test] +pub async fn test_interface_exports_interfaces_on_object_type() { + #[derive(Deserialize)] + struct QueryResponse { + #[serde(rename = "__type")] + ty: TypeResponse, + } + + #[derive(Deserialize)] + struct TypeResponse { + name: String, + kind: String, + interfaces: Vec, + } + + #[derive(Deserialize)] + struct InterfaceResponse { + name: String, + } + + let schema = build_schema(); + + let resp: QueryResponse = from_value( + schema + .execute(r#"{ __type(name: "ObjectA") { name kind interfaces { name }} }"#) + .await + .into_result() + .unwrap() + .data, + ) + .unwrap(); + + assert_eq!(resp.ty.name, "ObjectA"); + assert_eq!(resp.ty.kind, "OBJECT"); + assert!(resp.ty.interfaces.iter().any(|i| i.name == "ExplicitInterface")); + assert!(resp.ty.interfaces.iter().any(|i| i.name == "ImplicitInterface")); + assert!(!resp.ty.interfaces.iter().any(|i| i.name == "InvisibleInterface")); +} + +#[tokio::test] +pub async fn test_interface_exports_explicit_interface_type() { + let schema = build_schema(); + + let data = schema + .execute(r#"{ __type(name: "ExplicitInterface") { name kind } }"#) + .await + .into_result() + .unwrap() + .data; + + assert_eq!( + data, + value!({ + "__type": { + "name": "ExplicitInterface", + "kind": "INTERFACE", + } + }) + ); +} + +#[tokio::test] +pub async fn test_interface_exports_implicit_interface_type() { + let schema = build_schema(); + + let data = schema + .execute(r#"{ __type(name: "ImplicitInterface") { name kind } }"#) + .await + .into_result() + .unwrap() + .data; + + assert_eq!( + data, + value!({ + "__type": { + "name": "ImplicitInterface", + "kind": "INTERFACE", + } + }) + ); +} + +#[tokio::test] +pub async fn test_interface_no_export_invisible_interface_type() { + let schema = build_schema(); + + let data = schema + .execute(r#"{ __type(name: "InvisibleInterface") { name } }"#) + .await + .into_result() + .unwrap() + .data; + + assert_eq!( + data, + value!({ + "__type": null, + }) + ); +} + +#[tokio::test] +pub async fn test_interface_no_export_unreferenced_interface_type() { + let schema = build_schema(); + + let data = schema + .execute(r#"{ __type(name: "UnreferencedInterface") { name } }"#) + .await + .into_result() + .unwrap() + .data; + + assert_eq!( + data, + value!({ + "__type": null, + }) + ); +}