Skip to content
23 changes: 23 additions & 0 deletions apollo-federation/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use apollo_federation::query_plan::query_planner::QueryPlannerConfig;
use apollo_federation::sources::connect::expand::ExpansionResult;
use apollo_federation::sources::connect::expand::expand_connectors;
use apollo_federation::subgraph;
use apollo_federation::subgraph::typestate;
use clap::Parser;
use tracing_subscriber::prelude::*;

Expand Down Expand Up @@ -92,6 +93,11 @@ enum Command {
/// Path(s) to subgraph schemas.
schemas: Vec<PathBuf>,
},
/// Expand and validate a subgraph schema and print the result
Subgraph {
/// The path to the subgraph schema file, or `-` for stdin
subgraph_schema: PathBuf,
},
/// Extract subgraph schemas from a supergraph schema to stdout (or in a directory if specified)
Extract {
/// The path to the supergraph schema file, or `-` for stdin
Expand Down Expand Up @@ -171,6 +177,7 @@ fn main() -> ExitCode {
planner,
} => cmd_plan(&query, &schemas, planner),
Command::Validate { schemas } => cmd_validate(&schemas),
Command::Subgraph { subgraph_schema } => cmd_subgraph(&subgraph_schema),
Command::Compose { schemas } => cmd_compose(&schemas),
Command::Extract {
supergraph_schema,
Expand Down Expand Up @@ -320,6 +327,22 @@ fn cmd_validate(file_paths: &[PathBuf]) -> Result<(), FederationError> {
Ok(())
}

fn cmd_subgraph(file_path: &Path) -> Result<(), FederationError> {
let doc_str = read_input(file_path);
let name = file_path
.file_name()
.and_then(|name| name.to_str().map(|x| x.to_string()));
let name = name.unwrap_or("subgraph".to_string());
let subgraph = typestate::Subgraph::parse(&name, &format!("http://{name}"), &doc_str)
.expect("valid schema")
.expand_links()
.expect("expanded subgraph to be valid")
.validate(true)
.map_err(|e| e.into_inner())?;
println!("{}", subgraph.schema_string());
Ok(())
}

fn cmd_compose(file_paths: &[PathBuf]) -> Result<(), FederationError> {
let supergraph = compose_files(file_paths)?;
println!("{}", supergraph.schema.schema());
Expand Down
7 changes: 7 additions & 0 deletions apollo-federation/src/link/federation_spec_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ impl FederationSpecDefinition {
Self::for_version(latest_version).unwrap()
}

/// Some users rely on auto-expanding fed v1 graphs with fed v2 directives. While technically
/// we should only expand @tag directive from v2 definitions, we will continue expanding other
/// directives (up to v2.4) to ensure backwards compatibility.
pub(crate) fn auto_expanded_federation_spec() -> &'static Self {
Self::for_version(&Version { major: 2, minor: 4 }).unwrap()
}

pub(crate) fn is_fed1(&self) -> bool {
self.version().satisfies(&Version { major: 1, minor: 0 })
}
Expand Down
4 changes: 4 additions & 0 deletions apollo-federation/src/subgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,10 @@ impl SubgraphError {
&self.error
}

pub fn into_inner(self) -> FederationError {
self.error
}

// Format subgraph errors in the same way as `Rover` does.
// And return them as a vector of (error_code, error_message) tuples
// - Gather associated errors from the validation error.
Expand Down
65 changes: 64 additions & 1 deletion apollo-federation/src/subgraph/typestate.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use apollo_compiler::Name;
use apollo_compiler::Node;
use apollo_compiler::Schema;
use apollo_compiler::ast;
use apollo_compiler::collections::IndexSet;
use apollo_compiler::name;
use apollo_compiler::schema::Component;
use apollo_compiler::schema::ComponentName;
use apollo_compiler::schema::Directive;
use apollo_compiler::schema::Type;

use crate::LinkSpecDefinition;
Expand All @@ -15,7 +18,13 @@ use crate::link::federation_spec_definition::FEDERATION_EXTENDS_DIRECTIVE_NAME_I
use crate::link::federation_spec_definition::FEDERATION_KEY_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_PROVIDES_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_REQUIRES_DIRECTIVE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_VERSIONS;
use crate::link::federation_spec_definition::FederationSpecDefinition;
use crate::link::federation_spec_definition::add_fed1_link_to_schema;
use crate::link::link_spec_definition::LINK_DIRECTIVE_IMPORT_ARGUMENT_NAME;
use crate::link::link_spec_definition::LINK_DIRECTIVE_URL_ARGUMENT_NAME;
use crate::link::spec::Identity;
use crate::link::spec::Version;
use crate::link::spec_definition::SpecDefinition;
use crate::schema::FederationSchema;
use crate::schema::blueprint::FederationBlueprint;
Expand Down Expand Up @@ -117,7 +126,7 @@ impl Subgraph<Raw> {
}

pub fn parse(
name: &'static str,
name: &str,
url: &str,
schema_str: &str,
) -> Result<Subgraph<Raw>, FederationError> {
Expand All @@ -129,6 +138,18 @@ impl Subgraph<Raw> {
Ok(Self::new(name, url, schema))
}

/// Converts the schema to a fed2 schema.
/// - It is assumed to have no `@link` to the federation spec.
/// - Returns an equivalent subgraph with a `@link` to the auto expanded federation spec.
/// - This is mainly for testing and not optimized.
// PORT_NOTE: Corresponds to `asFed2SubgraphDocument` function in JS, but simplified.
pub fn into_fed2_subgraph(self) -> Result<Self, FederationError> {
let mut schema = self.state.schema;
let federation_spec = FederationSpecDefinition::auto_expanded_federation_spec();
add_federation_link_to_schema(&mut schema, federation_spec.version())?;
Ok(Self::new(&self.name, &self.url, schema))
}

pub fn assume_expanded(self) -> Result<Subgraph<Expanded>, FederationError> {
let schema = FederationSchema::new(self.state.schema)?;
let metadata = compute_subgraph_metadata(&schema)?.ok_or_else(|| {
Expand Down Expand Up @@ -201,6 +222,48 @@ impl Subgraph<Raw> {
}
}

/// Adds a federation (v2 or above) link directive to the schema.
/// - Similar to `add_fed1_link_to_schema`, but the link is added before bootstrapping.
/// - This is mainly for testing.
fn add_federation_link_to_schema(
schema: &mut Schema,
federation_version: &Version,
) -> Result<(), FederationError> {
let federation_spec = FEDERATION_VERSIONS
.find(federation_version)
.ok_or_else(|| internal_error!(
"Subgraph unexpectedly does not use a supported federation spec version. Requested version: {}",
federation_version,
))?;

// Insert `@link(url: "http://specs.apollo.dev/federation/vX.Y", import: ...)`.
// - auto import all directives.
let imports: Vec<_> = federation_spec
.directive_specs()
.iter()
.map(|d| format!("@{}", d.name()).into())
.collect();

schema
.schema_definition
.make_mut()
.directives
.push(Component::new(Directive {
name: Identity::link_identity().name,
arguments: vec![
Node::new(ast::Argument {
name: LINK_DIRECTIVE_URL_ARGUMENT_NAME,
value: federation_spec.url().to_string().into(),
}),
Node::new(ast::Argument {
name: LINK_DIRECTIVE_IMPORT_ARGUMENT_NAME,
value: Node::new(ast::Value::List(imports)),
}),
],
}));
Ok(())
}

fn add_federation_operations(schema: &mut FederationSchema) -> Result<(), FederationError> {
// Add federation operation types
ANY_TYPE_SPEC.check_or_add(schema, None)?;
Expand Down
Loading