diff --git a/apollo-federation/src/lib.rs b/apollo-federation/src/lib.rs index 92420d2c03..96743c96db 100644 --- a/apollo-federation/src/lib.rs +++ b/apollo-federation/src/lib.rs @@ -30,6 +30,7 @@ pub mod query_plan; pub mod schema; pub mod sources; pub mod subgraph; +mod supergraph; pub(crate) mod utils; use apollo_compiler::ast::NamedType; @@ -47,10 +48,10 @@ use crate::link::spec::Identity; use crate::link::spec_definition::SpecDefinitions; use crate::merge::merge_subgraphs; use crate::merge::MergeFailure; -pub use crate::query_graph::extract_subgraphs_from_supergraph::ValidFederationSubgraph; -pub use crate::query_graph::extract_subgraphs_from_supergraph::ValidFederationSubgraphs; use crate::schema::ValidFederationSchema; use crate::subgraph::ValidSubgraph; +pub use crate::supergraph::ValidFederationSubgraph; +pub use crate::supergraph::ValidFederationSubgraphs; pub(crate) type SupergraphSpecs = (&'static LinkSpecDefinition, &'static JoinSpecDefinition); @@ -129,10 +130,7 @@ impl Supergraph { } pub fn extract_subgraphs(&self) -> Result { - crate::query_graph::extract_subgraphs_from_supergraph::extract_subgraphs_from_supergraph( - &self.schema, - None, - ) + supergraph::extract_subgraphs_from_supergraph(&self.schema, None) } } diff --git a/apollo-federation/src/query_graph/build_query_graph.rs b/apollo-federation/src/query_graph/build_query_graph.rs index 8aca65e9e0..3dd7abbcd6 100644 --- a/apollo-federation/src/query_graph/build_query_graph.rs +++ b/apollo-federation/src/query_graph/build_query_graph.rs @@ -21,7 +21,6 @@ use crate::link::federation_spec_definition::KeyDirectiveArguments; use crate::operation::merge_selection_sets; use crate::operation::Selection; use crate::operation::SelectionSet; -use crate::query_graph::extract_subgraphs_from_supergraph::extract_subgraphs_from_supergraph; use crate::query_graph::QueryGraph; use crate::query_graph::QueryGraphEdge; use crate::query_graph::QueryGraphEdgeTransition; @@ -41,6 +40,7 @@ use crate::schema::position::SchemaRootDefinitionPosition; use crate::schema::position::TypeDefinitionPosition; use crate::schema::position::UnionTypeDefinitionPosition; use crate::schema::ValidFederationSchema; +use crate::supergraph::extract_subgraphs_from_supergraph; /// Builds a "federated" query graph based on the provided supergraph and API schema. /// diff --git a/apollo-federation/src/query_graph/mod.rs b/apollo-federation/src/query_graph/mod.rs index e77d191efa..15e83f49f9 100644 --- a/apollo-federation/src/query_graph/mod.rs +++ b/apollo-federation/src/query_graph/mod.rs @@ -30,7 +30,6 @@ use crate::schema::ValidFederationSchema; pub mod build_query_graph; pub(crate) mod condition_resolver; -pub(crate) mod extract_subgraphs_from_supergraph; pub(crate) mod graph_path; pub mod output; pub(crate) mod path_tree; diff --git a/apollo-federation/src/query_plan/fetch_dependency_graph.rs b/apollo-federation/src/query_plan/fetch_dependency_graph.rs index d14e57bad1..d09c360faa 100644 --- a/apollo-federation/src/query_plan/fetch_dependency_graph.rs +++ b/apollo-federation/src/query_plan/fetch_dependency_graph.rs @@ -44,8 +44,6 @@ use crate::operation::SelectionMap; use crate::operation::SelectionSet; use crate::operation::VariableCollector; use crate::operation::TYPENAME_FIELD; -use crate::query_graph::extract_subgraphs_from_supergraph::FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME; -use crate::query_graph::extract_subgraphs_from_supergraph::FEDERATION_REPRESENTATIONS_VAR_NAME; use crate::query_graph::graph_path::concat_op_paths; use crate::query_graph::graph_path::concat_paths_in_parents; use crate::query_graph::graph_path::OpGraphPathContext; @@ -74,6 +72,8 @@ use crate::schema::position::TypeDefinitionPosition; use crate::schema::ValidFederationSchema; use crate::subgraph::spec::ANY_SCALAR_NAME; use crate::subgraph::spec::ENTITIES_QUERY; +use crate::supergraph::FEDERATION_REPRESENTATIONS_ARGUMENTS_NAME; +use crate::supergraph::FEDERATION_REPRESENTATIONS_VAR_NAME; use crate::utils::logging::snapshot; /// Represents the value of a `@defer(label:)` argument. diff --git a/apollo-federation/src/sources/connect/expand/carryover.rs b/apollo-federation/src/sources/connect/expand/carryover.rs index 980a9af879..53e594bc04 100644 --- a/apollo-federation/src/sources/connect/expand/carryover.rs +++ b/apollo-federation/src/sources/connect/expand/carryover.rs @@ -443,8 +443,8 @@ mod tests { use super::carryover_directives; use crate::merge::merge_federation_subgraphs; - use crate::query_graph::extract_subgraphs_from_supergraph::extract_subgraphs_from_supergraph; use crate::schema::FederationSchema; + use crate::supergraph::extract_subgraphs_from_supergraph; #[test] fn test_carryover() { diff --git a/apollo-federation/src/sources/connect/expand/mod.rs b/apollo-federation/src/sources/connect/expand/mod.rs index 4dbc182c23..80331bb770 100644 --- a/apollo-federation/src/sources/connect/expand/mod.rs +++ b/apollo-federation/src/sources/connect/expand/mod.rs @@ -198,7 +198,6 @@ mod helpers { use crate::error::FederationError; use crate::link::spec::Identity; use crate::link::Link; - use crate::query_graph::extract_subgraphs_from_supergraph::new_empty_fed_2_subgraph_schema; use crate::schema::position::ObjectFieldDefinitionPosition; use crate::schema::position::ObjectOrInterfaceTypeDefinitionPosition; use crate::schema::position::ObjectTypeDefinitionPosition; @@ -217,6 +216,7 @@ mod helpers { use crate::subgraph::spec::EXTERNAL_DIRECTIVE_NAME; use crate::subgraph::spec::KEY_DIRECTIVE_NAME; use crate::subgraph::spec::REQUIRES_DIRECTIVE_NAME; + use crate::supergraph::new_empty_fed_2_subgraph_schema; use crate::ValidFederationSubgraph; /// A helper struct for expanding a subgraph into one per connect directive. diff --git a/apollo-federation/src/sources/connect/models.rs b/apollo-federation/src/sources/connect/models.rs index 422be9ce90..b97a83b95a 100644 --- a/apollo-federation/src/sources/connect/models.rs +++ b/apollo-federation/src/sources/connect/models.rs @@ -231,8 +231,8 @@ mod tests { use insta::assert_debug_snapshot; use super::*; - use crate::query_graph::extract_subgraphs_from_supergraph::extract_subgraphs_from_supergraph; use crate::schema::FederationSchema; + use crate::supergraph::extract_subgraphs_from_supergraph; use crate::ValidFederationSubgraphs; static SIMPLE_SUPERGRAPH: &str = include_str!("./tests/schemas/simple.graphql"); diff --git a/apollo-federation/src/sources/connect/spec/directives.rs b/apollo-federation/src/sources/connect/spec/directives.rs index d33b052902..db1e04da4e 100644 --- a/apollo-federation/src/sources/connect/spec/directives.rs +++ b/apollo-federation/src/sources/connect/spec/directives.rs @@ -389,11 +389,11 @@ mod tests { use apollo_compiler::name; use apollo_compiler::Schema; - use crate::query_graph::extract_subgraphs_from_supergraph::extract_subgraphs_from_supergraph; use crate::schema::FederationSchema; use crate::sources::connect::spec::schema::SourceDirectiveArguments; use crate::sources::connect::spec::schema::CONNECT_DIRECTIVE_NAME_IN_SPEC; use crate::sources::connect::spec::schema::SOURCE_DIRECTIVE_NAME_IN_SPEC; + use crate::supergraph::extract_subgraphs_from_supergraph; use crate::ValidFederationSubgraphs; static SIMPLE_SUPERGRAPH: &str = include_str!("../tests/schemas/simple.graphql"); diff --git a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs b/apollo-federation/src/supergraph/mod.rs similarity index 93% rename from apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs rename to apollo-federation/src/supergraph/mod.rs index 555680d7c8..72ec807dfe 100644 --- a/apollo-federation/src/query_graph/extract_subgraphs_from_supergraph.rs +++ b/apollo-federation/src/supergraph/mod.rs @@ -1,5 +1,6 @@ -use std::collections::BTreeMap; -use std::fmt; +mod schema; +mod subgraph; + use std::fmt::Write; use std::ops::Deref; use std::sync::Arc; @@ -28,7 +29,6 @@ use apollo_compiler::schema::InterfaceType; use apollo_compiler::schema::NamedType; use apollo_compiler::schema::ObjectType; use apollo_compiler::schema::ScalarType; -use apollo_compiler::schema::SchemaBuilder; use apollo_compiler::schema::Type; use apollo_compiler::schema::UnionType; use apollo_compiler::validation::Valid; @@ -36,6 +36,8 @@ use apollo_compiler::Name; use apollo_compiler::Node; use itertools::Itertools; use lazy_static::lazy_static; +pub use subgraph::ValidFederationSubgraph; +pub use subgraph::ValidFederationSubgraphs; use time::OffsetDateTime; use crate::error::FederationError; @@ -50,9 +52,7 @@ use crate::link::join_spec_definition::JoinSpecDefinition; use crate::link::join_spec_definition::TypeDirectiveArguments; use crate::link::spec::Identity; use crate::link::spec::Version; -use crate::link::spec::APOLLO_SPEC_DOMAIN; use crate::link::spec_definition::SpecDefinition; -use crate::link::Link; use crate::link::DEFAULT_LINK_NAME; use crate::schema::field_set::parse_field_set_without_normalization; use crate::schema::position::is_graphql_reserved_name; @@ -77,8 +77,11 @@ use crate::schema::type_and_directive_specification::ScalarTypeSpecification; use crate::schema::type_and_directive_specification::TypeAndDirectiveSpecification; use crate::schema::type_and_directive_specification::UnionTypeSpecification; use crate::schema::FederationSchema; -use crate::schema::ValidFederationSchema; use crate::sources::connect::ConnectSpecDefinition; +use crate::supergraph::schema::get_apollo_directive_names; +pub(crate) use crate::supergraph::schema::new_empty_fed_2_subgraph_schema; +use crate::supergraph::subgraph::FederationSubgraph; +use crate::supergraph::subgraph::FederationSubgraphs; /// Assumes the given schema has been validated. /// @@ -145,16 +148,16 @@ pub(crate) fn extract_subgraphs_from_supergraph( subgraph.schema = schema; if is_fed_1 { let message = - String::from("Supergraphs composed with federation version 1 are not supported. Please recompose your supergraph with federation version 2 or greater"); + String::from("Supergraphs composed with federation version 1 are not supported. Please recompose your supergraph with federation version 2 or greater"); return Err(SingleFederationError::UnsupportedFederationVersion { message, } .into()); } else { let mut message = format!( - "Unexpected error extracting {} from the supergraph: this is either a bug, or the supergraph has been corrupted.\n\nDetails:\n{error}", - subgraph.name, - ); + "Unexpected error extracting {} from the supergraph: this is either a bug, or the supergraph has been corrupted.\n\nDetails:\n{error}", + subgraph.name, + ); maybe_dump_subgraph_schema(subgraph, &mut message); return Err( SingleFederationError::InvalidFederationSupergraph { message }.into(), @@ -232,70 +235,6 @@ fn collect_empty_subgraphs( )) } -/// TODO: Use the JS/programmatic approach instead of hard-coding definitions. -pub(crate) fn new_empty_fed_2_subgraph_schema() -> Result { - let builder = SchemaBuilder::new().adopt_orphan_extensions(); - let builder = builder.parse( - r#" - 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 - - 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 - } - - 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 federation__FieldSet - - scalar federation__Scope - "#, - "subgraph.graphql", - ); - FederationSchema::new(builder.build()?) -} - struct TypeInfo { name: NamedType, // IndexMap @@ -310,43 +249,6 @@ struct TypeInfos { input_object_types: Vec, } -/// Builds a map of original name to new name for Apollo feature directives. This is -/// used to handle cases where a directive is renamed via an import statement. For -/// example, importing a directive with a custom name like -/// ```graphql -/// @link(url: "https://specs.apollo.dev/cost/v0.1", import: [{ name: "@cost", as: "@renamedCost" }]) -/// ``` -/// results in a map entry of `cost -> renamedCost` with the `@` prefix removed. -/// -/// If the directive is imported under its default name, that also results in an entry. So, -/// ```graphql -/// @link(url: "https://specs.apollo.dev/cost/v0.1", import: ["@cost"]) -/// ``` -/// results in a map entry of `cost -> cost`. This duals as a way to check if a directive -/// is included in the supergraph schema. -/// -/// **Important:** This map does _not_ include directives imported from identities other -/// than `specs.apollo.dev`. This helps us avoid extracting directives to subgraphs -/// when a custom directive's name conflicts with that of a default one. -fn get_apollo_directive_names( - supergraph_schema: &FederationSchema, -) -> Result, FederationError> { - let mut hm: HashMap = HashMap::default(); - for directive in &supergraph_schema.schema().schema_definition.directives { - if directive.name.as_str() == "link" { - if let Ok(link) = Link::from_directive_application(directive) { - if link.url.identity.domain != APOLLO_SPEC_DOMAIN { - continue; - } - for import in link.imports { - hm.insert(import.element.clone(), import.imported_name().clone()); - } - } - } - } - Ok(hm) -} - fn extract_subgraphs_from_fed_2_supergraph( supergraph_schema: &FederationSchema, subgraphs: &mut FederationSubgraphs, @@ -1656,105 +1558,6 @@ fn get_subgraph<'subgraph>( }) } -struct FederationSubgraph { - name: String, - url: String, - schema: FederationSchema, -} - -struct FederationSubgraphs { - subgraphs: BTreeMap, -} - -impl FederationSubgraphs { - fn new() -> Self { - FederationSubgraphs { - subgraphs: BTreeMap::new(), - } - } - - fn add(&mut self, subgraph: FederationSubgraph) -> Result<(), FederationError> { - if self.subgraphs.contains_key(&subgraph.name) { - return Err(SingleFederationError::InvalidFederationSupergraph { - message: format!("A subgraph named \"{}\" already exists", subgraph.name), - } - .into()); - } - self.subgraphs.insert(subgraph.name.clone(), subgraph); - Ok(()) - } - - fn get(&self, name: &str) -> Option<&FederationSubgraph> { - self.subgraphs.get(name) - } - - fn get_mut(&mut self, name: &str) -> Option<&mut FederationSubgraph> { - self.subgraphs.get_mut(name) - } -} - -impl IntoIterator for FederationSubgraphs { - type Item = as IntoIterator>::Item; - type IntoIter = as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.subgraphs.into_iter() - } -} - -// TODO(@goto-bus-stop): consider an appropriate name for this in the public API -// TODO(@goto-bus-stop): should this exist separately from the `crate::subgraph::Subgraph` type? -#[derive(Debug, Clone)] -pub struct ValidFederationSubgraph { - pub name: String, - pub url: String, - pub schema: ValidFederationSchema, -} - -pub struct ValidFederationSubgraphs { - subgraphs: BTreeMap, ValidFederationSubgraph>, -} - -impl fmt::Debug for ValidFederationSubgraphs { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("ValidFederationSubgraphs ")?; - f.debug_map().entries(self.subgraphs.iter()).finish() - } -} - -impl ValidFederationSubgraphs { - pub(crate) fn new() -> Self { - ValidFederationSubgraphs { - subgraphs: BTreeMap::new(), - } - } - - pub(crate) fn add(&mut self, subgraph: ValidFederationSubgraph) -> Result<(), FederationError> { - if self.subgraphs.contains_key(subgraph.name.as_str()) { - return Err(SingleFederationError::InvalidFederationSupergraph { - message: format!("A subgraph named \"{}\" already exists", subgraph.name), - } - .into()); - } - self.subgraphs - .insert(subgraph.name.as_str().into(), subgraph); - Ok(()) - } - - pub fn get(&self, name: &str) -> Option<&ValidFederationSubgraph> { - self.subgraphs.get(name) - } -} - -impl IntoIterator for ValidFederationSubgraphs { - type Item = , ValidFederationSubgraph> as IntoIterator>::Item; - type IntoIter = , ValidFederationSubgraph> as IntoIterator>::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.subgraphs.into_iter() - } -} - lazy_static! { static ref EXECUTABLE_DIRECTIVE_LOCATIONS: IndexSet = { [ @@ -2490,8 +2293,8 @@ mod tests { use apollo_compiler::Schema; use insta::assert_snapshot; + use super::*; use crate::schema::FederationSchema; - use crate::ValidFederationSubgraphs; // JS PORT NOTE: these tests were ported from // https://github.com/apollographql/federation/blob/3e2c845c74407a136b9e0066e44c1ad1467d3013/internals-js/src/__tests__/extractSubgraphsFromSupergraph.test.ts @@ -2622,11 +2425,9 @@ mod tests { "#; let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); - let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( - &FederationSchema::new(schema).unwrap(), - Some(true), - ) - .unwrap(); + let ValidFederationSubgraphs { subgraphs } = + extract_subgraphs_from_supergraph(&FederationSchema::new(schema).unwrap(), Some(true)) + .unwrap(); assert_eq!(subgraphs.len(), 3); @@ -2763,11 +2564,9 @@ mod tests { "#; let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); - let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( - &FederationSchema::new(schema).unwrap(), - Some(true), - ) - .unwrap(); + let ValidFederationSubgraphs { subgraphs } = + extract_subgraphs_from_supergraph(&FederationSchema::new(schema).unwrap(), Some(true)) + .unwrap(); assert_eq!(subgraphs.len(), 2); @@ -2911,11 +2710,9 @@ mod tests { "#; let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); - let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( - &FederationSchema::new(schema).unwrap(), - Some(true), - ) - .unwrap(); + let ValidFederationSubgraphs { subgraphs } = + extract_subgraphs_from_supergraph(&FederationSchema::new(schema).unwrap(), Some(true)) + .unwrap(); assert_eq!(subgraphs.len(), 2); @@ -3166,11 +2963,9 @@ mod tests { "###; let schema = Schema::parse(supergraph, "supergraph.graphql").unwrap(); - let ValidFederationSubgraphs { subgraphs } = super::extract_subgraphs_from_supergraph( - &FederationSchema::new(schema).unwrap(), - Some(true), - ) - .unwrap(); + let ValidFederationSubgraphs { subgraphs } = + extract_subgraphs_from_supergraph(&FederationSchema::new(schema).unwrap(), Some(true)) + .unwrap(); let subgraph = subgraphs.get("subgraph").unwrap(); assert_snapshot!(subgraph.schema.schema().schema_definition.directives, @r###" @link(url: "https://specs.apollo.dev/link/v1.0") @link(url: "https://specs.apollo.dev/federation/v2.9") @link(url: "https://specs.apollo.dev/hello/v0.1", import: ["@hello"])"###); diff --git a/apollo-federation/src/supergraph/schema.rs b/apollo-federation/src/supergraph/schema.rs new file mode 100644 index 0000000000..fe7f796ebb --- /dev/null +++ b/apollo-federation/src/supergraph/schema.rs @@ -0,0 +1,109 @@ +use apollo_compiler::collections::HashMap; +use apollo_compiler::schema::SchemaBuilder; +use apollo_compiler::Name; + +use crate::error::FederationError; +use crate::link::spec::APOLLO_SPEC_DOMAIN; +use crate::link::Link; +use crate::schema::FederationSchema; + +/// Builds a map of original name to new name for Apollo feature directives. This is +/// used to handle cases where a directive is renamed via an import statement. For +/// example, importing a directive with a custom name like +/// ```graphql +/// @link(url: "https://specs.apollo.dev/cost/v0.1", import: [{ name: "@cost", as: "@renamedCost" }]) +/// ``` +/// results in a map entry of `cost -> renamedCost` with the `@` prefix removed. +/// +/// If the directive is imported under its default name, that also results in an entry. So, +/// ```graphql +/// @link(url: "https://specs.apollo.dev/cost/v0.1", import: ["@cost"]) +/// ``` +/// results in a map entry of `cost -> cost`. This duals as a way to check if a directive +/// is included in the supergraph schema. +/// +/// **Important:** This map does _not_ include directives imported from identities other +/// than `specs.apollo.dev`. This helps us avoid extracting directives to subgraphs +/// when a custom directive's name conflicts with that of a default one. +pub(super) fn get_apollo_directive_names( + supergraph_schema: &FederationSchema, +) -> Result, FederationError> { + let mut hm: HashMap = HashMap::default(); + for directive in &supergraph_schema.schema().schema_definition.directives { + if directive.name.as_str() == "link" { + if let Ok(link) = Link::from_directive_application(directive) { + if link.url.identity.domain != APOLLO_SPEC_DOMAIN { + continue; + } + for import in link.imports { + hm.insert(import.element.clone(), import.imported_name().clone()); + } + } + } + } + Ok(hm) +} + +/// TODO: Use the JS/programmatic approach instead of hard-coding definitions. +pub(crate) fn new_empty_fed_2_subgraph_schema() -> Result { + let builder = SchemaBuilder::new().adopt_orphan_extensions(); + let builder = builder.parse( + r#" + 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 + + 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 + } + + 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 federation__FieldSet + + scalar federation__Scope + "#, + "subgraph.graphql", + ); + FederationSchema::new(builder.build()?) +} diff --git a/apollo-federation/src/supergraph/subgraph.rs b/apollo-federation/src/supergraph/subgraph.rs new file mode 100644 index 0000000000..7697d3b569 --- /dev/null +++ b/apollo-federation/src/supergraph/subgraph.rs @@ -0,0 +1,107 @@ +use std::collections::BTreeMap; +use std::fmt; +use std::sync::Arc; + +use crate::error::FederationError; +use crate::error::SingleFederationError; +use crate::schema::FederationSchema; +use crate::schema::ValidFederationSchema; + +pub(super) struct FederationSubgraph { + pub(super) name: String, + pub(super) url: String, + pub(super) schema: FederationSchema, +} + +pub(super) struct FederationSubgraphs { + pub(super) subgraphs: BTreeMap, +} + +impl FederationSubgraphs { + pub(super) fn new() -> Self { + FederationSubgraphs { + subgraphs: BTreeMap::new(), + } + } + + pub(super) fn add(&mut self, subgraph: FederationSubgraph) -> Result<(), FederationError> { + if self.subgraphs.contains_key(&subgraph.name) { + return Err(SingleFederationError::InvalidFederationSupergraph { + message: format!("A subgraph named \"{}\" already exists", subgraph.name), + } + .into()); + } + self.subgraphs.insert(subgraph.name.clone(), subgraph); + Ok(()) + } + + fn get(&self, name: &str) -> Option<&FederationSubgraph> { + self.subgraphs.get(name) + } + + pub(super) fn get_mut(&mut self, name: &str) -> Option<&mut FederationSubgraph> { + self.subgraphs.get_mut(name) + } +} + +impl IntoIterator for FederationSubgraphs { + type Item = as IntoIterator>::Item; + type IntoIter = as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.subgraphs.into_iter() + } +} + +// TODO(@goto-bus-stop): consider an appropriate name for this in the public API +// TODO(@goto-bus-stop): should this exist separately from the `crate::subgraph::Subgraph` type? +#[derive(Debug, Clone)] +pub struct ValidFederationSubgraph { + pub name: String, + pub url: String, + pub schema: ValidFederationSchema, +} + +pub struct ValidFederationSubgraphs { + pub(super) subgraphs: BTreeMap, ValidFederationSubgraph>, +} + +impl fmt::Debug for ValidFederationSubgraphs { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("ValidFederationSubgraphs ")?; + f.debug_map().entries(self.subgraphs.iter()).finish() + } +} + +impl ValidFederationSubgraphs { + pub(crate) fn new() -> Self { + ValidFederationSubgraphs { + subgraphs: BTreeMap::new(), + } + } + + pub(crate) fn add(&mut self, subgraph: ValidFederationSubgraph) -> Result<(), FederationError> { + if self.subgraphs.contains_key(subgraph.name.as_str()) { + return Err(SingleFederationError::InvalidFederationSupergraph { + message: format!("A subgraph named \"{}\" already exists", subgraph.name), + } + .into()); + } + self.subgraphs + .insert(subgraph.name.as_str().into(), subgraph); + Ok(()) + } + + pub fn get(&self, name: &str) -> Option<&ValidFederationSubgraph> { + self.subgraphs.get(name) + } +} + +impl IntoIterator for ValidFederationSubgraphs { + type Item = , ValidFederationSubgraph> as IntoIterator>::Item; + type IntoIter = , ValidFederationSubgraph> as IntoIterator>::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.subgraphs.into_iter() + } +}