From cbe50255b0e6588a970249a3565368ee3ee13a97 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Fri, 12 Jul 2024 14:22:18 -0500 Subject: [PATCH 01/11] Initial implementation of cost and listSize extraction --- .../extract_subgraphs_from_supergraph.rs | 148 +++++++++++- apollo-federation/tests/extract_subgraphs.rs | 214 ++++++++++++++++++ ...tract_subgraphs__can_extract_subgraph.snap | 8 + ...s__extracts_demand_control_directives.snap | 153 +++++++++++++ ...cts_renamed_demand_control_directives.snap | 153 +++++++++++++ 5 files changed, 667 insertions(+), 9 deletions(-) create mode 100644 apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap create mode 100644 apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs index 9a4f6f72f0..c639fa63fd 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -1,9 +1,11 @@ use std::collections::BTreeMap; +use std::collections::HashMap; use std::fmt; use std::fmt::Write; use std::ops::Deref; use std::sync::Arc; +use apollo_compiler::ast::Directive; use apollo_compiler::ast::FieldDefinition; use apollo_compiler::executable; use apollo_compiler::name; @@ -274,6 +276,10 @@ pub(crate) fn new_empty_fed_2_subgraph_schema() -> Result, } +fn get_original_directive_names<'schema>( + supergraph_schema: &'schema FederationSchema, +) -> HashMap<&'schema str, &'schema str> { + let mut hm = HashMap::new(); + for directive in &supergraph_schema.schema().schema_definition.directives { + if directive.name.as_str() == "link" { + if let (Some(url), Some(new_name)) = ( + directive.argument_by_name("url"), + directive.argument_by_name("as"), + ) { + if url + .as_str() + .is_some_and(|s| s.starts_with("https://specs.apollo.dev/cost")) + { + hm.insert("cost", new_name.as_str().unwrap_or("cost")); + } else if url + .as_str() + .is_some_and(|s| s.starts_with("https://specs.apollo.dev/listSize")) + { + hm.insert("listSize", new_name.as_str().unwrap_or("listSize")); + } + } + } + } + hm +} + fn extract_subgraphs_from_fed_2_supergraph( supergraph_schema: &FederationSchema, subgraphs: &mut FederationSubgraphs, @@ -320,6 +353,8 @@ fn extract_subgraphs_from_fed_2_supergraph( filtered_types, )?; + let original_directive_names = get_original_directive_names(supergraph_schema); + extract_object_type_content( supergraph_schema, subgraphs, @@ -327,6 +362,7 @@ fn extract_subgraphs_from_fed_2_supergraph( federation_spec_definitions, join_spec_definition, &object_types, + &original_directive_names, )?; extract_interface_type_content( supergraph_schema, @@ -335,6 +371,7 @@ fn extract_subgraphs_from_fed_2_supergraph( federation_spec_definitions, join_spec_definition, &interface_types, + &original_directive_names, )?; extract_union_type_content( supergraph_schema, @@ -349,6 +386,7 @@ fn extract_subgraphs_from_fed_2_supergraph( graph_enum_value_name_to_subgraph_name, join_spec_definition, &enum_types, + &original_directive_names, )?; extract_input_object_type_content( supergraph_schema, @@ -356,6 +394,7 @@ fn extract_subgraphs_from_fed_2_supergraph( graph_enum_value_name_to_subgraph_name, join_spec_definition, &input_object_types, + &original_directive_names, )?; // We add all the "executable" directive definitions from the supergraph to each subgraphs, as @@ -705,6 +744,7 @@ fn extract_object_type_content( federation_spec_definitions: &IndexMap, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], + original_directive_names: &HashMap<&str, &str>, ) -> Result<(), FederationError> { let field_directive_definition = join_spec_definition.field_directive_definition(supergraph_schema)?; @@ -784,6 +824,7 @@ fn extract_object_type_content( federation_spec_definition, is_shareable, None, + original_directive_names, )?; } } else { @@ -833,6 +874,7 @@ fn extract_object_type_content( federation_spec_definition, is_shareable, Some(field_directive_application), + original_directive_names, )?; } } @@ -849,6 +891,7 @@ fn extract_interface_type_content( federation_spec_definitions: &IndexMap, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], + original_directive_names: &HashMap<&str, &str>, ) -> Result<(), FederationError> { let field_directive_definition = join_spec_definition.field_directive_definition(supergraph_schema)?; @@ -978,6 +1021,7 @@ fn extract_interface_type_content( federation_spec_definition, false, None, + original_directive_names, )?; } } else { @@ -1020,6 +1064,7 @@ fn extract_interface_type_content( federation_spec_definition, false, Some(field_directive_application), + original_directive_names, )?; } } @@ -1127,6 +1172,7 @@ fn extract_enum_type_content( graph_enum_value_name_to_subgraph_name: &IndexMap>, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], + original_directive_names: &HashMap<&str, &str>, ) -> Result<(), FederationError> { // This was added in join 0.3, so it can genuinely be None. let enum_value_directive_definition = @@ -1142,6 +1188,35 @@ fn extract_enum_type_content( }; let type_ = pos.get(supergraph_schema.schema())?; + for graph_enum_value in subgraph_info.keys() { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + // BEGIN MESSY IMPL + let cost_directive_name = original_directive_names.get("cost").unwrap_or(&"cost"); + if let Some(cost_directive) = type_.directives.get(*cost_directive_name) { + let destination_directive = Directive { + name: name!("federation__cost"), + arguments: cost_directive.arguments.clone(), + }; + pos.insert_directive(&mut subgraph.schema, Component::from(destination_directive))?; + } + + let list_size_directive_name = original_directive_names + .get("listSize") + .unwrap_or(&"listSize"); + if let Some(list_size_directive) = type_.directives.get(*list_size_directive_name) { + let destination_directive = Directive { + name: name!("federation__listSize"), + arguments: list_size_directive.arguments.clone(), + }; + pos.insert_directive(&mut subgraph.schema, Component::from(destination_directive))?; + } + // END MESSY IMPL + } + for (value_name, value) in type_.values.iter() { let value_pos = pos.value(value_name.clone()); let mut enum_value_directive_applications = Vec::new(); @@ -1211,6 +1286,7 @@ fn extract_input_object_type_content( graph_enum_value_name_to_subgraph_name: &IndexMap>, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], + original_directive_names: &HashMap<&str, &str>, ) -> Result<(), FederationError> { let field_directive_definition = join_spec_definition.field_directive_definition(supergraph_schema)?; @@ -1242,7 +1318,13 @@ fn extract_input_object_type_content( graph_enum_value_name_to_subgraph_name, graph_enum_value, )?; - add_subgraph_input_field(input_field_pos.clone(), input_field, subgraph, None)?; + add_subgraph_input_field( + input_field_pos.clone(), + input_field, + subgraph, + None, + original_directive_names, + )?; } } else { for field_directive_application in &field_directive_applications { @@ -1274,6 +1356,7 @@ fn extract_input_object_type_content( input_field, subgraph, Some(field_directive_application), + original_directive_names, )?; } } @@ -1290,6 +1373,7 @@ fn add_subgraph_field( federation_spec_definition: &'static FederationSpecDefinition, is_shareable: bool, field_directive_application: Option<&FieldDirectiveArguments>, + original_directive_names: &HashMap<&str, &str>, ) -> Result<(), FederationError> { let field_directive_application = field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments { @@ -1317,15 +1401,22 @@ fn add_subgraph_field( }; for argument in &field.arguments { + let mut destination_argument = InputValueDefinition { + description: None, + name: argument.name.clone(), + ty: argument.ty.clone(), + default_value: argument.default_value.clone(), + directives: Default::default(), + }; + propagate_demand_control_directives( + &argument.directives, + &mut destination_argument.directives, + original_directive_names, + ); + subgraph_field .arguments - .push(Node::new(InputValueDefinition { - description: None, - name: argument.name.clone(), - ty: argument.ty.clone(), - default_value: argument.default_value.clone(), - directives: Default::default(), - })) + .push(Node::new(destination_argument)) } if let Some(requires) = &field_directive_application.requires { subgraph_field.directives.push(Node::new( @@ -1367,6 +1458,12 @@ fn add_subgraph_field( )); } + propagate_demand_control_directives( + &field.directives, + &mut subgraph_field.directives, + original_directive_names, + ); + match object_or_interface_field_definition_position { ObjectOrInterfaceFieldDefinitionPosition::Object(pos) => { pos.insert(&mut subgraph.schema, Component::from(subgraph_field))?; @@ -1384,6 +1481,7 @@ fn add_subgraph_input_field( input_field: &InputValueDefinition, subgraph: &mut FederationSubgraph, field_directive_application: Option<&FieldDirectiveArguments>, + original_directive_names: &HashMap<&str, &str>, ) -> Result<(), FederationError> { let field_directive_application = field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments { @@ -1400,7 +1498,7 @@ fn add_subgraph_input_field( Some(t) => Node::new(decode_type(t)?), None => input_field.ty.clone(), }; - let subgraph_input_field = InputValueDefinition { + let mut subgraph_input_field = InputValueDefinition { description: None, name: input_object_field_definition_position.field_name.clone(), ty: subgraph_input_field_type, @@ -1408,6 +1506,12 @@ fn add_subgraph_input_field( directives: Default::default(), }; + propagate_demand_control_directives( + &input_field.directives, + &mut subgraph_input_field.directives, + original_directive_names, + ); + input_object_field_definition_position .insert(&mut subgraph.schema, Component::from(subgraph_input_field))?; @@ -1448,6 +1552,32 @@ fn get_subgraph<'subgraph>( }) } +fn propagate_demand_control_directives( + source: &apollo_compiler::ast::DirectiveList, + dest: &mut apollo_compiler::ast::DirectiveList, + original_directive_names: &HashMap<&str, &str>, +) { + let cost_directive_name = original_directive_names.get("cost").unwrap_or(&"cost"); + if let Some(cost_directive) = source.get(*cost_directive_name) { + let destination_directive = Directive { + name: name!("federation__cost"), + arguments: cost_directive.arguments.clone(), + }; + dest.push(Node::new(destination_directive)); + } + + let list_size_directive_name = original_directive_names + .get("listSize") + .unwrap_or(&"listSize"); + if let Some(list_size_directive) = source.get(*list_size_directive_name) { + let destination_directive = Directive { + name: name!("federation__listSize"), + arguments: list_size_directive.arguments.clone(), + }; + dest.push(Node::new(destination_directive)); + } +} + struct FederationSubgraph { name: String, url: String, diff --git a/apollo-federation/tests/extract_subgraphs.rs b/apollo-federation/tests/extract_subgraphs.rs index 51a505345f..2452ef8af1 100644 --- a/apollo-federation/tests/extract_subgraphs.rs +++ b/apollo-federation/tests/extract_subgraphs.rs @@ -255,3 +255,217 @@ fn erase_empty_types_due_to_overridden_fields() { .schema(); assert!(!b.types.contains_key("User")); } + +#[test] +fn extracts_demand_control_directives() { + let subgraphs = Supergraph::new(r#" + 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/cost/v0.1") + @link(url: "https://specs.apollo.dev/listSize/v0.1") + { + query: Query + } + + directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + 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__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + enum AorB + @join__type(graph: SUBGRAPHWITHCOST) + @cost(weight: 15) + { + A @join__enumValue(graph: SUBGRAPHWITHCOST) + B @join__enumValue(graph: SUBGRAPHWITHCOST) + } + + input InputTypeWithCost + @join__type(graph: SUBGRAPHWITHCOST) + { + somethingWithCost: Int @cost(weight: 20) + } + + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } + + scalar join__DirectiveArguments + + scalar join__FieldSet + + scalar join__FieldValue + + enum join__Graph { + SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") + SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SUBGRAPHWITHCOST) + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @cost(weight: 5) + argWithCost(arg: Int @cost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) + enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) + inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) + fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(assumedSize: 2000, requireOneSlicingArgument: false) + } + "#) + .expect("is supergraph") + .extract_subgraphs() + .expect("extracts subgraphs"); + + let mut snapshot = String::new(); + for (_name, subgraph) in subgraphs { + use std::fmt::Write; + + _ = writeln!( + &mut snapshot, + "{}\n---\n{}", + subgraph.name, + subgraph.schema.schema() + ); + } + insta::assert_snapshot!(snapshot); +} + +#[test] +fn extracts_renamed_demand_control_directives() { + let subgraphs = Supergraph::new(r#" + 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/cost/v0.1", as: "renamedCost") + @link(url: "https://specs.apollo.dev/listSize/v0.1", as: "renamedListSize") + { + query: Query + } + + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + 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__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @renamedCost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + + directive @renamedListSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + enum AorB + @join__type(graph: SUBGRAPHWITHCOST) + @renamedCost(weight: 15) + { + A @join__enumValue(graph: SUBGRAPHWITHCOST) + B @join__enumValue(graph: SUBGRAPHWITHCOST) + } + + input InputTypeWithCost + @join__type(graph: SUBGRAPHWITHCOST) + { + somethingWithCost: Int @renamedCost(weight: 20) + } + + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } + + scalar join__DirectiveArguments + + scalar join__FieldSet + + scalar join__FieldValue + + enum join__Graph { + SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") + SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SUBGRAPHWITHCOST) + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @renamedCost(weight: 5) + argWithCost(arg: Int @renamedCost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) + enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) + inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) + fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false) + } + "#) + .expect("parses") + .extract_subgraphs() + .expect("extracts"); + + let mut snapshot = String::new(); + for (_name, subgraph) in subgraphs { + use std::fmt::Write; + + _ = writeln!( + &mut snapshot, + "{}\n---\n{}", + subgraph.name, + subgraph.schema.schema() + ); + } + insta::assert_snapshot!(snapshot); +} diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap index f036c14999..288620c3d1 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap @@ -38,6 +38,10 @@ directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + scalar link__Import enum link__Purpose { @@ -115,6 +119,10 @@ directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + scalar link__Import enum link__Purpose { diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap new file mode 100644 index 0000000000..6cda490b52 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap @@ -0,0 +1,153 @@ +--- +source: apollo-federation/tests/extract_subgraphs.rs +expression: snapshot +--- +subgraphWithCost +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!, label: String) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +enum AorB @federation__cost(weight: 15) { + A + B +} + +input InputTypeWithCost { + somethingWithCost: Int @federation__cost(weight: 20) +} + +type Query { + fieldWithCost: Int @federation__cost(weight: 5) + argWithCost( + arg: Int @federation__cost(weight: 10), + ): Int + enumWithCost: AorB + inputWithCost(someInput: InputTypeWithCost): Int + _service: _Service! +} + +scalar _Any + +type _Service { + sdl: String +} + +subgraphWithListSize +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!, label: String) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +type Query { + fieldWithListSize: [String!] @federation__listSize(assumedSize: 2000, requireOneSlicingArgument: false) + _service: _Service! +} + +scalar _Any + +type _Service { + sdl: String +} diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap new file mode 100644 index 0000000000..6cda490b52 --- /dev/null +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap @@ -0,0 +1,153 @@ +--- +source: apollo-federation/tests/extract_subgraphs.rs +expression: snapshot +--- +subgraphWithCost +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!, label: String) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +enum AorB @federation__cost(weight: 15) { + A + B +} + +input InputTypeWithCost { + somethingWithCost: Int @federation__cost(weight: 20) +} + +type Query { + fieldWithCost: Int @federation__cost(weight: 5) + argWithCost( + arg: Int @federation__cost(weight: 10), + ): Int + enumWithCost: AorB + inputWithCost(someInput: InputTypeWithCost): Int + _service: _Service! +} + +scalar _Any + +type _Service { + sdl: String +} + +subgraphWithListSize +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!, label: String) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +type Query { + fieldWithListSize: [String!] @federation__listSize(assumedSize: 2000, requireOneSlicingArgument: false) + _service: _Service! +} + +scalar _Any + +type _Service { + sdl: String +} From d973eafa455aa7311dab4c983e32ababe25d524d Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Fri, 12 Jul 2024 16:14:52 -0500 Subject: [PATCH 02/11] Introduce a proper spec definition derived from the schema, and use it to propagate the directives --- .../src/link/cost_spec_definition.rs | 171 ++++++++++++++++ .../src/link/federation_spec_definition.rs | 16 ++ apollo-federation/src/link/mod.rs | 1 + apollo-federation/src/link/spec.rs | 7 + apollo-federation/src/link/spec_definition.rs | 11 ++ .../extract_subgraphs_from_supergraph.rs | 183 ++++++++++-------- 6 files changed, 308 insertions(+), 81 deletions(-) create mode 100644 apollo-federation/src/link/cost_spec_definition.rs diff --git a/apollo-federation/src/link/cost_spec_definition.rs b/apollo-federation/src/link/cost_spec_definition.rs new file mode 100644 index 0000000000..8f0af599fd --- /dev/null +++ b/apollo-federation/src/link/cost_spec_definition.rs @@ -0,0 +1,171 @@ +use std::collections::HashMap; + +use apollo_compiler::ast::Argument; +use apollo_compiler::ast::Directive; +use apollo_compiler::name; +use apollo_compiler::schema::Component; +use apollo_compiler::schema::EnumType; +use apollo_compiler::Name; +use apollo_compiler::Node; +use lazy_static::lazy_static; + +use crate::error::FederationError; +use crate::link::spec::Identity; +use crate::link::spec::Url; +use crate::link::spec::Version; +use crate::link::spec_definition::SpecDefinition; +use crate::link::spec_definition::SpecDefinitions; +use crate::schema::position::EnumTypeDefinitionPosition; +use crate::schema::FederationSchema; + +pub(crate) const COST_DIRECTIVE_NAME_IN_SPEC: Name = name!("cost"); +pub(crate) const COST_DIRECTIVE_NAME_DEFAULT: Name = name!("federation__cost"); +pub(crate) const COST_WEIGHT_ARGUMENT_NAME: Name = name!("weight"); + +pub(crate) const LIST_SIZE_DIRECTIVE_NAME_IN_SPEC: Name = name!("listSize"); +pub(crate) const LIST_SIZE_DIRECTIVE_NAME_DEFAULT: Name = name!("federation__listSize"); +pub(crate) const LIST_SIZE_ASSUMED_SIZE_ARGUMENT_NAME: Name = name!("assumedSize"); +pub(crate) const LIST_SIZE_SLICING_ARGUMENTS_ARGUMENT_NAME: Name = name!("slicingArguments"); +pub(crate) const LIST_SIZE_SIZED_FIELDS_ARGUMENT_NAME: Name = name!("sizedFields"); +pub(crate) const LIST_SIZE_REQUIRE_ONE_SLICING_ARGUMENT_ARGUMENT_NAME: Name = + name!("requireOneSlicingArgument"); + +#[derive(Clone)] +pub(crate) struct CostSpecDefinition { + url: Url, + minimum_federation_version: Option, +} + +impl CostSpecDefinition { + pub(crate) fn new(version: Version, minimum_federation_version: Option) -> Self { + Self { + url: Url { + identity: Identity::cost_identity(), + version, + }, + minimum_federation_version, + } + } + + pub(crate) fn cost_directive( + &self, + schema: &FederationSchema, + arguments: Vec>, + ) -> Result { + let name = self + .directive_name_in_schema(schema, &COST_DIRECTIVE_NAME_IN_SPEC)? + .unwrap_or(COST_DIRECTIVE_NAME_DEFAULT); + + Ok(Directive { name, arguments }) + } + + pub(crate) fn list_size_directive( + &self, + schema: &FederationSchema, + arguments: Vec>, + ) -> Result { + let name = self + .directive_name_in_schema(schema, &LIST_SIZE_DIRECTIVE_NAME_IN_SPEC)? + .unwrap_or(LIST_SIZE_DIRECTIVE_NAME_DEFAULT); + + Ok(Directive { name, arguments }) + } + + pub(crate) fn propagate_demand_control_directives( + &self, + subgraph_schema: &FederationSchema, + source: &apollo_compiler::ast::DirectiveList, + dest: &mut apollo_compiler::ast::DirectiveList, + original_directive_names: &HashMap, + ) -> Result<(), FederationError> { + let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); + if let Some(cost_directive) = source.get( + cost_directive_name + .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.push(Node::new(self.cost_directive( + subgraph_schema, + cost_directive.arguments.clone(), + )?)); + } + + let list_size_directive_name = + original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); + if let Some(list_size_directive) = source.get( + list_size_directive_name + .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.push(Node::new(self.list_size_directive( + subgraph_schema, + list_size_directive.arguments.clone(), + )?)); + } + + Ok(()) + } + + pub(crate) fn propagate_demand_control_directives_for_enum( + &self, + subgraph_schema: &mut FederationSchema, + source: &Node, + dest: &EnumTypeDefinitionPosition, + original_directive_names: &HashMap, + ) -> Result<(), FederationError> { + let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); + if let Some(cost_directive) = source.directives.get( + cost_directive_name + .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.insert_directive( + subgraph_schema, + Component::from( + self.cost_directive(subgraph_schema, cost_directive.arguments.clone())?, + ), + )?; + } + + let list_size_directive_name = + original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); + if let Some(list_size_directive) = source.directives.get( + list_size_directive_name + .unwrap_or(&&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.insert_directive( + subgraph_schema, + Component::from( + self.list_size_directive( + subgraph_schema, + list_size_directive.arguments.clone(), + )?, + ), + )?; + } + + Ok(()) + } +} + +impl SpecDefinition for CostSpecDefinition { + fn url(&self) -> &Url { + &self.url + } + + fn minimum_federation_version(&self) -> Option<&Version> { + self.minimum_federation_version.as_ref() + } +} + +lazy_static! { + pub(crate) static ref COST_VERSIONS: SpecDefinitions = { + let mut definitions = SpecDefinitions::new(Identity::cost_identity()); + definitions.add(CostSpecDefinition::new( + Version { major: 0, minor: 1 }, + Some(Version { major: 2, minor: 9 }), + )); + definitions + }; +} diff --git a/apollo-federation/src/link/federation_spec_definition.rs b/apollo-federation/src/link/federation_spec_definition.rs index b62fb7d762..4a1d9da002 100644 --- a/apollo-federation/src/link/federation_spec_definition.rs +++ b/apollo-federation/src/link/federation_spec_definition.rs @@ -426,6 +426,22 @@ lazy_static! { major: 2, minor: 5, })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 6, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 7, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 8, + })); + definitions.add(FederationSpecDefinition::new(Version { + major: 2, + minor: 9, + })); definitions }; } diff --git a/apollo-federation/src/link/mod.rs b/apollo-federation/src/link/mod.rs index 272c5f4adc..965377622f 100644 --- a/apollo-federation/src/link/mod.rs +++ b/apollo-federation/src/link/mod.rs @@ -20,6 +20,7 @@ use crate::link::spec::Identity; use crate::link::spec::Url; pub(crate) mod argument; +pub(crate) mod cost_spec_definition; pub mod database; pub(crate) mod federation_spec_definition; pub(crate) mod graphql_definition; diff --git a/apollo-federation/src/link/spec.rs b/apollo-federation/src/link/spec.rs index 25dd24c4b2..5c1386644b 100644 --- a/apollo-federation/src/link/spec.rs +++ b/apollo-federation/src/link/spec.rs @@ -88,6 +88,13 @@ impl Identity { name: name!("inaccessible"), } } + + pub fn cost_identity() -> Identity { + Identity { + domain: APOLLO_SPEC_DOMAIN.to_string(), + name: name!("cost"), + } + } } /// The version of a `@link` specification, in the form of a major and minor version numbers. diff --git a/apollo-federation/src/link/spec_definition.rs b/apollo-federation/src/link/spec_definition.rs index 5826f8f4d9..b0770bc145 100644 --- a/apollo-federation/src/link/spec_definition.rs +++ b/apollo-federation/src/link/spec_definition.rs @@ -182,6 +182,17 @@ impl SpecDefinitions { self.definitions.get(requested) } + pub(crate) fn find_for_federation_version(&self, federation_version: &Version) -> Option<&T> { + for (_, definition) in &self.definitions { + if let Some(minimum_federation_version) = definition.minimum_federation_version() { + if minimum_federation_version >= federation_version { + return Some(definition); + } + } + } + None + } + pub(crate) fn versions(&self) -> Keys { self.definitions.keys() } diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs index c639fa63fd..46dbc5a5fe 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -5,7 +5,6 @@ use std::fmt::Write; use std::ops::Deref; use std::sync::Arc; -use apollo_compiler::ast::Directive; use apollo_compiler::ast::FieldDefinition; use apollo_compiler::executable; use apollo_compiler::name; @@ -39,6 +38,10 @@ use time::OffsetDateTime; use crate::error::FederationError; use crate::error::MultipleFederationErrors; use crate::error::SingleFederationError; +use crate::link::cost_spec_definition::CostSpecDefinition; +use crate::link::cost_spec_definition::COST_DIRECTIVE_NAME_IN_SPEC; +use crate::link::cost_spec_definition::COST_VERSIONS; +use crate::link::cost_spec_definition::LIST_SIZE_DIRECTIVE_NAME_IN_SPEC; use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; use crate::link::federation_spec_definition::FederationSpecDefinition; use crate::link::federation_spec_definition::FEDERATION_VERSIONS; @@ -305,29 +308,23 @@ struct TypeInfos { fn get_original_directive_names<'schema>( supergraph_schema: &'schema FederationSchema, -) -> HashMap<&'schema str, &'schema str> { +) -> Result, FederationError> { let mut hm = HashMap::new(); for directive in &supergraph_schema.schema().schema_definition.directives { if directive.name.as_str() == "link" { if let (Some(url), Some(new_name)) = ( - directive.argument_by_name("url"), - directive.argument_by_name("as"), + directive.argument_by_name("url").and_then(|s| s.as_str()), + directive.argument_by_name("as").and_then(|s| s.as_str()), ) { - if url - .as_str() - .is_some_and(|s| s.starts_with("https://specs.apollo.dev/cost")) - { - hm.insert("cost", new_name.as_str().unwrap_or("cost")); - } else if url - .as_str() - .is_some_and(|s| s.starts_with("https://specs.apollo.dev/listSize")) - { - hm.insert("listSize", new_name.as_str().unwrap_or("listSize")); + if url.starts_with("https://specs.apollo.dev/cost") { + hm.insert(COST_DIRECTIVE_NAME_IN_SPEC, Name::new(new_name)?); + } else if url.starts_with("https://specs.apollo.dev/listSize") { + hm.insert(LIST_SIZE_DIRECTIVE_NAME_IN_SPEC, Name::new(new_name)?); } } } } - hm + Ok(hm) } fn extract_subgraphs_from_fed_2_supergraph( @@ -353,7 +350,7 @@ fn extract_subgraphs_from_fed_2_supergraph( filtered_types, )?; - let original_directive_names = get_original_directive_names(supergraph_schema); + let original_directive_names = get_original_directive_names(supergraph_schema)?; extract_object_type_content( supergraph_schema, @@ -384,6 +381,7 @@ fn extract_subgraphs_from_fed_2_supergraph( supergraph_schema, subgraphs, graph_enum_value_name_to_subgraph_name, + federation_spec_definitions, join_spec_definition, &enum_types, &original_directive_names, @@ -392,6 +390,7 @@ fn extract_subgraphs_from_fed_2_supergraph( supergraph_schema, subgraphs, graph_enum_value_name_to_subgraph_name, + federation_spec_definitions, join_spec_definition, &input_object_types, &original_directive_names, @@ -744,7 +743,7 @@ fn extract_object_type_content( federation_spec_definitions: &IndexMap, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], - original_directive_names: &HashMap<&str, &str>, + original_directive_names: &HashMap, ) -> Result<(), FederationError> { let field_directive_definition = join_spec_definition.field_directive_definition(supergraph_schema)?; @@ -817,6 +816,8 @@ fn extract_object_type_content( message: "Subgraph unexpectedly does not use federation spec" .to_owned(), })?; + let cost_spec_definition = + get_cost_spec_definition(&subgraph.schema, federation_spec_definition); add_subgraph_field( field_pos.clone().into(), field, @@ -824,6 +825,7 @@ fn extract_object_type_content( federation_spec_definition, is_shareable, None, + cost_spec_definition, original_directive_names, )?; } @@ -855,6 +857,8 @@ fn extract_object_type_content( message: "Subgraph unexpectedly does not use federation spec" .to_owned(), })?; + let cost_spec_definition = + get_cost_spec_definition(&subgraph.schema, federation_spec_definition); if !subgraph_info.contains_key(graph_enum_value) { return Err( SingleFederationError::InvalidFederationSupergraph { @@ -874,6 +878,7 @@ fn extract_object_type_content( federation_spec_definition, is_shareable, Some(field_directive_application), + cost_spec_definition, original_directive_names, )?; } @@ -891,7 +896,7 @@ fn extract_interface_type_content( federation_spec_definitions: &IndexMap, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], - original_directive_names: &HashMap<&str, &str>, + original_directive_names: &HashMap, ) -> Result<(), FederationError> { let field_directive_definition = join_spec_definition.field_directive_definition(supergraph_schema)?; @@ -1014,6 +1019,8 @@ fn extract_interface_type_content( message: "Subgraph unexpectedly does not use federation spec" .to_owned(), })?; + let cost_spec_definition = + get_cost_spec_definition(&subgraph.schema, federation_spec_definition); add_subgraph_field( pos.field(field_name.clone()), field, @@ -1021,6 +1028,7 @@ fn extract_interface_type_content( federation_spec_definition, false, None, + cost_spec_definition, original_directive_names, )?; } @@ -1045,6 +1053,8 @@ fn extract_interface_type_content( message: "Subgraph unexpectedly does not use federation spec" .to_owned(), })?; + let cost_spec_definition = + get_cost_spec_definition(&subgraph.schema, federation_spec_definition); if !subgraph_info.contains_key(graph_enum_value) { return Err( SingleFederationError::InvalidFederationSupergraph { @@ -1064,6 +1074,7 @@ fn extract_interface_type_content( federation_spec_definition, false, Some(field_directive_application), + cost_spec_definition, original_directive_names, )?; } @@ -1170,9 +1181,10 @@ fn extract_enum_type_content( supergraph_schema: &FederationSchema, subgraphs: &mut FederationSubgraphs, graph_enum_value_name_to_subgraph_name: &IndexMap>, + federation_spec_definitions: &IndexMap, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], - original_directive_names: &HashMap<&str, &str>, + original_directive_names: &HashMap, ) -> Result<(), FederationError> { // This was added in join 0.3, so it can genuinely be None. let enum_value_directive_definition = @@ -1194,27 +1206,21 @@ fn extract_enum_type_content( graph_enum_value_name_to_subgraph_name, graph_enum_value, )?; - // BEGIN MESSY IMPL - let cost_directive_name = original_directive_names.get("cost").unwrap_or(&"cost"); - if let Some(cost_directive) = type_.directives.get(*cost_directive_name) { - let destination_directive = Directive { - name: name!("federation__cost"), - arguments: cost_directive.arguments.clone(), - }; - pos.insert_directive(&mut subgraph.schema, Component::from(destination_directive))?; - } - - let list_size_directive_name = original_directive_names - .get("listSize") - .unwrap_or(&"listSize"); - if let Some(list_size_directive) = type_.directives.get(*list_size_directive_name) { - let destination_directive = Directive { - name: name!("federation__listSize"), - arguments: list_size_directive.arguments.clone(), - }; - pos.insert_directive(&mut subgraph.schema, Component::from(destination_directive))?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec".to_owned(), + })?; + if let Some(cost_spec_definition) = + get_cost_spec_definition(&subgraph.schema, federation_spec_definition) + { + cost_spec_definition.propagate_demand_control_directives_for_enum( + &mut subgraph.schema, + type_, + &pos, + original_directive_names, + )?; } - // END MESSY IMPL } for (value_name, value) in type_.values.iter() { @@ -1284,9 +1290,10 @@ fn extract_input_object_type_content( supergraph_schema: &FederationSchema, subgraphs: &mut FederationSubgraphs, graph_enum_value_name_to_subgraph_name: &IndexMap>, + federation_spec_definitions: &IndexMap, join_spec_definition: &JoinSpecDefinition, info: &[TypeInfo], - original_directive_names: &HashMap<&str, &str>, + original_directive_names: &HashMap, ) -> Result<(), FederationError> { let field_directive_definition = join_spec_definition.field_directive_definition(supergraph_schema)?; @@ -1318,11 +1325,20 @@ fn extract_input_object_type_content( graph_enum_value_name_to_subgraph_name, graph_enum_value, )?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec" + .to_owned(), + })?; + let cost_spec_definition = + get_cost_spec_definition(&subgraph.schema, federation_spec_definition); add_subgraph_input_field( input_field_pos.clone(), input_field, subgraph, None, + cost_spec_definition, original_directive_names, )?; } @@ -1339,6 +1355,14 @@ fn extract_input_object_type_content( graph_enum_value_name_to_subgraph_name, graph_enum_value, )?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec" + .to_owned(), + })?; + let cost_spec_definition = + get_cost_spec_definition(&subgraph.schema, federation_spec_definition); if !subgraph_info.contains_key(graph_enum_value) { return Err( SingleFederationError::InvalidFederationSupergraph { @@ -1356,6 +1380,7 @@ fn extract_input_object_type_content( input_field, subgraph, Some(field_directive_application), + cost_spec_definition, original_directive_names, )?; } @@ -1373,7 +1398,8 @@ fn add_subgraph_field( federation_spec_definition: &'static FederationSpecDefinition, is_shareable: bool, field_directive_application: Option<&FieldDirectiveArguments>, - original_directive_names: &HashMap<&str, &str>, + cost_spec_definition: Option<&'static CostSpecDefinition>, + original_directive_names: &HashMap, ) -> Result<(), FederationError> { let field_directive_application = field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments { @@ -1408,11 +1434,14 @@ fn add_subgraph_field( default_value: argument.default_value.clone(), directives: Default::default(), }; - propagate_demand_control_directives( - &argument.directives, - &mut destination_argument.directives, - original_directive_names, - ); + if let Some(cost_spec_definition) = cost_spec_definition { + cost_spec_definition.propagate_demand_control_directives( + &subgraph.schema, + &argument.directives, + &mut destination_argument.directives, + original_directive_names, + )?; + } subgraph_field .arguments @@ -1458,11 +1487,14 @@ fn add_subgraph_field( )); } - propagate_demand_control_directives( - &field.directives, - &mut subgraph_field.directives, - original_directive_names, - ); + if let Some(cost_spec_definition) = cost_spec_definition { + cost_spec_definition.propagate_demand_control_directives( + &subgraph.schema, + &field.directives, + &mut subgraph_field.directives, + original_directive_names, + )?; + } match object_or_interface_field_definition_position { ObjectOrInterfaceFieldDefinitionPosition::Object(pos) => { @@ -1481,7 +1513,8 @@ fn add_subgraph_input_field( input_field: &InputValueDefinition, subgraph: &mut FederationSubgraph, field_directive_application: Option<&FieldDirectiveArguments>, - original_directive_names: &HashMap<&str, &str>, + cost_spec_definition: Option<&'static CostSpecDefinition>, + original_directive_names: &HashMap, ) -> Result<(), FederationError> { let field_directive_application = field_directive_application.unwrap_or_else(|| &FieldDirectiveArguments { @@ -1506,11 +1539,14 @@ fn add_subgraph_input_field( directives: Default::default(), }; - propagate_demand_control_directives( - &input_field.directives, - &mut subgraph_input_field.directives, - original_directive_names, - ); + if let Some(cost_spec_definition) = cost_spec_definition { + cost_spec_definition.propagate_demand_control_directives( + &subgraph.schema, + &input_field.directives, + &mut subgraph_input_field.directives, + original_directive_names, + )?; + } input_object_field_definition_position .insert(&mut subgraph.schema, Component::from(subgraph_input_field))?; @@ -1552,30 +1588,15 @@ fn get_subgraph<'subgraph>( }) } -fn propagate_demand_control_directives( - source: &apollo_compiler::ast::DirectiveList, - dest: &mut apollo_compiler::ast::DirectiveList, - original_directive_names: &HashMap<&str, &str>, -) { - let cost_directive_name = original_directive_names.get("cost").unwrap_or(&"cost"); - if let Some(cost_directive) = source.get(*cost_directive_name) { - let destination_directive = Directive { - name: name!("federation__cost"), - arguments: cost_directive.arguments.clone(), - }; - dest.push(Node::new(destination_directive)); - } - - let list_size_directive_name = original_directive_names - .get("listSize") - .unwrap_or(&"listSize"); - if let Some(list_size_directive) = source.get(*list_size_directive_name) { - let destination_directive = Directive { - name: name!("federation__listSize"), - arguments: list_size_directive.arguments.clone(), - }; - dest.push(Node::new(destination_directive)); - } +fn get_cost_spec_definition( + schema: &FederationSchema, + federation_spec_definition: &'static FederationSpecDefinition, +) -> Option<&'static CostSpecDefinition> { + schema + .metadata() + .and_then(|metadata| metadata.for_identity(&Identity::cost_identity())) + .and_then(|link| COST_VERSIONS.find(&link.url.version)) + .or_else(|| COST_VERSIONS.find_for_federation_version(federation_spec_definition.version())) } struct FederationSubgraph { From 7f997b56084d4cd925d6073075be5c0a5d90181d Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Mon, 15 Jul 2024 09:07:46 -0500 Subject: [PATCH 03/11] Fix clippy issues --- apollo-federation/src/link/cost_spec_definition.rs | 2 +- apollo-federation/src/link/spec_definition.rs | 2 +- .../src/query_graph/extract_subgraphs_from_supergraph.rs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/apollo-federation/src/link/cost_spec_definition.rs b/apollo-federation/src/link/cost_spec_definition.rs index 8f0af599fd..73907ec1c9 100644 --- a/apollo-federation/src/link/cost_spec_definition.rs +++ b/apollo-federation/src/link/cost_spec_definition.rs @@ -131,7 +131,7 @@ impl CostSpecDefinition { original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); if let Some(list_size_directive) = source.directives.get( list_size_directive_name - .unwrap_or(&&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) + .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) .as_str(), ) { dest.insert_directive( diff --git a/apollo-federation/src/link/spec_definition.rs b/apollo-federation/src/link/spec_definition.rs index b0770bc145..1fb084afe5 100644 --- a/apollo-federation/src/link/spec_definition.rs +++ b/apollo-federation/src/link/spec_definition.rs @@ -183,7 +183,7 @@ impl SpecDefinitions { } pub(crate) fn find_for_federation_version(&self, federation_version: &Version) -> Option<&T> { - for (_, definition) in &self.definitions { + for definition in self.definitions.values() { if let Some(minimum_federation_version) = definition.minimum_federation_version() { if minimum_federation_version >= federation_version { return Some(definition); diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs index 46dbc5a5fe..ccddb8bf11 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -306,8 +306,8 @@ struct TypeInfos { input_object_types: Vec, } -fn get_original_directive_names<'schema>( - supergraph_schema: &'schema FederationSchema, +fn get_original_directive_names( + supergraph_schema: &FederationSchema, ) -> Result, FederationError> { let mut hm = HashMap::new(); for directive in &supergraph_schema.schema().schema_definition.directives { @@ -1391,6 +1391,7 @@ fn extract_input_object_type_content( Ok(()) } +#[allow(clippy::too_many_arguments)] fn add_subgraph_field( object_or_interface_field_definition_position: ObjectOrInterfaceFieldDefinitionPosition, field: &FieldDefinition, From 3ff2674d5419c64deffdaf650ce7bb512e50b7b9 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Thu, 18 Jul 2024 11:52:58 -0500 Subject: [PATCH 04/11] Adjust cost extraction to new composed link structure --- .../extract_subgraphs_from_supergraph.rs | 18 +- apollo-federation/tests/extract_subgraphs.rs | 340 +++++++++--------- ...s__extracts_demand_control_directives.snap | 4 +- ...cts_renamed_demand_control_directives.snap | 4 +- 4 files changed, 181 insertions(+), 185 deletions(-) diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs index ccddb8bf11..916a987159 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -39,9 +39,7 @@ use crate::error::FederationError; use crate::error::MultipleFederationErrors; use crate::error::SingleFederationError; use crate::link::cost_spec_definition::CostSpecDefinition; -use crate::link::cost_spec_definition::COST_DIRECTIVE_NAME_IN_SPEC; use crate::link::cost_spec_definition::COST_VERSIONS; -use crate::link::cost_spec_definition::LIST_SIZE_DIRECTIVE_NAME_IN_SPEC; use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; use crate::link::federation_spec_definition::FederationSpecDefinition; use crate::link::federation_spec_definition::FEDERATION_VERSIONS; @@ -51,6 +49,7 @@ use crate::link::join_spec_definition::TypeDirectiveArguments; use crate::link::spec::Identity; use crate::link::spec::Version; use crate::link::spec_definition::SpecDefinition; +use crate::link::Link; use crate::schema::field_set::parse_field_set_without_normalization; use crate::schema::position::is_graphql_reserved_name; use crate::schema::position::CompositeTypeDefinitionPosition; @@ -235,7 +234,7 @@ pub(crate) fn new_empty_fed_2_subgraph_schema() -> Result Result, FederationError> { - let mut hm = HashMap::new(); + let mut hm: HashMap = HashMap::new(); for directive in &supergraph_schema.schema().schema_definition.directives { if directive.name.as_str() == "link" { - if let (Some(url), Some(new_name)) = ( - directive.argument_by_name("url").and_then(|s| s.as_str()), - directive.argument_by_name("as").and_then(|s| s.as_str()), - ) { - if url.starts_with("https://specs.apollo.dev/cost") { - hm.insert(COST_DIRECTIVE_NAME_IN_SPEC, Name::new(new_name)?); - } else if url.starts_with("https://specs.apollo.dev/listSize") { - hm.insert(LIST_SIZE_DIRECTIVE_NAME_IN_SPEC, Name::new(new_name)?); + if let Ok(link) = Link::from_directive_application(directive) { + for import in link.imports { + hm.insert(import.element.clone(), import.imported_name().clone()); } } } diff --git a/apollo-federation/tests/extract_subgraphs.rs b/apollo-federation/tests/extract_subgraphs.rs index 2452ef8af1..37d603e776 100644 --- a/apollo-federation/tests/extract_subgraphs.rs +++ b/apollo-federation/tests/extract_subgraphs.rs @@ -260,90 +260,91 @@ fn erase_empty_types_due_to_overridden_fields() { fn extracts_demand_control_directives() { let subgraphs = Supergraph::new(r#" 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/cost/v0.1") - @link(url: "https://specs.apollo.dev/listSize/v0.1") - { - query: Query - } - - directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR - - directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - - directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - - 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__graph(name: String!, url: String!) on ENUM_VALUE - - directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE - - 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION - - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION - - enum AorB - @join__type(graph: SUBGRAPHWITHCOST) - @cost(weight: 15) - { - A @join__enumValue(graph: SUBGRAPHWITHCOST) - B @join__enumValue(graph: SUBGRAPHWITHCOST) - } - - input InputTypeWithCost - @join__type(graph: SUBGRAPHWITHCOST) - { - somethingWithCost: Int @cost(weight: 20) - } - - input join__ContextArgument { - name: String! - type: String! - context: String! - selection: join__FieldValue! - } - - scalar join__DirectiveArguments - - scalar join__FieldSet - - scalar join__FieldValue - - enum join__Graph { - SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") - SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") - } - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - type Query - @join__type(graph: SUBGRAPHWITHCOST) - @join__type(graph: SUBGRAPHWITHLISTSIZE) - { - fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @cost(weight: 5) - argWithCost(arg: Int @cost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) - enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) - inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) - fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(assumedSize: 2000, requireOneSlicingArgument: false) - } + @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/cost/v0.1", import: ["@cost", "@listSize"]) + { + query: Query + } + + directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + + directive @cost__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + 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__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + enum AorB + @join__type(graph: SUBGRAPHWITHCOST) + @cost(weight: 15) + { + A @join__enumValue(graph: SUBGRAPHWITHCOST) + B @join__enumValue(graph: SUBGRAPHWITHCOST) + } + + input InputTypeWithCost + @join__type(graph: SUBGRAPHWITHCOST) + { + somethingWithCost: Int @cost(weight: 20) + } + + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } + + scalar join__DirectiveArguments + + scalar join__FieldSet + + scalar join__FieldValue + + enum join__Graph { + SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") + SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SUBGRAPHWITHCOST) + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @cost(weight: 5) + argWithCost(arg: Int @cost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) + enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) + inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) + fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(assumedSize: 2000, requireOneSlicingArgument: false) + } "#) .expect("is supergraph") .extract_subgraphs() @@ -366,91 +367,92 @@ fn extracts_demand_control_directives() { #[test] fn extracts_renamed_demand_control_directives() { let subgraphs = Supergraph::new(r#" - 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/cost/v0.1", as: "renamedCost") - @link(url: "https://specs.apollo.dev/listSize/v0.1", as: "renamedListSize") - { - query: Query - } - - directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - - directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - - 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__graph(name: String!, url: String!) on ENUM_VALUE - - directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE - - 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION - - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - directive @renamedCost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR - - directive @renamedListSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION - - enum AorB - @join__type(graph: SUBGRAPHWITHCOST) - @renamedCost(weight: 15) - { - A @join__enumValue(graph: SUBGRAPHWITHCOST) - B @join__enumValue(graph: SUBGRAPHWITHCOST) - } - - input InputTypeWithCost - @join__type(graph: SUBGRAPHWITHCOST) - { - somethingWithCost: Int @renamedCost(weight: 20) - } - - input join__ContextArgument { - name: String! - type: String! - context: String! - selection: join__FieldValue! - } - - scalar join__DirectiveArguments - - scalar join__FieldSet - - scalar join__FieldValue - - enum join__Graph { - SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") - SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") - } - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - type Query - @join__type(graph: SUBGRAPHWITHCOST) - @join__type(graph: SUBGRAPHWITHLISTSIZE) - { - fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @renamedCost(weight: 5) - argWithCost(arg: Int @renamedCost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) - enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) - inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) - fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false) - } + 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/cost/v0.1", import: [{name: "@cost", as: "@renamedCost"}, {name: "@listSize", as: "@renamedListSize"}]) + { + query: Query + } + + directive @cost__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + 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__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @renamedCost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + + directive @renamedListSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + enum AorB + @join__type(graph: SUBGRAPHWITHCOST) + @renamedCost(weight: 15) + { + A @join__enumValue(graph: SUBGRAPHWITHCOST) + B @join__enumValue(graph: SUBGRAPHWITHCOST) + } + + input InputTypeWithCost + @join__type(graph: SUBGRAPHWITHCOST) + { + somethingWithCost: Int @renamedCost(weight: 20) + } + + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } + + scalar join__DirectiveArguments + + scalar join__FieldSet + + scalar join__FieldValue + + enum join__Graph { + SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") + SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SUBGRAPHWITHCOST) + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @renamedCost(weight: 5) + argWithCost(arg: Int @renamedCost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) + enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) + inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) + fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false) + } "#) .expect("parses") .extract_subgraphs() diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap index 6cda490b52..aa6d026fda 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap @@ -8,7 +8,7 @@ schema { query: Query } -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA @@ -90,7 +90,7 @@ schema { query: Query } -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap index 6cda490b52..aa6d026fda 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap @@ -8,7 +8,7 @@ schema { query: Query } -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA @@ -90,7 +90,7 @@ schema { query: Query } -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA From bf53cacf2c05208c42f7e0cb7a82352c72153923 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Thu, 18 Jul 2024 12:38:39 -0500 Subject: [PATCH 05/11] Test for extracting listSize with dynamic arguments --- apollo-federation/tests/extract_subgraphs.rs | 95 +++++++++++ ...ize_directives_with_dynamic_arguments.snap | 147 ++++++++++++++++++ 2 files changed, 242 insertions(+) create mode 100644 apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap diff --git a/apollo-federation/tests/extract_subgraphs.rs b/apollo-federation/tests/extract_subgraphs.rs index 37d603e776..a5dd03cd1f 100644 --- a/apollo-federation/tests/extract_subgraphs.rs +++ b/apollo-federation/tests/extract_subgraphs.rs @@ -471,3 +471,98 @@ fn extracts_renamed_demand_control_directives() { } insta::assert_snapshot!(snapshot); } + +#[test] +fn extracts_list_size_directives_with_dynamic_arguments() { + let subgraphs = Supergraph::new(r#" + 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/cost/v0.1", import: ["@listSize"]) + { + query: Query + } + + directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + 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__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + type HasInts + @join__type(graph: SUBGRAPH_A) + @join__type(graph: SUBGRAPH_B) + { + ints: [Int!] + } + + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } + + scalar join__DirectiveArguments + + scalar join__FieldSet + + scalar join__FieldValue + + enum join__Graph { + SUBGRAPH_A @join__graph(name: "subgraph-a", url: "") + SUBGRAPH_B @join__graph(name: "subgraph-b", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SUBGRAPH_A) + @join__type(graph: SUBGRAPH_B) + { + sizedList(first: Int!): HasInts @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false) + } + "#) + .expect("parses") + .extract_subgraphs() + .expect("extracts subgraphs"); + + let mut snapshot = String::new(); + for (_name, subgraph) in subgraphs { + use std::fmt::Write; + + _ = writeln!( + &mut snapshot, + "{}\n---\n{}", + subgraph.name, + subgraph.schema.schema() + ); + } + insta::assert_snapshot!(snapshot); +} diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap new file mode 100644 index 0000000000..b8245535fc --- /dev/null +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap @@ -0,0 +1,147 @@ +--- +source: apollo-federation/tests/extract_subgraphs.rs +expression: snapshot +--- +subgraph-a +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!, label: String) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +type HasInts { + ints: [Int!] @federation__shareable +} + +type Query { + sizedList(first: Int!): HasInts @federation__shareable @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false) + _service: _Service! +} + +scalar _Any + +type _Service { + sdl: String +} + +subgraph-b +--- +schema { + query: Query +} + +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") + +directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + +directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE + +directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION + +directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION + +directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA + +directive @federation__extends on OBJECT | INTERFACE + +directive @federation__shareable on OBJECT | FIELD_DEFINITION + +directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION + +directive @federation__override(from: String!, label: String) on FIELD_DEFINITION + +directive @federation__composeDirective(name: String) repeatable on SCHEMA + +directive @federation__interfaceObject on OBJECT + +directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM + +directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + +directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + +scalar link__Import + +enum link__Purpose { + """ + \`SECURITY\` features provide metadata necessary to securely resolve fields. + """ + SECURITY + """ + \`EXECUTION\` features provide metadata necessary for operation execution. + """ + EXECUTION +} + +scalar federation__FieldSet + +scalar federation__Scope + +type HasInts { + ints: [Int!] @federation__shareable +} + +type Query { + sizedList(first: Int!): HasInts @federation__shareable @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false) + _service: _Service! +} + +scalar _Any + +type _Service { + sdl: String +} From 5cb3d4cf6023b425438c1d58c888dcd9582c443e Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Mon, 22 Jul 2024 13:36:02 -0500 Subject: [PATCH 06/11] Include dynamic listSize directive in tests --- apollo-federation/tests/extract_subgraphs.rs | 109 ++----------- ...tract_subgraphs__can_extract_subgraph.snap | 4 +- ...s__extracts_demand_control_directives.snap | 5 + ...ize_directives_with_dynamic_arguments.snap | 147 ------------------ ...cts_renamed_demand_control_directives.snap | 5 + 5 files changed, 26 insertions(+), 244 deletions(-) delete mode 100644 apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap diff --git a/apollo-federation/tests/extract_subgraphs.rs b/apollo-federation/tests/extract_subgraphs.rs index a5dd03cd1f..49c6067d44 100644 --- a/apollo-federation/tests/extract_subgraphs.rs +++ b/apollo-federation/tests/extract_subgraphs.rs @@ -297,6 +297,12 @@ fn extracts_demand_control_directives() { B @join__enumValue(graph: SUBGRAPHWITHCOST) } + type HasInts + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + ints: [Int!] + } + input InputTypeWithCost @join__type(graph: SUBGRAPHWITHCOST) { @@ -344,6 +350,7 @@ fn extracts_demand_control_directives() { enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(assumedSize: 2000, requireOneSlicingArgument: false) + fieldWithDynamicListSize(first: Int!): HasInts @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true) } "#) .expect("is supergraph") @@ -405,6 +412,12 @@ fn extracts_renamed_demand_control_directives() { B @join__enumValue(graph: SUBGRAPHWITHCOST) } + type HasInts + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + ints: [Int!] + } + input InputTypeWithCost @join__type(graph: SUBGRAPHWITHCOST) { @@ -452,6 +465,7 @@ fn extracts_renamed_demand_control_directives() { enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false) + fieldWithDynamicListSize(first: Int!): HasInts @join__field(graph: SUBGRAPHWITHLISTSIZE) @renamedListSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true) } "#) .expect("parses") @@ -471,98 +485,3 @@ fn extracts_renamed_demand_control_directives() { } insta::assert_snapshot!(snapshot); } - -#[test] -fn extracts_list_size_directives_with_dynamic_arguments() { - let subgraphs = Supergraph::new(r#" - 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/cost/v0.1", import: ["@listSize"]) - { - query: Query - } - - directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR - - directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - - directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - - 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__graph(name: String!, url: String!) on ENUM_VALUE - - directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE - - 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION - - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION - - type HasInts - @join__type(graph: SUBGRAPH_A) - @join__type(graph: SUBGRAPH_B) - { - ints: [Int!] - } - - input join__ContextArgument { - name: String! - type: String! - context: String! - selection: join__FieldValue! - } - - scalar join__DirectiveArguments - - scalar join__FieldSet - - scalar join__FieldValue - - enum join__Graph { - SUBGRAPH_A @join__graph(name: "subgraph-a", url: "") - SUBGRAPH_B @join__graph(name: "subgraph-b", url: "") - } - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - type Query - @join__type(graph: SUBGRAPH_A) - @join__type(graph: SUBGRAPH_B) - { - sizedList(first: Int!): HasInts @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false) - } - "#) - .expect("parses") - .extract_subgraphs() - .expect("extracts subgraphs"); - - let mut snapshot = String::new(); - for (_name, subgraph) in subgraphs { - use std::fmt::Write; - - _ = writeln!( - &mut snapshot, - "{}\n---\n{}", - subgraph.name, - subgraph.schema.schema() - ); - } - insta::assert_snapshot!(snapshot); -} diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap index 288620c3d1..324709bd56 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__can_extract_subgraph.snap @@ -8,7 +8,7 @@ schema { query: Query } -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA @@ -89,7 +89,7 @@ schema { query: Query } -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.5") +extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap index aa6d026fda..5e73771e77 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap @@ -141,8 +141,13 @@ scalar federation__FieldSet scalar federation__Scope +type HasInts { + ints: [Int!] +} + type Query { fieldWithListSize: [String!] @federation__listSize(assumedSize: 2000, requireOneSlicingArgument: false) + fieldWithDynamicListSize(first: Int!): HasInts @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true) _service: _Service! } diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap deleted file mode 100644 index b8245535fc..0000000000 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_list_size_directives_with_dynamic_arguments.snap +++ /dev/null @@ -1,147 +0,0 @@ ---- -source: apollo-federation/tests/extract_subgraphs.rs -expression: snapshot ---- -subgraph-a ---- -schema { - query: Query -} - -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") - -directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - -directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE - -directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION - -directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION - -directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION - -directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA - -directive @federation__extends on OBJECT | INTERFACE - -directive @federation__shareable on OBJECT | FIELD_DEFINITION - -directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION - -directive @federation__override(from: String!, label: String) on FIELD_DEFINITION - -directive @federation__composeDirective(name: String) repeatable on SCHEMA - -directive @federation__interfaceObject on OBJECT - -directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - -directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - -directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR - -directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION - -scalar link__Import - -enum link__Purpose { - """ - \`SECURITY\` features provide metadata necessary to securely resolve fields. - """ - SECURITY - """ - \`EXECUTION\` features provide metadata necessary for operation execution. - """ - EXECUTION -} - -scalar federation__FieldSet - -scalar federation__Scope - -type HasInts { - ints: [Int!] @federation__shareable -} - -type Query { - sizedList(first: Int!): HasInts @federation__shareable @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false) - _service: _Service! -} - -scalar _Any - -type _Service { - sdl: String -} - -subgraph-b ---- -schema { - query: Query -} - -extend schema @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") - -directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - -directive @federation__key(fields: federation__FieldSet!, resolvable: Boolean = true) repeatable on OBJECT | INTERFACE - -directive @federation__requires(fields: federation__FieldSet!) on FIELD_DEFINITION - -directive @federation__provides(fields: federation__FieldSet!) on FIELD_DEFINITION - -directive @federation__external(reason: String) on OBJECT | FIELD_DEFINITION - -directive @federation__tag(name: String!) repeatable on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION | SCHEMA - -directive @federation__extends on OBJECT | INTERFACE - -directive @federation__shareable on OBJECT | FIELD_DEFINITION - -directive @federation__inaccessible on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | ARGUMENT_DEFINITION | SCALAR | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION - -directive @federation__override(from: String!, label: String) on FIELD_DEFINITION - -directive @federation__composeDirective(name: String) repeatable on SCHEMA - -directive @federation__interfaceObject on OBJECT - -directive @federation__authenticated on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - -directive @federation__requiresScopes(scopes: [[federation__Scope!]!]!) on FIELD_DEFINITION | OBJECT | INTERFACE | SCALAR | ENUM - -directive @federation__cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR - -directive @federation__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION - -scalar link__Import - -enum link__Purpose { - """ - \`SECURITY\` features provide metadata necessary to securely resolve fields. - """ - SECURITY - """ - \`EXECUTION\` features provide metadata necessary for operation execution. - """ - EXECUTION -} - -scalar federation__FieldSet - -scalar federation__Scope - -type HasInts { - ints: [Int!] @federation__shareable -} - -type Query { - sizedList(first: Int!): HasInts @federation__shareable @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: false) - _service: _Service! -} - -scalar _Any - -type _Service { - sdl: String -} diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap index aa6d026fda..5e73771e77 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap @@ -141,8 +141,13 @@ scalar federation__FieldSet scalar federation__Scope +type HasInts { + ints: [Int!] +} + type Query { fieldWithListSize: [String!] @federation__listSize(assumedSize: 2000, requireOneSlicingArgument: false) + fieldWithDynamicListSize(first: Int!): HasInts @federation__listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true) _service: _Service! } From a7bdb765454e68cf210f6e70e49d069c7c13dee6 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Mon, 22 Jul 2024 13:49:59 -0500 Subject: [PATCH 07/11] Remove unused constants from cost spec --- apollo-federation/src/link/cost_spec_definition.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apollo-federation/src/link/cost_spec_definition.rs b/apollo-federation/src/link/cost_spec_definition.rs index 73907ec1c9..2500ba774b 100644 --- a/apollo-federation/src/link/cost_spec_definition.rs +++ b/apollo-federation/src/link/cost_spec_definition.rs @@ -20,15 +20,9 @@ use crate::schema::FederationSchema; pub(crate) const COST_DIRECTIVE_NAME_IN_SPEC: Name = name!("cost"); pub(crate) const COST_DIRECTIVE_NAME_DEFAULT: Name = name!("federation__cost"); -pub(crate) const COST_WEIGHT_ARGUMENT_NAME: Name = name!("weight"); pub(crate) const LIST_SIZE_DIRECTIVE_NAME_IN_SPEC: Name = name!("listSize"); pub(crate) const LIST_SIZE_DIRECTIVE_NAME_DEFAULT: Name = name!("federation__listSize"); -pub(crate) const LIST_SIZE_ASSUMED_SIZE_ARGUMENT_NAME: Name = name!("assumedSize"); -pub(crate) const LIST_SIZE_SLICING_ARGUMENTS_ARGUMENT_NAME: Name = name!("slicingArguments"); -pub(crate) const LIST_SIZE_SIZED_FIELDS_ARGUMENT_NAME: Name = name!("sizedFields"); -pub(crate) const LIST_SIZE_REQUIRE_ONE_SLICING_ARGUMENT_ARGUMENT_NAME: Name = - name!("requireOneSlicingArgument"); #[derive(Clone)] pub(crate) struct CostSpecDefinition { From 47fe800bea883deef7d8a08085d7192edfe9a91e Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Wed, 31 Jul 2024 12:32:02 -0500 Subject: [PATCH 08/11] Move cost spec definition lookup into FederationSpecDefinition impl --- .../src/link/federation_spec_definition.rs | 13 ++++++++++ .../extract_subgraphs_from_supergraph.rs | 26 +++++-------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/apollo-federation/src/link/federation_spec_definition.rs b/apollo-federation/src/link/federation_spec_definition.rs index 4a1d9da002..67f181ec8b 100644 --- a/apollo-federation/src/link/federation_spec_definition.rs +++ b/apollo-federation/src/link/federation_spec_definition.rs @@ -13,6 +13,8 @@ use crate::error::FederationError; use crate::error::SingleFederationError; use crate::link::argument::directive_optional_boolean_argument; use crate::link::argument::directive_required_string_argument; +use crate::link::cost_spec_definition::CostSpecDefinition; +use crate::link::cost_spec_definition::COST_VERSIONS; use crate::link::spec::Identity; use crate::link::spec::Url; use crate::link::spec::Version; @@ -387,6 +389,17 @@ impl FederationSpecDefinition { arguments, }) } + + pub(crate) fn get_cost_spec_definition( + &self, + schema: &FederationSchema, + ) -> Option<&'static CostSpecDefinition> { + schema + .metadata() + .and_then(|metadata| metadata.for_identity(&Identity::cost_identity())) + .and_then(|link| COST_VERSIONS.find(&link.url.version)) + .or_else(|| COST_VERSIONS.find_for_federation_version(self.version())) + } } impl SpecDefinition for FederationSpecDefinition { diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs index 4026ce788e..4f2ad174dc 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -39,7 +39,6 @@ use crate::error::FederationError; use crate::error::MultipleFederationErrors; use crate::error::SingleFederationError; use crate::link::cost_spec_definition::CostSpecDefinition; -use crate::link::cost_spec_definition::COST_VERSIONS; use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph; use crate::link::federation_spec_definition::FederationSpecDefinition; use crate::link::federation_spec_definition::FEDERATION_VERSIONS; @@ -811,7 +810,7 @@ fn extract_object_type_content( .to_owned(), })?; let cost_spec_definition = - get_cost_spec_definition(&subgraph.schema, federation_spec_definition); + federation_spec_definition.get_cost_spec_definition(&subgraph.schema); add_subgraph_field( field_pos.clone().into(), field, @@ -852,7 +851,7 @@ fn extract_object_type_content( .to_owned(), })?; let cost_spec_definition = - get_cost_spec_definition(&subgraph.schema, federation_spec_definition); + federation_spec_definition.get_cost_spec_definition(&subgraph.schema); if !subgraph_info.contains_key(graph_enum_value) { return Err( SingleFederationError::InvalidFederationSupergraph { @@ -1014,7 +1013,7 @@ fn extract_interface_type_content( .to_owned(), })?; let cost_spec_definition = - get_cost_spec_definition(&subgraph.schema, federation_spec_definition); + federation_spec_definition.get_cost_spec_definition(&subgraph.schema); add_subgraph_field( pos.field(field_name.clone()), field, @@ -1048,7 +1047,7 @@ fn extract_interface_type_content( .to_owned(), })?; let cost_spec_definition = - get_cost_spec_definition(&subgraph.schema, federation_spec_definition); + federation_spec_definition.get_cost_spec_definition(&subgraph.schema); if !subgraph_info.contains_key(graph_enum_value) { return Err( SingleFederationError::InvalidFederationSupergraph { @@ -1206,7 +1205,7 @@ fn extract_enum_type_content( message: "Subgraph unexpectedly does not use federation spec".to_owned(), })?; if let Some(cost_spec_definition) = - get_cost_spec_definition(&subgraph.schema, federation_spec_definition) + federation_spec_definition.get_cost_spec_definition(&subgraph.schema) { cost_spec_definition.propagate_demand_control_directives_for_enum( &mut subgraph.schema, @@ -1326,7 +1325,7 @@ fn extract_input_object_type_content( .to_owned(), })?; let cost_spec_definition = - get_cost_spec_definition(&subgraph.schema, federation_spec_definition); + federation_spec_definition.get_cost_spec_definition(&subgraph.schema); add_subgraph_input_field( input_field_pos.clone(), input_field, @@ -1356,7 +1355,7 @@ fn extract_input_object_type_content( .to_owned(), })?; let cost_spec_definition = - get_cost_spec_definition(&subgraph.schema, federation_spec_definition); + federation_spec_definition.get_cost_spec_definition(&subgraph.schema); if !subgraph_info.contains_key(graph_enum_value) { return Err( SingleFederationError::InvalidFederationSupergraph { @@ -1578,17 +1577,6 @@ fn get_subgraph<'subgraph>( }) } -fn get_cost_spec_definition( - schema: &FederationSchema, - federation_spec_definition: &'static FederationSpecDefinition, -) -> Option<&'static CostSpecDefinition> { - schema - .metadata() - .and_then(|metadata| metadata.for_identity(&Identity::cost_identity())) - .and_then(|link| COST_VERSIONS.find(&link.url.version)) - .or_else(|| COST_VERSIONS.find_for_federation_version(federation_spec_definition.version())) -} - struct FederationSubgraph { name: String, url: String, From 8245686f3d83370f9852e6a86717b78dde77d8ad Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Fri, 2 Aug 2024 11:37:50 -0500 Subject: [PATCH 09/11] Support SCALAR and OBJECT locations for @cost directive extraction --- .../src/link/cost_spec_definition.rs | 79 +++++++ .../extract_subgraphs_from_supergraph.rs | 62 ++++- apollo-federation/tests/extract_subgraphs.rs | 212 ++++++++++-------- ...s__extracts_demand_control_directives.snap | 8 + 4 files changed, 258 insertions(+), 103 deletions(-) diff --git a/apollo-federation/src/link/cost_spec_definition.rs b/apollo-federation/src/link/cost_spec_definition.rs index 2500ba774b..1b624a2fb2 100644 --- a/apollo-federation/src/link/cost_spec_definition.rs +++ b/apollo-federation/src/link/cost_spec_definition.rs @@ -5,6 +5,7 @@ use apollo_compiler::ast::Directive; use apollo_compiler::name; use apollo_compiler::schema::Component; use apollo_compiler::schema::EnumType; +use apollo_compiler::schema::ObjectType; use apollo_compiler::Name; use apollo_compiler::Node; use lazy_static::lazy_static; @@ -16,6 +17,7 @@ use crate::link::spec::Version; use crate::link::spec_definition::SpecDefinition; use crate::link::spec_definition::SpecDefinitions; use crate::schema::position::EnumTypeDefinitionPosition; +use crate::schema::position::ObjectTypeDefinitionPosition; use crate::schema::FederationSchema; pub(crate) const COST_DIRECTIVE_NAME_IN_SPEC: Name = name!("cost"); @@ -100,6 +102,41 @@ impl CostSpecDefinition { Ok(()) } + pub(crate) fn propagate_demand_control_schema_directives( + &self, + subgraph_schema: &FederationSchema, + source: &apollo_compiler::schema::DirectiveList, + dest: &mut apollo_compiler::schema::DirectiveList, + original_directive_names: &HashMap, + ) -> Result<(), FederationError> { + let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); + if let Some(cost_directive) = source.get( + cost_directive_name + .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.push(Component::from(self.cost_directive( + subgraph_schema, + cost_directive.arguments.clone(), + )?)); + } + + let list_size_directive_name = + original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); + if let Some(list_size_directive) = source.get( + list_size_directive_name + .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.push(Component::from(self.list_size_directive( + subgraph_schema, + list_size_directive.arguments.clone(), + )?)); + } + + Ok(()) + } + pub(crate) fn propagate_demand_control_directives_for_enum( &self, subgraph_schema: &mut FederationSchema, @@ -141,6 +178,48 @@ impl CostSpecDefinition { Ok(()) } + + pub(crate) fn propagate_demand_control_directives_for_object( + &self, + subgraph_schema: &mut FederationSchema, + source: &Node, + dest: &ObjectTypeDefinitionPosition, + original_directive_names: &HashMap, + ) -> Result<(), FederationError> { + let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); + if let Some(cost_directive) = source.directives.get( + cost_directive_name + .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.insert_directive( + subgraph_schema, + Component::from( + self.cost_directive(subgraph_schema, cost_directive.arguments.clone())?, + ), + )?; + } + + let list_size_directive_name = + original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); + if let Some(list_size_directive) = source.directives.get( + list_size_directive_name + .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.insert_directive( + subgraph_schema, + Component::from( + self.list_size_directive( + subgraph_schema, + list_size_directive.arguments.clone(), + )?, + ), + )?; + } + + Ok(()) + } } impl SpecDefinition for CostSpecDefinition { diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs index abaaf6c607..43095c1d67 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -332,6 +332,8 @@ fn extract_subgraphs_from_fed_2_supergraph( join_spec_definition: &'static JoinSpecDefinition, filtered_types: &Vec, ) -> Result<(), FederationError> { + let original_directive_names = get_original_directive_names(supergraph_schema)?; + let TypeInfos { object_types, interface_types, @@ -345,10 +347,9 @@ fn extract_subgraphs_from_fed_2_supergraph( federation_spec_definitions, join_spec_definition, filtered_types, + &original_directive_names, )?; - let original_directive_names = get_original_directive_names(supergraph_schema)?; - extract_object_type_content( supergraph_schema, subgraphs, @@ -463,6 +464,7 @@ fn add_all_empty_subgraph_types( federation_spec_definitions: &IndexMap, join_spec_definition: &'static JoinSpecDefinition, filtered_types: &Vec, + original_directive_names: &HashMap, ) -> Result { let type_directive_definition = join_spec_definition.type_directive_definition(supergraph_schema)?; @@ -492,15 +494,32 @@ fn add_all_empty_subgraph_types( graph_enum_value_name_to_subgraph_name, &type_directive_application.graph, )?; + let federation_spec_definition = federation_spec_definitions + .get(&type_directive_application.graph) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec" + .to_owned(), + })?; + let cost_spec_definition = + federation_spec_definition.get_cost_spec_definition(&subgraph.schema); + + let mut subgraph_type = ScalarType { + description: None, + name: pos.type_name.clone(), + directives: Default::default(), + }; + + if let Some(cost_spec_definition) = cost_spec_definition { + cost_spec_definition.propagate_demand_control_schema_directives( + &subgraph.schema, + type_.directives(), + &mut subgraph_type.directives, + original_directive_names, + )?; + } + pos.pre_insert(&mut subgraph.schema)?; - pos.insert( - &mut subgraph.schema, - Node::new(ScalarType { - description: None, - name: pos.type_name.clone(), - directives: Default::default(), - }), - )?; + pos.insert(&mut subgraph.schema, Node::new(subgraph_type))?; } None } @@ -796,6 +815,29 @@ fn extract_object_type_content( )?; } + for graph_enum_value in subgraph_info.keys() { + let subgraph = get_subgraph( + subgraphs, + graph_enum_value_name_to_subgraph_name, + graph_enum_value, + )?; + let federation_spec_definition = federation_spec_definitions + .get(graph_enum_value) + .ok_or_else(|| SingleFederationError::InvalidFederationSupergraph { + message: "Subgraph unexpectedly does not use federation spec".to_owned(), + })?; + if let Some(cost_spec_definition) = + federation_spec_definition.get_cost_spec_definition(&subgraph.schema) + { + cost_spec_definition.propagate_demand_control_directives_for_object( + &mut subgraph.schema, + type_, + &pos, + original_directive_names, + )?; + } + } + for (field_name, field) in type_.fields.iter() { let field_pos = pos.field(field_name.clone()); let mut field_directive_applications = Vec::new(); diff --git a/apollo-federation/tests/extract_subgraphs.rs b/apollo-federation/tests/extract_subgraphs.rs index 49c6067d44..a3316e895d 100644 --- a/apollo-federation/tests/extract_subgraphs.rs +++ b/apollo-federation/tests/extract_subgraphs.rs @@ -259,99 +259,112 @@ fn erase_empty_types_due_to_overridden_fields() { #[test] fn extracts_demand_control_directives() { let subgraphs = Supergraph::new(r#" - 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/cost/v0.1", import: ["@cost", "@listSize"]) - { - query: Query - } - - directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR - - directive @cost__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION - - directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION - - directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE - - 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__graph(name: String!, url: String!) on ENUM_VALUE - - directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE - - 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION - - directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA - - directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION - - enum AorB - @join__type(graph: SUBGRAPHWITHCOST) - @cost(weight: 15) - { - A @join__enumValue(graph: SUBGRAPHWITHCOST) - B @join__enumValue(graph: SUBGRAPHWITHCOST) - } - - type HasInts - @join__type(graph: SUBGRAPHWITHLISTSIZE) - { - ints: [Int!] - } - - input InputTypeWithCost - @join__type(graph: SUBGRAPHWITHCOST) - { - somethingWithCost: Int @cost(weight: 20) - } - - input join__ContextArgument { - name: String! - type: String! - context: String! - selection: join__FieldValue! - } - - scalar join__DirectiveArguments - - scalar join__FieldSet - - scalar join__FieldValue - - enum join__Graph { - SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") - SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") - } - - scalar link__Import - - enum link__Purpose { - """ - `SECURITY` features provide metadata necessary to securely resolve fields. - """ - SECURITY - - """ - `EXECUTION` features provide metadata necessary for operation execution. - """ - EXECUTION - } - - type Query - @join__type(graph: SUBGRAPHWITHCOST) - @join__type(graph: SUBGRAPHWITHLISTSIZE) - { - fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @cost(weight: 5) - argWithCost(arg: Int @cost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) - enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) - inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) - fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(assumedSize: 2000, requireOneSlicingArgument: false) - fieldWithDynamicListSize(first: Int!): HasInts @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true) - } + 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/cost/v0.1", import: ["@cost", "@listSize"]) + { + query: Query + } + + directive @cost(weight: Int!) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR + + directive @cost__listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + directive @join__directive(graphs: [join__Graph!], name: String!, args: join__DirectiveArguments) repeatable on SCHEMA | OBJECT | INTERFACE | FIELD_DEFINITION + + directive @join__enumValue(graph: join__Graph!) repeatable on ENUM_VALUE + + 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__graph(name: String!, url: String!) on ENUM_VALUE + + directive @join__implements(graph: join__Graph!, interface: String!) repeatable on OBJECT | INTERFACE + + 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__unionMember(graph: join__Graph!, member: String!) repeatable on UNION + + directive @link(url: String, as: String, for: link__Purpose, import: [link__Import]) repeatable on SCHEMA + + directive @listSize(assumedSize: Int, slicingArguments: [String!], sizedFields: [String!], requireOneSlicingArgument: Boolean = true) on FIELD_DEFINITION + + enum AorB + @join__type(graph: SUBGRAPHWITHCOST) + @cost(weight: 15) + { + A @join__enumValue(graph: SUBGRAPHWITHCOST) + B @join__enumValue(graph: SUBGRAPHWITHCOST) + } + + scalar ExpensiveInt + @join__type(graph: SUBGRAPHWITHCOST) + @cost(weight: 30) + + type ExpensiveObject + @join__type(graph: SUBGRAPHWITHCOST) + @cost(weight: 40) + { + id: ID + } + + type HasInts + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + ints: [Int!] + } + + input InputTypeWithCost + @join__type(graph: SUBGRAPHWITHCOST) + { + somethingWithCost: Int @cost(weight: 20) + } + + input join__ContextArgument { + name: String! + type: String! + context: String! + selection: join__FieldValue! + } + + scalar join__DirectiveArguments + + scalar join__FieldSet + + scalar join__FieldValue + + enum join__Graph { + SUBGRAPHWITHCOST @join__graph(name: "subgraphWithCost", url: "") + SUBGRAPHWITHLISTSIZE @join__graph(name: "subgraphWithListSize", url: "") + } + + scalar link__Import + + enum link__Purpose { + """ + `SECURITY` features provide metadata necessary to securely resolve fields. + """ + SECURITY + + """ + `EXECUTION` features provide metadata necessary for operation execution. + """ + EXECUTION + } + + type Query + @join__type(graph: SUBGRAPHWITHCOST) + @join__type(graph: SUBGRAPHWITHLISTSIZE) + { + fieldWithCost: Int @join__field(graph: SUBGRAPHWITHCOST) @cost(weight: 5) + argWithCost(arg: Int @cost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) + enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) + inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) + scalarWithCost: ExpensiveInt @join__field(graph: SUBGRAPHWITHCOST) + objectWithCost: ExpensiveObject @join__field(graph: SUBGRAPHWITHCOST) + fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(assumedSize: 2000, requireOneSlicingArgument: false) + fieldWithDynamicListSize(first: Int!): HasInts @join__field(graph: SUBGRAPHWITHLISTSIZE) @listSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true) + } "#) .expect("is supergraph") .extract_subgraphs() @@ -412,6 +425,17 @@ fn extracts_renamed_demand_control_directives() { B @join__enumValue(graph: SUBGRAPHWITHCOST) } + scalar ExpensiveInt + @join__type(graph: SUBGRAPHWITHCOST) + @renamedCost(weight: 30) + + type ExpensiveObject + @join__type(graph: SUBGRAPHWITHCOST) + @renamedCost(weight: 40) + { + id: ID + } + type HasInts @join__type(graph: SUBGRAPHWITHLISTSIZE) { @@ -464,6 +488,8 @@ fn extracts_renamed_demand_control_directives() { argWithCost(arg: Int @renamedCost(weight: 10)): Int @join__field(graph: SUBGRAPHWITHCOST) enumWithCost: AorB @join__field(graph: SUBGRAPHWITHCOST) inputWithCost(someInput: InputTypeWithCost): Int @join__field(graph: SUBGRAPHWITHCOST) + scalarWithCost: ExpensiveInt @join__field(graph: SUBGRAPHWITHCOST) + objectWithCost: ExpensiveObject @join__field(graph: SUBGRAPHWITHCOST) fieldWithListSize: [String!] @join__field(graph: SUBGRAPHWITHLISTSIZE) @renamedListSize(assumedSize: 2000, requireOneSlicingArgument: false) fieldWithDynamicListSize(first: Int!): HasInts @join__field(graph: SUBGRAPHWITHLISTSIZE) @renamedListSize(slicingArguments: ["first"], sizedFields: ["ints"], requireOneSlicingArgument: true) } diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap index 5e73771e77..319b91d908 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_demand_control_directives.snap @@ -64,6 +64,12 @@ enum AorB @federation__cost(weight: 15) { B } +scalar ExpensiveInt @federation__cost(weight: 30) + +type ExpensiveObject @federation__cost(weight: 40) { + id: ID +} + input InputTypeWithCost { somethingWithCost: Int @federation__cost(weight: 20) } @@ -75,6 +81,8 @@ type Query { ): Int enumWithCost: AorB inputWithCost(someInput: InputTypeWithCost): Int + scalarWithCost: ExpensiveInt + objectWithCost: ExpensiveObject _service: _Service! } From d81d92a384cd5d9473ec3819d5aaf1e5afec642a Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Fri, 2 Aug 2024 13:56:06 -0500 Subject: [PATCH 10/11] Update renamed demand control snapshot and DRY up cost spec definition with some macros --- .../src/link/cost_spec_definition.rs | 257 +++++++----------- ...cts_renamed_demand_control_directives.snap | 8 + 2 files changed, 112 insertions(+), 153 deletions(-) diff --git a/apollo-federation/src/link/cost_spec_definition.rs b/apollo-federation/src/link/cost_spec_definition.rs index 1b624a2fb2..57ec91db43 100644 --- a/apollo-federation/src/link/cost_spec_definition.rs +++ b/apollo-federation/src/link/cost_spec_definition.rs @@ -32,6 +32,89 @@ pub(crate) struct CostSpecDefinition { minimum_federation_version: Option, } +macro_rules! propagate_demand_control_directives { + ($func_name:ident, $directives_ty:ty, $wrap_ty:expr) => { + pub(crate) fn $func_name( + &self, + subgraph_schema: &FederationSchema, + source: &$directives_ty, + dest: &mut $directives_ty, + original_directive_names: &HashMap, + ) -> Result<(), FederationError> { + let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); + if let Some(cost_directive) = source.get( + cost_directive_name + .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.push($wrap_ty(self.cost_directive( + subgraph_schema, + cost_directive.arguments.clone(), + )?)); + } + + let list_size_directive_name = + original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); + if let Some(list_size_directive) = source.get( + list_size_directive_name + .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.push($wrap_ty(self.list_size_directive( + subgraph_schema, + list_size_directive.arguments.clone(), + )?)); + } + + Ok(()) + } + }; +} + +macro_rules! propagate_demand_control_directives_to_position { + ($func_name:ident, $source_ty:ty, $dest_ty:ty) => { + pub(crate) fn $func_name( + &self, + subgraph_schema: &mut FederationSchema, + source: &Node<$source_ty>, + dest: &$dest_ty, + original_directive_names: &HashMap, + ) -> Result<(), FederationError> { + let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); + if let Some(cost_directive) = source.directives.get( + cost_directive_name + .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.insert_directive( + subgraph_schema, + Component::from( + self.cost_directive(subgraph_schema, cost_directive.arguments.clone())?, + ), + )?; + } + + let list_size_directive_name = + original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); + if let Some(list_size_directive) = source.directives.get( + list_size_directive_name + .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) + .as_str(), + ) { + dest.insert_directive( + subgraph_schema, + Component::from(self.list_size_directive( + subgraph_schema, + list_size_directive.arguments.clone(), + )?), + )?; + } + + Ok(()) + } + }; +} + impl CostSpecDefinition { pub(crate) fn new(version: Version, minimum_federation_version: Option) -> Self { Self { @@ -67,159 +150,27 @@ impl CostSpecDefinition { Ok(Directive { name, arguments }) } - pub(crate) fn propagate_demand_control_directives( - &self, - subgraph_schema: &FederationSchema, - source: &apollo_compiler::ast::DirectiveList, - dest: &mut apollo_compiler::ast::DirectiveList, - original_directive_names: &HashMap, - ) -> Result<(), FederationError> { - let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); - if let Some(cost_directive) = source.get( - cost_directive_name - .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.push(Node::new(self.cost_directive( - subgraph_schema, - cost_directive.arguments.clone(), - )?)); - } - - let list_size_directive_name = - original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); - if let Some(list_size_directive) = source.get( - list_size_directive_name - .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.push(Node::new(self.list_size_directive( - subgraph_schema, - list_size_directive.arguments.clone(), - )?)); - } - - Ok(()) - } - - pub(crate) fn propagate_demand_control_schema_directives( - &self, - subgraph_schema: &FederationSchema, - source: &apollo_compiler::schema::DirectiveList, - dest: &mut apollo_compiler::schema::DirectiveList, - original_directive_names: &HashMap, - ) -> Result<(), FederationError> { - let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); - if let Some(cost_directive) = source.get( - cost_directive_name - .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.push(Component::from(self.cost_directive( - subgraph_schema, - cost_directive.arguments.clone(), - )?)); - } - - let list_size_directive_name = - original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); - if let Some(list_size_directive) = source.get( - list_size_directive_name - .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.push(Component::from(self.list_size_directive( - subgraph_schema, - list_size_directive.arguments.clone(), - )?)); - } - - Ok(()) - } - - pub(crate) fn propagate_demand_control_directives_for_enum( - &self, - subgraph_schema: &mut FederationSchema, - source: &Node, - dest: &EnumTypeDefinitionPosition, - original_directive_names: &HashMap, - ) -> Result<(), FederationError> { - let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); - if let Some(cost_directive) = source.directives.get( - cost_directive_name - .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.insert_directive( - subgraph_schema, - Component::from( - self.cost_directive(subgraph_schema, cost_directive.arguments.clone())?, - ), - )?; - } - - let list_size_directive_name = - original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); - if let Some(list_size_directive) = source.directives.get( - list_size_directive_name - .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.insert_directive( - subgraph_schema, - Component::from( - self.list_size_directive( - subgraph_schema, - list_size_directive.arguments.clone(), - )?, - ), - )?; - } - - Ok(()) - } - - pub(crate) fn propagate_demand_control_directives_for_object( - &self, - subgraph_schema: &mut FederationSchema, - source: &Node, - dest: &ObjectTypeDefinitionPosition, - original_directive_names: &HashMap, - ) -> Result<(), FederationError> { - let cost_directive_name = original_directive_names.get(&COST_DIRECTIVE_NAME_IN_SPEC); - if let Some(cost_directive) = source.directives.get( - cost_directive_name - .unwrap_or(&COST_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.insert_directive( - subgraph_schema, - Component::from( - self.cost_directive(subgraph_schema, cost_directive.arguments.clone())?, - ), - )?; - } - - let list_size_directive_name = - original_directive_names.get(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC); - if let Some(list_size_directive) = source.directives.get( - list_size_directive_name - .unwrap_or(&LIST_SIZE_DIRECTIVE_NAME_IN_SPEC) - .as_str(), - ) { - dest.insert_directive( - subgraph_schema, - Component::from( - self.list_size_directive( - subgraph_schema, - list_size_directive.arguments.clone(), - )?, - ), - )?; - } - - Ok(()) - } + propagate_demand_control_directives!( + propagate_demand_control_directives, + apollo_compiler::ast::DirectiveList, + Node::new + ); + propagate_demand_control_directives!( + propagate_demand_control_schema_directives, + apollo_compiler::schema::DirectiveList, + Component::from + ); + + propagate_demand_control_directives_to_position!( + propagate_demand_control_directives_for_enum, + EnumType, + EnumTypeDefinitionPosition + ); + propagate_demand_control_directives_to_position!( + propagate_demand_control_directives_for_object, + ObjectType, + ObjectTypeDefinitionPosition + ); } impl SpecDefinition for CostSpecDefinition { diff --git a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap index 5e73771e77..319b91d908 100644 --- a/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap +++ b/apollo-federation/tests/snapshots/main__extract_subgraphs__extracts_renamed_demand_control_directives.snap @@ -64,6 +64,12 @@ enum AorB @federation__cost(weight: 15) { B } +scalar ExpensiveInt @federation__cost(weight: 30) + +type ExpensiveObject @federation__cost(weight: 40) { + id: ID +} + input InputTypeWithCost { somethingWithCost: Int @federation__cost(weight: 20) } @@ -75,6 +81,8 @@ type Query { ): Int enumWithCost: AorB inputWithCost(someInput: InputTypeWithCost): Int + scalarWithCost: ExpensiveInt + objectWithCost: ExpensiveObject _service: _Service! } From 220fa01d7c4f06d1d787ec237f2c9c5b17566b27 Mon Sep 17 00:00:00 2001 From: Taylor Ninesling Date: Fri, 2 Aug 2024 14:09:33 -0500 Subject: [PATCH 11/11] Change to position-based insert for scalar types --- .../src/link/cost_spec_definition.rs | 7 +++++ .../extract_subgraphs_from_supergraph.rs | 31 ++++++++++--------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/apollo-federation/src/link/cost_spec_definition.rs b/apollo-federation/src/link/cost_spec_definition.rs index 57ec91db43..38e1f94619 100644 --- a/apollo-federation/src/link/cost_spec_definition.rs +++ b/apollo-federation/src/link/cost_spec_definition.rs @@ -6,6 +6,7 @@ use apollo_compiler::name; use apollo_compiler::schema::Component; use apollo_compiler::schema::EnumType; use apollo_compiler::schema::ObjectType; +use apollo_compiler::schema::ScalarType; use apollo_compiler::Name; use apollo_compiler::Node; use lazy_static::lazy_static; @@ -18,6 +19,7 @@ use crate::link::spec_definition::SpecDefinition; use crate::link::spec_definition::SpecDefinitions; use crate::schema::position::EnumTypeDefinitionPosition; use crate::schema::position::ObjectTypeDefinitionPosition; +use crate::schema::position::ScalarTypeDefinitionPosition; use crate::schema::FederationSchema; pub(crate) const COST_DIRECTIVE_NAME_IN_SPEC: Name = name!("cost"); @@ -171,6 +173,11 @@ impl CostSpecDefinition { ObjectType, ObjectTypeDefinitionPosition ); + propagate_demand_control_directives_to_position!( + propagate_demand_control_directives_for_scalar, + ScalarType, + ScalarTypeDefinitionPosition + ); } impl SpecDefinition for CostSpecDefinition { diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs index 43095c1d67..23b1b7fbe4 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs @@ -500,26 +500,27 @@ fn add_all_empty_subgraph_types( message: "Subgraph unexpectedly does not use federation spec" .to_owned(), })?; - let cost_spec_definition = - federation_spec_definition.get_cost_spec_definition(&subgraph.schema); - let mut subgraph_type = ScalarType { - description: None, - name: pos.type_name.clone(), - directives: Default::default(), - }; + pos.pre_insert(&mut subgraph.schema)?; + pos.insert( + &mut subgraph.schema, + Node::new(ScalarType { + description: None, + name: pos.type_name.clone(), + directives: Default::default(), + }), + )?; - if let Some(cost_spec_definition) = cost_spec_definition { - cost_spec_definition.propagate_demand_control_schema_directives( - &subgraph.schema, - type_.directives(), - &mut subgraph_type.directives, + if let Some(cost_spec_definition) = + federation_spec_definition.get_cost_spec_definition(&subgraph.schema) + { + cost_spec_definition.propagate_demand_control_directives_for_scalar( + &mut subgraph.schema, + pos.get(supergraph_schema.schema())?, + pos, original_directive_names, )?; } - - pos.pre_insert(&mut subgraph.schema)?; - pos.insert(&mut subgraph.schema, Node::new(subgraph_type))?; } None }