diff --git a/apollo-federation/src/merger/merger.rs b/apollo-federation/src/merger/merger.rs index 2736dcfe7b..47ab3a5dc4 100644 --- a/apollo-federation/src/merger/merger.rs +++ b/apollo-federation/src/merger/merger.rs @@ -17,6 +17,8 @@ use apollo_compiler::ast::Type; use apollo_compiler::ast::Value; use apollo_compiler::collections::IndexMap; use apollo_compiler::schema::Component; +use apollo_compiler::schema::ComponentName; +use apollo_compiler::schema::ComponentOrigin; use apollo_compiler::schema::ExtendedType; use indexmap::IndexSet; use itertools::Itertools; @@ -1013,7 +1015,13 @@ impl Merger { } } for implemented_itf in implemented { - dest.insert_implements_interface(&mut self.merged, implemented_itf)?; + dest.insert_implements_interface( + &mut self.merged, + ComponentName { + origin: ComponentOrigin::Definition, + name: implemented_itf.name.clone(), + }, + )?; } Ok(()) } diff --git a/apollo-federation/tests/composition/compose_basic.rs b/apollo-federation/tests/composition/compose_basic.rs index 8064ac0f7f..bf4fa418b5 100644 --- a/apollo-federation/tests/composition/compose_basic.rs +++ b/apollo-federation/tests/composition/compose_basic.rs @@ -53,6 +53,47 @@ fn generates_a_valid_supergraph() { assert_snapshot!(api_schema.schema()); } +/// Ensures that when a type T implements an interface I in both a base definition (Subgraph1) +/// and an extension (Subgraph2), the supergraph SDL emits "type T implements I" on the +/// main type definition line rather than splitting into "type T" + "extend type T implements I". +#[test] +fn implements_on_type_definition_not_extend_type() { + let subgraph_a = ServiceDefinition { + name: "Subgraph1", + type_defs: r#" + type Query { + t: T + } + + interface I { + id: ID! + } + + type T implements I @key(fields: "id") { + id: ID! + } + "#, + }; + + let subgraph_b = ServiceDefinition { + name: "Subgraph2", + type_defs: r#" + interface I { + id: ID! + } + + extend type T implements I @key(fields: "id") { + id: ID! + note: String + } + "#, + }; + + let supergraph = + compose_as_fed2_subgraphs(&[subgraph_a, subgraph_b]).expect("composition should succeed"); + assert_snapshot!(supergraph.schema().schema()); +} + #[test] fn preserves_descriptions() { let subgraph1 = ServiceDefinition { diff --git a/apollo-federation/tests/composition/snapshots/main__composition__compose_basic__implements_on_type_definition_not_extend_type.snap b/apollo-federation/tests/composition/snapshots/main__composition__compose_basic__implements_on_type_definition_not_extend_type.snap new file mode 100644 index 0000000000..feb11d9972 --- /dev/null +++ b/apollo-federation/tests/composition/snapshots/main__composition__compose_basic__implements_on_type_definition_not_extend_type.snap @@ -0,0 +1,67 @@ +--- +source: apollo-federation/tests/composition/compose_basic.rs +expression: supergraph.schema().schema() +--- +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) { + query: Query +} + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @join__graph(name: String!, url: String!) on ENUM_VALUE + +directive @join__type(graph: join__Graph!, key: join__FieldSet, extension: Boolean! = false, resolvable: Boolean! = true, isInterfaceObject: Boolean! = false) repeatable on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT | SCALAR + +directive @join__field(graph: join__Graph, requires: join__FieldSet, provides: join__FieldSet, type: String, external: Boolean, override: String, usedOverridden: Boolean, overrideLabel: String, contextArguments: [join__ContextArgument!]) repeatable on FIELD_DEFINITION | INPUT_FIELD_DEFINITION + +directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + +directive @join__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + +directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + +directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + +enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar link__Import + +enum join__Graph { + SUBGRAPH1 @join__graph(name: "Subgraph1", url: "http://Subgraph1") + SUBGRAPH2 @join__graph(name: "Subgraph2", url: "http://Subgraph2") +} + +scalar join__FieldSet + +scalar join__DirectiveArguments + +scalar join__FieldValue + +input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! +} + +type Query @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + t: T @join__field(graph: SUBGRAPH1) +} + +interface I @join__type(graph: SUBGRAPH1) @join__type(graph: SUBGRAPH2) { + id: ID! +} + +type T implements I @join__implements(graph: SUBGRAPH1, interface: "I") @join__implements(graph: SUBGRAPH2, interface: "I") @join__type(graph: SUBGRAPH1, key: "id") @join__type(graph: SUBGRAPH2, key: "id", extension: true) { + id: ID! + note: String @join__field(graph: SUBGRAPH2) +}