diff --git a/CHANGELOG.md b/CHANGELOG.md index 83cefeb2..62b75048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +- Federation's `_Entity` should not be sent if empty as it's in conflict with [GraphQL Union type validation](https://spec.graphql.org/draft/#sec-Unions.Type-Validation) [#765](https://github.com/async-graphql/async-graphql/pull/765). + ## [3.0.17] 2021-12-16 - Bump poem to `1.2.2`. diff --git a/src/registry/mod.rs b/src/registry/mod.rs index f4b198d0..1309a7fd 100644 --- a/src/registry/mod.rs +++ b/src/registry/mod.rs @@ -512,7 +512,12 @@ impl Registry { }) } - fn create_entity_type(&mut self) { + /// Each type annotated with @key should be added to the _Entity union. + /// If no types are annotated with the key directive, then the _Entity union + /// and Query._entities field should be removed from the schema. + /// + /// [Reference](https://www.apollographql.com/docs/federation/federation-spec/#resolve-requests-for-entities). + fn create_entity_type_and_root_field(&mut self) { let possible_types: IndexSet = self .types .values() @@ -531,16 +536,69 @@ impl Registry { }) .collect(); - self.types.insert( - "_Entity".to_string(), - MetaType::Union { - name: "_Entity".to_string(), - description: None, - possible_types, - visible: None, - rust_typename: "async_graphql::federation::Entity", - }, - ); + if !possible_types.is_empty() { + self.types.insert( + "_Entity".to_string(), + MetaType::Union { + name: "_Entity".to_string(), + description: None, + possible_types, + visible: None, + rust_typename: "async_graphql::federation::Entity", + }, + ); + + let query_root = self.types.get_mut(&self.query_type).unwrap(); + if let MetaType::Object { fields, .. } = query_root { + fields.insert( + "_service".to_string(), + MetaField { + name: "_service".to_string(), + description: None, + args: Default::default(), + ty: "_Service!".to_string(), + deprecation: Default::default(), + cache_control: Default::default(), + external: false, + requires: None, + provides: None, + visible: None, + compute_complexity: None, + }, + ); + + fields.insert( + "_entities".to_string(), + MetaField { + name: "_entities".to_string(), + description: None, + args: { + let mut args = IndexMap::new(); + args.insert( + "representations", + MetaInputValue { + name: "representations", + description: None, + ty: "[_Any!]!".to_string(), + default_value: None, + visible: None, + is_secret: false, + }, + ); + args + }, + ty: "[_Entity]!".to_string(), + deprecation: Default::default(), + cache_control: Default::default(), + external: false, + requires: None, + provides: None, + visible: None, + compute_complexity: None, + }, + ); + } + } } pub(crate) fn create_federation_types(&mut self) { @@ -580,58 +638,7 @@ impl Registry { }, ); - self.create_entity_type(); - - let query_root = self.types.get_mut(&self.query_type).unwrap(); - if let MetaType::Object { fields, .. } = query_root { - fields.insert( - "_service".to_string(), - MetaField { - name: "_service".to_string(), - description: None, - args: Default::default(), - ty: "_Service!".to_string(), - deprecation: Default::default(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - visible: None, - compute_complexity: None, - }, - ); - - fields.insert( - "_entities".to_string(), - MetaField { - name: "_entities".to_string(), - description: None, - args: { - let mut args = IndexMap::new(); - args.insert( - "representations", - MetaInputValue { - name: "representations", - description: None, - ty: "[_Any!]!".to_string(), - default_value: None, - visible: None, - is_secret: false, - }, - ); - args - }, - ty: "[_Entity]!".to_string(), - deprecation: Default::default(), - cache_control: Default::default(), - external: false, - requires: None, - provides: None, - visible: None, - compute_complexity: None, - }, - ); - } + self.create_entity_type_and_root_field(); } pub fn names(&self) -> Vec {