From dd548bb046b7c9fc2017350532e4477838bd1b00 Mon Sep 17 00:00:00 2001 From: Edward Rudd Date: Sat, 4 Dec 2021 13:39:36 -0500 Subject: [PATCH] correct adding implicit interfaces to the schema introspection If an interface is added manually (as it is not directly referenced by any type) it was not showing up in the introspection. Now it will be exposed if it is visible and references already accessible types --- src/registry/mod.rs | 13 +++ tests/interface_exporting.rs | 203 +++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 tests/interface_exporting.rs 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, + }) + ); +}