diff --git a/.changesets/fix_lb_carryover_input_types.md b/.changesets/fix_lb_carryover_input_types.md new file mode 100644 index 0000000000..22529b65de --- /dev/null +++ b/.changesets/fix_lb_carryover_input_types.md @@ -0,0 +1,7 @@ +### Fix directive inputs with `@composeDirective` and Connectors ([PR #7383](https://github.com/apollographql/router/pull/7383)) + +Prior to this fix, any time a directive added with `@composeDirective` has its own input types (custom scalars, enums, input types) and a Connector is used, those types would be lost and the supergraph would fail to compose. + + + +By [@lennyburdette](https://github.com/lennyburdette) in https://github.com/apollographql/router/pull/7383 \ No newline at end of file diff --git a/apollo-federation/src/sources/connect/expand/carryover.rs b/apollo-federation/src/sources/connect/expand/carryover.rs index cf28bd36eb..fc3e34a4bb 100644 --- a/apollo-federation/src/sources/connect/expand/carryover.rs +++ b/apollo-federation/src/sources/connect/expand/carryover.rs @@ -1,3 +1,5 @@ +mod inputs; + use apollo_compiler::Name; use apollo_compiler::Node; use apollo_compiler::ast::Argument; @@ -5,6 +7,8 @@ use apollo_compiler::ast::Directive; use apollo_compiler::ast::Value; use apollo_compiler::collections::HashSet; use apollo_compiler::name; +use inputs::copy_input_types; +use multimap::MultiMap; use crate::error::FederationError; use crate::link::DEFAULT_LINK_NAME; @@ -45,6 +49,7 @@ pub(super) fn carryover_directives( from: &FederationSchema, to: &mut FederationSchema, specs: impl Iterator, + subgraph_name_replacements: &MultiMap<&str, String>, ) -> Result<(), FederationError> { let Some(metadata) = from.metadata() else { return Ok(()); @@ -61,6 +66,10 @@ pub(super) fn carryover_directives( SchemaDefinitionPosition.insert_directive(to, link.to_directive_application().into())?; } + // before copying over directive definitions, we need to ensure we copy over + // any input types (scalars, enums, input objects) they use + copy_input_types(from, to, subgraph_name_replacements)?; + // @inaccessible if let Some(link) = metadata.for_identity(&Identity::inaccessible_identity()) { @@ -136,21 +145,6 @@ pub(super) fn carryover_directives( SchemaDefinitionPosition .insert_directive(to, link.to_directive_application().into())?; - let scalar_type_pos = ScalarTypeDefinitionPosition { - type_name: link.type_name_in_schema(&name!(Scope)), - }; - - // The scalar might already exist if a subgraph defined it - if scalar_type_pos.get(to.schema()).is_err() { - scalar_type_pos - .get(from.schema()) - .map_err(From::from) - .and_then(|def| { - scalar_type_pos.pre_insert(to)?; - scalar_type_pos.insert(to, def.clone()) - })?; - } - copy_directive_definition(from, to, directive_name.clone())?; } referencers.copy_directives(from, to, &directive_name) @@ -171,21 +165,6 @@ pub(super) fn carryover_directives( SchemaDefinitionPosition .insert_directive(to, link.to_directive_application().into())?; - let scalar_type_pos = ScalarTypeDefinitionPosition { - type_name: link.type_name_in_schema(&name!(Policy)), - }; - - // The scalar might already exist if a subgraph defined it - if scalar_type_pos.get(to.schema()).is_err() { - scalar_type_pos - .get(from.schema()) - .map_err(From::from) - .and_then(|def| { - scalar_type_pos.pre_insert(to)?; - scalar_type_pos.insert(to, def.clone()) - })?; - } - copy_directive_definition(from, to, directive_name.clone())?; } referencers.copy_directives(from, to, &directive_name) @@ -683,6 +662,7 @@ mod tests { &supergraph_schema, &mut schema, [ConnectSpec::V0_1].into_iter(), + &Default::default(), ) .expect("carryover failed"); assert_snapshot!(schema.schema().serialize().to_string()); diff --git a/apollo-federation/src/sources/connect/expand/carryover/inputs.rs b/apollo-federation/src/sources/connect/expand/carryover/inputs.rs new file mode 100644 index 0000000000..5ad5f15dea --- /dev/null +++ b/apollo-federation/src/sources/connect/expand/carryover/inputs.rs @@ -0,0 +1,323 @@ +use apollo_compiler::Name; +use apollo_compiler::Node; +use apollo_compiler::ast; +use apollo_compiler::ast::Value; +use apollo_compiler::collections::HashMap; +use apollo_compiler::name; +use apollo_compiler::schema::DirectiveList; +use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::ExtendedType; +use apollo_compiler::schema::InputObjectType; +use apollo_compiler::schema::ScalarType; +use itertools::Itertools; +use multimap::MultiMap; + +use crate::error::FederationError; +use crate::schema::FederationSchema; +use crate::schema::position::EnumTypeDefinitionPosition; +use crate::schema::position::InputObjectTypeDefinitionPosition; +use crate::schema::position::ScalarTypeDefinitionPosition; + +/// merge.rs doesn't have any logic for `@composeDirective` directives, so we +/// need to carry those directives AND their associated input types over into +/// the new supergraph. +/// +/// However, we can't just copy the definitions as-is, because their join__* +/// directives may reference subgraphs that no longer exist (were replaced by +/// "expanded" subgraphs/connectors). Each time we encounter a join__* directive +/// with a `graph:` argument referring to a missing subgraph, we'll need to +/// replace it with **one or more** new directives, one for each "expanded" +/// subgraph. +pub(super) fn copy_input_types( + from: &FederationSchema, + to: &mut FederationSchema, + subgraph_name_replacements: &MultiMap<&str, String>, +) -> Result<(), FederationError> { + let from_join_graph_enum = from + .schema() + .get_enum(&name!(join__Graph)) + .ok_or_else(|| FederationError::internal("Cannot find join__graph enum"))?; + let to_join_graph_enum = to + .schema() + .get_enum(&name!(join__Graph)) + .ok_or_else(|| FederationError::internal("Cannot find join__graph enum"))?; + let subgraph_enum_replacements = subgraph_replacements( + from_join_graph_enum, + to_join_graph_enum, + subgraph_name_replacements, + ) + .map_err(|e| FederationError::internal(format!("Failed to get subgraph replacements: {e}")))?; + + for (name, ty) in &from.schema().types { + if to.schema().types.contains_key(name) { + continue; + } + match ty { + ExtendedType::Scalar(node) => { + let pos = ScalarTypeDefinitionPosition { + type_name: node.name.clone(), + }; + let node = + strip_invalid_join_directives_from_scalar(node, &subgraph_enum_replacements); + pos.pre_insert(to).ok(); + pos.insert(to, node).ok(); + } + ExtendedType::Enum(node) => { + let pos = EnumTypeDefinitionPosition { + type_name: node.name.clone(), + }; + let node = + strip_invalid_join_directives_from_enum(node, &subgraph_enum_replacements); + pos.pre_insert(to).ok(); + pos.insert(to, node).ok(); + } + ExtendedType::InputObject(node) => { + let pos = InputObjectTypeDefinitionPosition { + type_name: node.name.clone(), + }; + let node = strip_invalid_join_directives_from_input_type( + node, + &subgraph_enum_replacements, + ); + pos.pre_insert(to).ok(); + pos.insert(to, node).ok(); + } + _ => {} + } + } + + Ok(()) +} + +/// Given an original join__Graph enum: +/// ```graphql +/// enum join__Graph { +/// REGULAR_SUBGRAPH @join__graph(name: "regular-subgraph") +/// CONNECTORS_SUBGRAPH @join__graph(name: "connectors-subgraph") +/// } +/// ``` +/// +/// and a new join__Graph enum: +/// ```graphql +/// enum join__Graph { +/// REGULAR_SUBGRAPH @join__graph(name: "regular-subgraph") +/// CONNECTORS_SUBGRAPH_QUERY_USER_0 @join__graph(name: "connectors-subgraph_Query_user_0") +/// CONNECTORS_SUBGRAPH_QUERY_USERS_0 @join__graph(name: "connectors-subgraph_Query_users_0") +/// } +/// ``` +/// +/// and a map of original subgraph names to new subgraph names: +/// ``` +/// { +/// "connectors-subgraph" => vec!["connectors-subgraph_Query_user_0", "connectors-subgraph_Query_users_0"] +/// } +/// ``` +/// +/// Return a map of enum value replacements: +/// ``` +/// { +/// "CONNECTORS_SUBGRAPH" => vec!["CONNECTORS_SUBGRAPH_QUERY_USER_0", "CONNECTORS_SUBGRAPH_QUERY_USERS_0"], +/// } +/// ``` +fn subgraph_replacements( + from_join_graph_enum: &EnumType, + to_join_graph_enum: &EnumType, + replaced_subgraph_names: &MultiMap<&str, String>, +) -> Result, String> { + let mut replacements = MultiMap::new(); + + fn subgraph_names_to_enum_values(enum_type: &EnumType) -> Result, &str> { + enum_type + .values + .iter() + .map(|(name, value)| { + value + .directives + .iter() + .find(|d| d.name == name!(join__graph)) + .and_then(|d| { + d.arguments + .iter() + .find(|a| a.name == name!(name)) + .and_then(|a| a.value.as_str()) + }) + .ok_or("no name argument on join__graph") + .map(|new_subgraph_name| (new_subgraph_name, name)) + }) + .try_collect() + } + + let new_subgraph_names_to_enum_values = subgraph_names_to_enum_values(to_join_graph_enum)?; + + let original_subgraph_names_to_enum_values = + subgraph_names_to_enum_values(from_join_graph_enum)?; + + for (original_subgraph_name, new_subgraph_names) in replaced_subgraph_names.iter_all() { + if let Some(original_enum_value) = original_subgraph_names_to_enum_values + .get(original_subgraph_name) + .cloned() + { + for new_subgraph_name in new_subgraph_names { + if let Some(new_enum_value) = new_subgraph_names_to_enum_values + .get(new_subgraph_name.as_str()) + .cloned() + { + replacements.insert(original_enum_value.clone(), new_enum_value.clone()); + } + } + } + } + + Ok(replacements) +} + +/// Given a list of directives and a directive name like `@join__type` or `@join__enumValue`, +/// replace the `graph:` argument with a new directive for each subgraph name in the +/// `replaced_subgraph_names` map. +fn replace_join_enum( + directives: &DirectiveList, + directive_name: &Name, + replaced_subgraph_names: &MultiMap, +) -> DirectiveList { + let mut new_directives = DirectiveList::new(); + for d in directives.iter() { + if &d.name == directive_name { + let Some(graph_arg) = d + .arguments + .iter() + .find(|a| a.name == name!(graph)) + .and_then(|a| a.value.as_enum()) + else { + continue; + }; + + let Some(replacements) = replaced_subgraph_names.get_vec(graph_arg) else { + new_directives.push(d.clone()); + continue; + }; + + for replacement in replacements { + let mut new_directive = d.clone(); + let new_directive = new_directive.make_mut(); + if let Some(a) = new_directive + .arguments + .iter_mut() + .find(|a| a.name == name!(graph)) + { + let a = a.make_mut(); + a.value = Value::Enum(replacement.clone()).into(); + }; + new_directives.push(new_directive.clone()); + } + } else { + new_directives.push(d.clone()); + } + } + new_directives +} + +/// Unfortunately, there are two different DirectiveList types, so this +/// function is duplicated. +fn replace_join_enum_ast( + directives: &ast::DirectiveList, + directive_name: &Name, + replaced_subgraph_names: &MultiMap, +) -> ast::DirectiveList { + let mut new_directives = ast::DirectiveList::new(); + for d in directives.iter() { + if &d.name == directive_name { + let Some(graph_arg) = d + .arguments + .iter() + .find(|a| a.name == name!(graph)) + .and_then(|a| a.value.as_enum()) + else { + continue; + }; + + let Some(replacements) = replaced_subgraph_names.get_vec(graph_arg) else { + new_directives.push(d.clone()); + continue; + }; + + for replacement in replacements { + let mut new_directive = d.clone(); + let new_directive = new_directive.make_mut(); + if let Some(a) = new_directive + .arguments + .iter_mut() + .find(|a| a.name == name!(graph)) + { + let a = a.make_mut(); + a.value = Value::Enum(replacement.clone()).into(); + }; + new_directives.push(new_directive.clone()); + } + } else { + new_directives.push(d.clone()); + } + } + new_directives +} + +fn strip_invalid_join_directives_from_input_type( + node: &InputObjectType, + replaced_subgraph_names: &MultiMap, +) -> Node { + let mut node = node.clone(); + + node.directives = replace_join_enum( + &node.directives, + &name!(join__type), + replaced_subgraph_names, + ); + + for (_, field) in node.fields.iter_mut() { + let field = field.make_mut(); + field.directives = replace_join_enum_ast( + &field.directives, + &name!(join__field), + replaced_subgraph_names, + ); + } + + node.into() +} + +fn strip_invalid_join_directives_from_enum( + node: &EnumType, + replaced_subgraph_names: &MultiMap, +) -> Node { + let mut node = node.clone(); + + node.directives = replace_join_enum( + &node.directives, + &name!(join__type), + replaced_subgraph_names, + ); + + for (_, value) in node.values.iter_mut() { + let value = value.make_mut(); + value.directives = replace_join_enum_ast( + &value.directives, + &name!(join__enumValue), + replaced_subgraph_names, + ); + } + node.into() +} + +fn strip_invalid_join_directives_from_scalar( + node: &ScalarType, + replaced_subgraph_names: &MultiMap, +) -> Node { + let mut node = node.clone(); + + node.directives = replace_join_enum( + &node.directives, + &name!(join__type), + replaced_subgraph_names, + ); + + node.into() +} diff --git a/apollo-federation/src/sources/connect/expand/mod.rs b/apollo-federation/src/sources/connect/expand/mod.rs index a729707a98..a2659169b0 100644 --- a/apollo-federation/src/sources/connect/expand/mod.rs +++ b/apollo-federation/src/sources/connect/expand/mod.rs @@ -6,6 +6,7 @@ use apollo_compiler::validation::Valid; use carryover::carryover_directives; use indexmap::IndexMap; use itertools::Itertools; +use multimap::MultiMap; use crate::ApiSchemaOptions; use crate::Supergraph; @@ -93,11 +94,22 @@ pub fn expand_connectors( FederationError::internal(format!("could not merge expanded subgraphs: {e:?}")) })?; + let subgraph_name_replacements = expanded_subgraphs + .iter() + .map(|(connector, _)| { + ( + connector.id.subgraph_name.as_str(), + connector.id.synthetic_name(), + ) + }) + .collect::>(); + let mut new_supergraph = FederationSchema::new(new_supergraph.schema.into_inner())?; carryover_directives( &supergraph.schema, &mut new_supergraph, spec_versions.into_iter(), + &subgraph_name_replacements, ) .map_err(|e| FederationError::internal(format!("could not carry over directives: {e:?}")))?; diff --git a/apollo-federation/src/sources/connect/expand/snapshots/apollo_federation__sources__connect__expand__carryover__tests__carryover.snap b/apollo-federation/src/sources/connect/expand/snapshots/apollo_federation__sources__connect__expand__carryover__tests__carryover.snap index f65b1d68fd..d132b4a2f9 100644 --- a/apollo-federation/src/sources/connect/expand/snapshots/apollo_federation__sources__connect__expand__carryover__tests__carryover.snap +++ b/apollo-federation/src/sources/connect/expand/snapshots/apollo_federation__sources__connect__expand__carryover__tests__carryover.snap @@ -103,6 +103,8 @@ type Z @join__type(graph: TWO, key: "id") @context(name: "two__ctx") { x: X @join__field(graph: TWO, type: "X") } -scalar requiresScopes__Scope +scalar context__ContextFieldValue scalar policy__Policy + +scalar requiresScopes__Scope diff --git a/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.graphql b/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.graphql index 4ae3831769..dd48bf1e8b 100644 --- a/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.graphql +++ b/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.graphql @@ -8,6 +8,8 @@ schema @link(url: "https://specs.apollo.dev/policy/v0.1", for: SECURITY) @link(url: "https://specs.apollo.dev/context/v0.1", for: SECURITY) @link(url: "http://specs.example.org/custom/v0.1", import: ["@custom"]) + @link(url: "http://specs.example.org/custom2/v0.1", import: ["@custom2"]) + @link(url: "http://specs.example.org/custom3/v0.1", import: ["@custom3"]) @link(url: "https://specs.apollo.dev/connect/v0.2", for: EXECUTION) @join__directive(graphs: [ONE], name: "link", args: {url: "https://specs.apollo.dev/connect/v0.1", import: ["@connect", "@source"]}) @join__directive(graphs: [ONE], name: "source", args: {name: "json", http: {baseURL: "http://example/"}}) @@ -21,7 +23,11 @@ directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION directive @context__fromContext(field: context__ContextFieldValue) on ARGUMENT_DEFINITION -directive @custom on OBJECT | FIELD_DEFINITION +directive @custom(s: custom__Scalar, e: custom__Enum, i: custom__Input) on OBJECT | FIELD_DEFINITION + +directive @custom2(s: custom__Scalar2, e: custom__Enum2, i: custom__Input2) on OBJECT | FIELD_DEFINITION + +directive @custom3(s: custom__Scalar3, e: custom__Enum3, i: custom__Input3) on OBJECT | FIELD_DEFINITION directive @inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION @@ -49,6 +55,60 @@ directive @tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFAC scalar context__ContextFieldValue +enum custom__Enum + @join__type(graph: ONE) + @join__type(graph: TWO) +{ + ONE @join__enumValue(graph: ONE) @join__enumValue(graph: TWO) + TWO @join__enumValue(graph: ONE) @join__enumValue(graph: TWO) +} + +enum custom__Enum2 + @join__type(graph: ONE) +{ + ONE @join__enumValue(graph: ONE) + TWO @join__enumValue(graph: ONE) +} + +enum custom__Enum3 + @join__type(graph: TWO) +{ + ONE @join__enumValue(graph: TWO) + TWO @join__enumValue(graph: TWO) +} + +input custom__Input + @join__type(graph: ONE) + @join__type(graph: TWO) +{ + one: String + two: String +} + +input custom__Input2 + @join__type(graph: ONE) +{ + one: String + two: String +} + +input custom__Input3 + @join__type(graph: TWO) +{ + one: String + two: String +} + +scalar custom__Scalar + @join__type(graph: ONE) + @join__type(graph: TWO) + +scalar custom__Scalar2 + @join__type(graph: ONE) + +scalar custom__Scalar3 + @join__type(graph: TWO) + input join__ContextArgument { name: String! type: String! @@ -107,7 +167,7 @@ type T id: ID! tagged: String @join__field(graph: ONE) @tag(name: "tag") hidden: String @inaccessible @join__field(graph: ONE) - custom: String @join__field(graph: ONE) @custom + custom: String @join__field(graph: ONE) @custom @custom2 authenticated: String @join__field(graph: ONE) @authenticated requiresScopes: String @join__field(graph: ONE) @requiresScopes(scopes: ["scope"]) policy: String @join__field(graph: ONE) @policy(policies: [["admin"]]) @@ -127,6 +187,6 @@ type Z @context(name: "two__ctx") { id: ID! - y: String - x: X + y: String @custom(s: "x", e: ONE, i: {one: "one"}) + x: X @custom3(s: "x", e: ONE, i: {one: "one"}) } diff --git a/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.yaml b/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.yaml index c99b66b2a5..079726c0d3 100644 --- a/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.yaml +++ b/apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.yaml @@ -12,10 +12,12 @@ subgraphs: ] ) @link(url: "http://specs.example.org/custom/v0.1", import: ["@custom"]) + @link(url: "http://specs.example.org/custom2/v0.1", import: ["@custom2"]) @link(url: "https://specs.apollo.dev/connect/v0.1", import: ["@connect", "@source"]) @composeDirective(name: "@custom") + @composeDirective(name: "@custom2") @source(name: "json" http: { baseURL: "http://example/" }) - directive @custom on OBJECT | FIELD_DEFINITION + type Query { ts: [T] @connect( source: "json" @@ -52,7 +54,7 @@ subgraphs: id: ID! tagged: String @tag(name: "tag") hidden: String @inaccessible - custom: String @custom + custom: String @custom @custom2 authenticated: String @authenticated requiresScopes: String @requiresScopes(scopes: ["scope"]) policy: String @policy(policies: [["admin"]]) @@ -67,12 +69,54 @@ subgraphs: type R { id: ID! } + + # bug fix: this won't compose until it's fixed and released in federation + # the graphql file is currently hand-edited to add these definitions + # + # @custom appears in both subgraphs, so will be merged appropriately, and it will attributed only to the non-connector subgraph + # @custom2 appears in the connector subgraph, so we have to add it and rewrite the join__* directives + # @custom3 appears in the non-connector subgraph, so it's composed appropriately + # + # this won't compose until after 2.11.0-preview.3 + + directive @custom(s: custom__Scalar, e: custom__Enum, i: custom__Input) on OBJECT | FIELD_DEFINITION + + scalar custom__Scalar + + enum custom__Enum { + ONE + TWO + } + + input custom__Input { + one: String + two: String + } + + directive @custom2(s: custom__Scalar2, e: custom__Enum2, i: custom__Input2) on OBJECT | FIELD_DEFINITION + + scalar custom__Scalar2 + + enum custom__Enum2 { + ONE + TWO + } + + input custom__Input2 { + one: String + two: String + } two: routing_url: none schema: sdl: | extend schema - @link(url: "https://specs.apollo.dev/federation/v2.8", import: ["@key", "@context", "@fromContext"]) + @link(url: "https://specs.apollo.dev/federation/v2.8", import: ["@key", "@context", "@fromContext", "@composeDirective"]) + @link(url: "http://specs.example.org/custom/v0.1", import: ["@custom"]) + @link(url: "http://specs.example.org/custom3/v0.1", import: ["@custom3"]) + @composeDirective(name: "@custom") + @composeDirective(name: "@custom3") + type T @key(fields: "id") { id: ID! overridden: String @@ -84,11 +128,38 @@ subgraphs: type Z @key(fields: "id") @context(name: "ctx") { id: ID! - y: String - x: X + y: String @custom(s: "x", e: ONE, i: { one: "one" }) + x: X @custom3(s: "x", e: ONE, i: { one: "one" }) } type X @key(fields: "id") { id: ID! w(z: String @fromContext(field: "$$ctx { y }")): String - } \ No newline at end of file + } + + directive @custom(s: custom__Scalar, e: custom__Enum, i: custom__Input) on OBJECT | FIELD_DEFINITION + scalar custom__Scalar + + enum custom__Enum { + ONE + TWO + } + + input custom__Input { + one: String + two: String + } + + directive @custom3(s: custom__Scalar3, e: custom__Enum3, i: custom__Input3) on OBJECT | FIELD_DEFINITION + + scalar custom__Scalar3 + + enum custom__Enum3 { + ONE + TWO + } + + input custom__Input3 { + one: String + two: String + } diff --git a/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@carryover.graphql.snap b/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@carryover.graphql.snap index c09cc663de..9306cde07b 100644 --- a/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@carryover.graphql.snap +++ b/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@carryover.graphql.snap @@ -3,7 +3,7 @@ source: apollo-federation/src/sources/connect/expand/tests/mod.rs expression: raw_sdl input_file: apollo-federation/src/sources/connect/expand/tests/schemas/expand/carryover.graphql --- -schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) @link(url: "https://specs.apollo.dev/inaccessible/v0.2", for: SECURITY) @join__directive(graphs: [], name: "link", args: {url: "https://specs.apollo.dev/connect/v0.1"}) @link(url: "https://specs.apollo.dev/connect/v0.2", for: EXECUTION) @link(url: "https://specs.apollo.dev/tag/v0.3") @link(url: "https://specs.apollo.dev/authenticated/v0.1", for: SECURITY) @link(url: "https://specs.apollo.dev/requiresScopes/v0.1", for: SECURITY) @link(url: "https://specs.apollo.dev/policy/v0.1", for: SECURITY) @link(url: "http://specs.example.org/custom/v0.1", import: ["@custom"]) @link(url: "https://specs.apollo.dev/context/v0.1", for: SECURITY) { +schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/join/v0.5", for: EXECUTION) @link(url: "https://specs.apollo.dev/inaccessible/v0.2", for: SECURITY) @join__directive(graphs: [], name: "link", args: {url: "https://specs.apollo.dev/connect/v0.1"}) @link(url: "https://specs.apollo.dev/connect/v0.2", for: EXECUTION) @link(url: "https://specs.apollo.dev/tag/v0.3") @link(url: "https://specs.apollo.dev/authenticated/v0.1", for: SECURITY) @link(url: "https://specs.apollo.dev/requiresScopes/v0.1", for: SECURITY) @link(url: "https://specs.apollo.dev/policy/v0.1", for: SECURITY) @link(url: "http://specs.example.org/custom/v0.1", import: ["@custom"]) @link(url: "http://specs.example.org/custom2/v0.1", import: ["@custom2"]) @link(url: "http://specs.example.org/custom3/v0.1", import: ["@custom3"]) @link(url: "https://specs.apollo.dev/context/v0.1", for: SECURITY) { query: Query } @@ -33,7 +33,11 @@ directive @requiresScopes(scopes: [[requiresScopes__Scope!]!]!) on FIELD_DEFINIT directive @policy(policies: [[policy__Policy!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM -directive @custom on OBJECT | FIELD_DEFINITION +directive @custom(s: custom__Scalar, e: custom__Enum, i: custom__Input) on OBJECT | FIELD_DEFINITION + +directive @custom2(s: custom__Scalar2, e: custom__Enum2, i: custom__Input2) on OBJECT | FIELD_DEFINITION + +directive @custom3(s: custom__Scalar3, e: custom__Enum3, i: custom__Input3) on OBJECT | FIELD_DEFINITION directive @context(name: String!) repeatable on INTERFACE | OBJECT | UNION @@ -70,7 +74,7 @@ enum join__Graph { type T @join__type(graph: ONE_QUERY_T_0, key: "id") @join__type(graph: ONE_QUERY_TS_0) @join__type(graph: ONE_T_R_0, key: "id") @join__type(graph: TWO, key: "id") { authenticated: String @join__field(graph: ONE_QUERY_T_0, type: "String") @join__field(graph: ONE_QUERY_TS_0, type: "String") @authenticated - custom: String @join__field(graph: ONE_QUERY_T_0, type: "String") @join__field(graph: ONE_QUERY_TS_0, type: "String") @custom + custom: String @join__field(graph: ONE_QUERY_T_0, type: "String") @join__field(graph: ONE_QUERY_TS_0, type: "String") @custom @custom2 hidden: String @join__field(graph: ONE_QUERY_T_0, type: "String") @join__field(graph: ONE_QUERY_TS_0, type: "String") @inaccessible id: ID! @join__field(graph: ONE_QUERY_T_0, type: "ID!") @join__field(graph: ONE_QUERY_TS_0, type: "ID!") @join__field(graph: ONE_T_R_0, type: "ID!") @join__field(graph: TWO, type: "ID!") overridden: String @join__field(graph: ONE_QUERY_T_0, override: "two", overrideLabel: "label", type: "String") @join__field(graph: ONE_QUERY_TS_0, override: "two", overrideLabel: "label", type: "String") @join__field(graph: TWO, type: "String") @@ -91,6 +95,30 @@ type R @join__type(graph: ONE_T_R_0) { id: ID! @join__field(graph: ONE_T_R_0, type: "ID!") } +enum custom__Enum @join__type(graph: TWO) { + ONE @join__enumValue(graph: TWO) + TWO @join__enumValue(graph: TWO) +} + +enum custom__Enum3 @join__type(graph: TWO) { + ONE @join__enumValue(graph: TWO) + TWO @join__enumValue(graph: TWO) +} + +input custom__Input @join__type(graph: TWO) { + one: String @join__field(graph: TWO, type: "String") + two: String @join__field(graph: TWO, type: "String") +} + +input custom__Input3 @join__type(graph: TWO) { + one: String @join__field(graph: TWO, type: "String") + two: String @join__field(graph: TWO, type: "String") +} + +scalar custom__Scalar @join__type(graph: TWO) + +scalar custom__Scalar3 @join__type(graph: TWO) + type X @join__type(graph: TWO, key: "id") { id: ID! @join__field(graph: TWO, type: "ID!") w: String @join__field(graph: TWO, type: "String", contextArguments: [{context: "two__ctx", name: "z", type: "String", selection: " { y }"}]) @@ -98,10 +126,24 @@ type X @join__type(graph: TWO, key: "id") { type Z @join__type(graph: TWO, key: "id") @context(name: "two__ctx") { id: ID! @join__field(graph: TWO, type: "ID!") - y: String @join__field(graph: TWO, type: "String") - x: X @join__field(graph: TWO, type: "X") + y: String @join__field(graph: TWO, type: "String") @custom(s: "x", e: ONE, i: {one: "one"}) + x: X @join__field(graph: TWO, type: "X") @custom3(s: "x", e: ONE, i: {one: "one"}) } -scalar requiresScopes__Scope +scalar context__ContextFieldValue + +enum custom__Enum2 @join__type(graph: ONE_QUERY_TS_0) @join__type(graph: ONE_QUERY_T_0) @join__type(graph: ONE_T_R_0) { + ONE @join__enumValue(graph: ONE_QUERY_TS_0) @join__enumValue(graph: ONE_QUERY_T_0) @join__enumValue(graph: ONE_T_R_0) + TWO @join__enumValue(graph: ONE_QUERY_TS_0) @join__enumValue(graph: ONE_QUERY_T_0) @join__enumValue(graph: ONE_T_R_0) +} + +input custom__Input2 @join__type(graph: ONE_QUERY_TS_0) @join__type(graph: ONE_QUERY_T_0) @join__type(graph: ONE_T_R_0) { + one: String + two: String +} + +scalar custom__Scalar2 @join__type(graph: ONE_QUERY_TS_0) @join__type(graph: ONE_QUERY_T_0) @join__type(graph: ONE_T_R_0) scalar policy__Policy + +scalar requiresScopes__Scope diff --git a/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@directives.graphql.snap b/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@directives.graphql.snap index eec3686f56..57e0454f59 100644 --- a/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@directives.graphql.snap +++ b/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@directives.graphql.snap @@ -104,6 +104,8 @@ type Z @join__type(graph: TWO, key: "id") @context(name: "two__ctx") { x: X @join__field(graph: TWO, type: "X") } -scalar requiresScopes__Scope +scalar context__ContextFieldValue scalar policy__Policy + +scalar requiresScopes__Scope diff --git a/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@steelthread.graphql.snap b/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@steelthread.graphql.snap index dbc2f18fb2..4e974cf228 100644 --- a/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@steelthread.graphql.snap +++ b/apollo-federation/src/sources/connect/expand/tests/snapshots/supergraph@steelthread.graphql.snap @@ -69,3 +69,5 @@ type Query @join__type(graph: CONNECTORS_QUERY_USER_0) @join__type(graph: CONNEC users: [User] @join__field(graph: CONNECTORS_QUERY_USERS_0, type: "[User]") _: ID @inaccessible @join__field(graph: CONNECTORS_USER_D_1, type: "ID") } + +scalar JSON @join__type(graph: CONNECTORS_QUERY_USERS_0) @join__type(graph: CONNECTORS_QUERY_USER_0) @join__type(graph: CONNECTORS_USER_D_1)