Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
385 changes: 179 additions & 206 deletions apollo-federation/src/connectors/expand/carryover.rs

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion apollo-federation/src/connectors/spec/connect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,7 +487,6 @@ mod tests {
let fields = schema
.referencers()
.get_directive(CONNECT_DIRECTIVE_NAME_IN_SPEC.as_str())
.unwrap()
.object_fields
.iter()
.map(|f| f.get(schema.schema()).unwrap().to_string())
Expand Down
6 changes: 2 additions & 4 deletions apollo-federation/src/connectors/spec/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,7 @@ mod tests {
insta::assert_debug_snapshot!(
subgraph.schema
.referencers()
.get_directive(SOURCE_DIRECTIVE_NAME_IN_SPEC.as_str())
.unwrap(),
.get_directive(SOURCE_DIRECTIVE_NAME_IN_SPEC.as_str()),
@r###"
DirectiveReferencers {
schema: Some(
Expand Down Expand Up @@ -406,8 +405,7 @@ mod tests {
// Extract the sources from the schema definition and map them to their `Source` equivalent
let sources = schema
.referencers()
.get_directive(&SOURCE_DIRECTIVE_NAME_IN_SPEC)
.unwrap();
.get_directive(&SOURCE_DIRECTIVE_NAME_IN_SPEC);

let schema_directive_refs = sources.schema.as_ref().unwrap();
let sources: Result<Vec<_>, _> = schema_directive_refs
Expand Down
28 changes: 28 additions & 0 deletions apollo-federation/src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,14 @@ pub enum SingleFederationError {
InvalidTagName { message: String },
#[error("{message}")]
QueryRootMissing { message: String },
#[error(
"Invalid use of @{directive_name} on {kind} \"{coordinate}\": @{directive_name} cannot be applied on interfaces, interface fields and interface objects"
)]
AuthRequirementsAppliedOnInterface {
directive_name: String,
kind: String,
coordinate: String,
},
}

impl SingleFederationError {
Expand Down Expand Up @@ -1053,6 +1061,9 @@ impl SingleFederationError {
SingleFederationError::InvalidFieldSharing { .. } => ErrorCode::InvalidFieldSharing,
SingleFederationError::InvalidTagName { .. } => ErrorCode::InvalidTagName,
SingleFederationError::QueryRootMissing { .. } => ErrorCode::QueryRootMissing,
SingleFederationError::AuthRequirementsAppliedOnInterface { .. } => {
ErrorCode::AuthRequirementsAppliedOnInterface
}
}
}

Expand Down Expand Up @@ -2372,6 +2383,19 @@ static QUERY_ROOT_MISSING: LazyLock<ErrorCodeDefinition> = LazyLock::new(|| {
)
});

static AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE: LazyLock<ErrorCodeDefinition> = LazyLock::new(
|| {
ErrorCodeDefinition::new(
"AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE".to_owned(),
"The @authenticated, @requiresScopes and @policy directive cannot be applied on interface, interface object or their fields.".to_owned(),
Some(ErrorCodeMetadata {
added_in: "2.9.4",
replaces: &[],
}),
)
},
);

#[derive(Debug, PartialEq, strum_macros::EnumIter)]
pub enum ErrorCode {
ErrorCodeMissing,
Expand Down Expand Up @@ -2474,6 +2498,7 @@ pub enum ErrorCode {
OverrideLabelInvalid,
ContextualArgumentNotContextualInAllSubgraphs,
QueryRootMissing,
AuthRequirementsAppliedOnInterface,
}

impl ErrorCode {
Expand Down Expand Up @@ -2597,6 +2622,9 @@ impl ErrorCode {
&CONTEXTUAL_ARGUMENT_NOT_CONTEXTUAL_IN_ALL_SUBGRAPHS
}
ErrorCode::QueryRootMissing => &QUERY_ROOT_MISSING,
ErrorCode::AuthRequirementsAppliedOnInterface => {
&AUTH_REQUIREMENTS_APPLIED_ON_INTERFACE
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ fn remove_inaccessible_elements(
})?;

// Find all elements that use @inaccessible. Clone so there's no live borrow.
let inaccessible_referencers = schema.referencers().get_directive(&directive_name)?.clone();
let inaccessible_referencers = schema.referencers().get_directive(&directive_name).clone();

// Remove fields and arguments from inaccessible types first. If any inaccessible type has a field
// that references another inaccessible type, it would prevent the other type from being
Expand Down
2 changes: 1 addition & 1 deletion apollo-federation/src/merger/merge_links.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ impl Merger {
let Some(linked_elem) = features.source_link_of_directive(directive) else {
continue;
};
if referencers.len() == 0 {
if referencers.is_empty() {
continue;
}
let source = linked_elem.link;
Expand Down
24 changes: 13 additions & 11 deletions apollo-federation/src/merger/merger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,13 +293,14 @@ impl Merger {
subgraphs
.iter()
.fold(Default::default(), |mut acc, subgraph| {
if let Ok(Some(directive_name)) = subgraph.from_context_directive_name()
&& let Ok(referencers) = subgraph
if let Ok(Some(directive_name)) = subgraph.from_context_directive_name() {
let referencers = subgraph
.schema()
.referencers()
.get_directive(&directive_name)
{
acc.extend(referencers);
.get_directive(&directive_name);
if !referencers.is_empty() {
acc.extend(referencers);
}
}
acc
})
Expand All @@ -311,13 +312,14 @@ impl Merger {
subgraphs
.iter()
.fold(Default::default(), |mut acc, subgraph| {
if let Ok(Some(directive_name)) = subgraph.override_directive_name()
&& let Ok(referencers) = subgraph
if let Ok(Some(directive_name)) = subgraph.override_directive_name() {
let referencers = subgraph
.schema()
.referencers()
.get_directive(&directive_name)
{
acc.extend(referencers);
.get_directive(&directive_name);
if !referencers.is_empty() {
acc.extend(referencers);
}
}
acc
})
Expand Down Expand Up @@ -2489,7 +2491,7 @@ format!("Field \"{field}\" of {} type \"{}\" is defined in some but not all subg
let graph_enum_values: Vec<Name> = graph_enum.values.keys().cloned().collect();

let referencers = self.merged.referencers();
let field_positions = referencers.get_directive(&join_field_directive_name)?;
let field_positions = referencers.get_directive(&join_field_directive_name);
let positions_to_process: Vec<DirectiveTargetPosition> = field_positions.iter().collect();

for pos in positions_to_process {
Expand Down
4 changes: 2 additions & 2 deletions apollo-federation/src/query_graph/build_query_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub fn build_supergraph_api_query_graph(
let join_field_definition = join_spec.field_directive_definition(&supergraph_schema)?;
let join_field_applications = supergraph_schema
.referencers()
.get_directive_applications(&supergraph_schema, &join_field_definition.name)?;
.get_directive_applications(&supergraph_schema, &join_field_definition.name);

let mut override_labels_by_field = IndexMap::default();
for (pos, node) in join_field_applications {
Expand Down Expand Up @@ -2251,7 +2251,7 @@ impl FederatedQueryGraphBuilder {
let subgraph_data = self.subgraphs.get(source)?;
for type_pos in &schema
.referencers()
.get_directive(&subgraph_data.interface_object_directive_definition_name)?
.get_directive(&subgraph_data.interface_object_directive_definition_name)
.object_types
{
// This method is run after handling @provides, so there may be multiple nodes here
Expand Down
8 changes: 7 additions & 1 deletion apollo-federation/src/schema/blueprint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::schema::ValidFederationSchema;
use crate::schema::compute_subgraph_metadata;
use crate::schema::position::DirectiveDefinitionPosition;
use crate::schema::subgraph_metadata::SubgraphMetadata;
use crate::schema::validators::access_control::validate_no_access_control_on_interfaces;
use crate::schema::validators::context::validate_context_directives;
use crate::schema::validators::cost::validate_cost_directives;
use crate::schema::validators::external::validate_external_directives;
Expand Down Expand Up @@ -139,6 +140,7 @@ impl FederationBlueprint {
validate_cost_directives(schema, &mut error_collector)?;
validate_list_size_directives(schema, &mut error_collector)?;
validate_tag_directives(schema, &mut error_collector)?;
validate_no_access_control_on_interfaces(schema, meta, &mut error_collector)?;

error_collector.into_result()
}
Expand Down Expand Up @@ -305,7 +307,11 @@ impl FederationBlueprint {
// definition to re-add the "correct" version, we'd have to re-attach existing applications (doable but not
// done). This assert is so we notice it quickly if that ever happens (again, unlikely, because fed1 schema
// is a backward compatibility thing and there is no reason to expand that too much in the future).
if schema.referencers().get_directive(directive_name)?.len() > 0 {
if !schema
.referencers()
.get_directive(directive_name)
.is_empty()
{
bail!(
"Subgraph has applications of @{directive_name} but we are trying to remove the definition."
);
Expand Down
23 changes: 10 additions & 13 deletions apollo-federation/src/schema/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ impl FederationSchema {
let context_directive_definition = federation_spec.context_directive_definition(self)?;
let context_directive_referencers = self
.referencers()
.get_directive(&context_directive_definition.name)?;
.get_directive(&context_directive_definition.name);

let mut applications = Vec::new();
for interface_type_position in &context_directive_referencers.interface_types {
Expand Down Expand Up @@ -472,7 +472,7 @@ impl FederationSchema {
let context_directive_definition = context_spec.context_directive_definition(self)?;
let context_directive_referencers = self
.referencers()
.get_directive(&context_directive_definition.name)?;
.get_directive(&context_directive_definition.name);
let mut applications = Vec::new();
for type_pos in context_directive_referencers.composite_type_positions() {
let directive_apps =
Expand All @@ -499,7 +499,7 @@ impl FederationSchema {
federation_spec.from_context_directive_definition(self)?;
let from_context_directive_referencers = self
.referencers()
.get_directive(&from_context_directive_definition.name)?;
.get_directive(&from_context_directive_definition.name);

let mut applications = Vec::new();

Expand Down Expand Up @@ -555,7 +555,7 @@ impl FederationSchema {
let key_directive_definition = federation_spec.key_directive_definition(self)?;
let key_directive_referencers = self
.referencers()
.get_directive(&key_directive_definition.name)?;
.get_directive(&key_directive_definition.name);

let mut applications: Vec<Result<KeyDirective, FederationError>> = Vec::new();
for object_type_position in &key_directive_referencers.object_types {
Expand Down Expand Up @@ -617,7 +617,7 @@ impl FederationSchema {
let provides_directive_definition = federation_spec.provides_directive_definition(self)?;
let provides_directive_referencers = self
.referencers()
.get_directive(&provides_directive_definition.name)?;
.get_directive(&provides_directive_definition.name);

let mut applications: Vec<Result<ProvidesDirective, FederationError>> = Vec::new();
for field_definition_position in provides_directive_referencers.object_or_interface_fields()
Expand Down Expand Up @@ -668,7 +668,7 @@ impl FederationSchema {
let requires_directive_definition = federation_spec.requires_directive_definition(self)?;
let requires_directive_referencers = self
.referencers()
.get_directive(&requires_directive_definition.name)?;
.get_directive(&requires_directive_definition.name);

let mut applications = Vec::new();
for field_definition_position in requires_directive_referencers.object_or_interface_fields()
Expand Down Expand Up @@ -716,7 +716,7 @@ impl FederationSchema {
let tag_directive_definition = federation_spec.tag_directive_definition(self)?;
let tag_directive_referencers = self
.referencers()
.get_directive(&tag_directive_definition.name)?;
.get_directive(&tag_directive_definition.name);

let mut applications = Vec::new();
// Schema
Expand Down Expand Up @@ -1026,12 +1026,9 @@ impl FederationSchema {
else {
return Ok(Vec::new());
};
let Ok(list_size_directive_referencers) = self
let list_size_directive_referencers = self
.referencers()
.get_directive(list_size_directive_name.as_str())
else {
return Ok(Vec::new());
};
.get_directive(list_size_directive_name.as_str());

let mut applications = Vec::new();
for field_definition_position in
Expand Down Expand Up @@ -1073,7 +1070,7 @@ impl FederationSchema {

let result = self
.referencers()
.get_directive_applications(self, &cache_tag_directive_definition.name)?
.get_directive_applications(self, &cache_tag_directive_definition.name)
.map(|(pos, application)| {
let arguments = federation_spec.cache_tag_directive_arguments(application);
arguments.map(|args| CacheTagDirective {
Expand Down
27 changes: 12 additions & 15 deletions apollo-federation/src/schema/referencer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use std::fmt;
use std::sync::LazyLock;

use apollo_compiler::Name;
use apollo_compiler::Node;
Expand All @@ -10,7 +11,6 @@ use itertools::Itertools;
use super::FederationSchema;
use crate::error::FederationError;
use crate::error::SingleFederationError;
use crate::internal_error;
use crate::schema::position::CompositeTypeDefinitionPosition;
use crate::schema::position::DirectiveArgumentDefinitionPosition;
use crate::schema::position::DirectiveTargetPosition;
Expand Down Expand Up @@ -181,29 +181,23 @@ impl Referencers {
})
}

pub(crate) fn get_directive(
&self,
name: &str,
) -> Result<&DirectiveReferencers, FederationError> {
self.directives.get(name).ok_or_else(|| {
internal_error!("Directive referencers unexpectedly missing directive `{name}`")
})
pub(crate) fn get_directive(&self, name: &str) -> &DirectiveReferencers {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Federation JS we generally use safe getPost20FederationDirective (link) that returns empty applications (aka referencers in RS) if not found. By changing this to return empty referencers we can avoid a number of potential exceptions when directive would be defined in the schema but not applied anywhere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a big fan of removing as many unnecessary "failure" cases as possible.

self.directives
.get(name)
.unwrap_or_else(|| &EMPTY_REFERENCERS)
}

pub(crate) fn get_directive_applications<'schema>(
&self,
schema: &'schema FederationSchema,
name: &Name,
) -> Result<
impl Iterator<Item = (DirectiveTargetPosition, &'schema Node<ast::Directive>)>,
FederationError,
> {
let directive_referencers = self.get_directive(name)?;
Ok(directive_referencers.iter().flat_map(|pos| {
) -> impl Iterator<Item = (DirectiveTargetPosition, &'schema Node<ast::Directive>)> {
let directive_referencers = self.get_directive(name);
directive_referencers.iter().flat_map(|pos| {
pos.get_applied_directives(schema, name)
.into_iter()
.map(move |directive_application| (pos.clone(), directive_application))
}))
})
}

pub(crate) fn rename_object_type(&mut self, old_name: &Name, new_name: &Name) {
Expand Down Expand Up @@ -525,6 +519,9 @@ impl InputObjectTypeReferencers {
}
}

static EMPTY_REFERENCERS: LazyLock<DirectiveReferencers> =
LazyLock::new(DirectiveReferencers::default);

#[derive(Debug, Clone, Default)]
pub(crate) struct DirectiveReferencers {
pub(crate) schema: Option<SchemaDefinitionPosition>,
Expand Down
8 changes: 4 additions & 4 deletions apollo-federation/src/schema/schema_upgrader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ impl SchemaUpgrader {
) -> Result<(), FederationError> {
// both @provides and @requires will only have an object_fields referencer
for directive_name in ["requires", "provides"] {
let referencers = schema.referencers().get_directive(directive_name)?;
let referencers = schema.referencers().get_directive(directive_name);
for field in &referencers.object_fields.clone() {
let field_type = field.make_mut(&mut schema.schema)?.make_mut();

Expand All @@ -331,7 +331,7 @@ impl SchemaUpgrader {

// now do the exact same thing for @key. The difference is that the directive location will be object_types
// rather than object_fields
let referencers = schema.referencers().get_directive("key")?;
let referencers = schema.referencers().get_directive("key");
for field in &referencers.object_types.clone() {
let field_type = field.make_mut(&mut schema.schema)?.make_mut();

Expand Down Expand Up @@ -689,7 +689,7 @@ impl SchemaUpgrader {
if let Some(key) = &upgrade_metadata.key_directive_name {
for pos in schema
.referencers()
.get_directive(key)?
.get_directive(key)
.interface_types
.clone()
{
Expand Down Expand Up @@ -1098,7 +1098,7 @@ fn is_interface_object_used(subgraph: &Subgraph<Expanded>) -> Result<bool, Feder
let referencers = subgraph
.schema()
.referencers()
.get_directive(interface_object_def.name.as_str())?;
.get_directive(interface_object_def.name.as_str());
if !referencers.object_types.is_empty() {
return Ok(true);
}
Expand Down
Loading