use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Deref;
use std::ops::Range;
use std::sync::Arc;
use apollo_compiler::Name;
use apollo_compiler::Node;
use apollo_compiler::Schema;
use apollo_compiler::ast::Directive;
use apollo_compiler::ast::FieldDefinition;
use apollo_compiler::ast::Value;
use apollo_compiler::collections::IndexSet;
use apollo_compiler::executable::FieldSet;
use apollo_compiler::parser::LineColumn;
use apollo_compiler::schema::ComponentOrigin;
use apollo_compiler::schema::ExtendedType;
use apollo_compiler::schema::ExtensionId;
use apollo_compiler::schema::SchemaDefinition;
use apollo_compiler::validation::Valid;
use apollo_compiler::validation::WithErrors;
use itertools::Itertools;
use position::DirectiveTargetPosition;
use position::FieldArgumentDefinitionPosition;
use position::ObjectOrInterfaceTypeDefinitionPosition;
use position::TagDirectiveTargetPosition;
use referencer::Referencers;
use crate::bail;
use crate::error::FederationError;
use crate::error::SingleFederationError;
use crate::internal_error;
use crate::link::Link;
use crate::link::LinksMetadata;
use crate::link::context_spec_definition::ContextSpecDefinition;
use crate::link::cost_spec_definition;
use crate::link::cost_spec_definition::CostSpecDefinition;
use crate::link::federation_spec_definition::CacheTagDirectiveArguments;
use crate::link::federation_spec_definition::ComposeDirectiveArguments;
use crate::link::federation_spec_definition::ContextDirectiveArguments;
use crate::link::federation_spec_definition::FEDERATION_ENTITY_TYPE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_FIELDS_ARGUMENT_NAME;
use crate::link::federation_spec_definition::FEDERATION_FIELDSET_TYPE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FEDERATION_SERVICE_TYPE_NAME_IN_SPEC;
use crate::link::federation_spec_definition::FederationSpecDefinition;
use crate::link::federation_spec_definition::FromContextDirectiveArguments;
use crate::link::federation_spec_definition::KeyDirectiveArguments;
use crate::link::federation_spec_definition::ProvidesDirectiveArguments;
use crate::link::federation_spec_definition::RequiresDirectiveArguments;
use crate::link::federation_spec_definition::TagDirectiveArguments;
use crate::link::federation_spec_definition::get_federation_spec_definition_from_subgraph;
use crate::link::spec::Version;
use crate::link::spec_definition::SPEC_REGISTRY;
use crate::link::spec_definition::SpecDefinition;
use crate::schema::position::CompositeTypeDefinitionPosition;
use crate::schema::position::DirectiveDefinitionPosition;
use crate::schema::position::EnumTypeDefinitionPosition;
use crate::schema::position::HasAppliedDirectives;
use crate::schema::position::InputObjectTypeDefinitionPosition;
use crate::schema::position::InterfaceTypeDefinitionPosition;
use crate::schema::position::ObjectOrInterfaceFieldDefinitionPosition;
use crate::schema::position::ObjectTypeDefinitionPosition;
use crate::schema::position::ScalarTypeDefinitionPosition;
use crate::schema::position::TypeDefinitionPosition;
use crate::schema::position::UnionTypeDefinitionPosition;
use crate::schema::subgraph_metadata::SubgraphMetadata;
pub(crate) mod argument_composition_strategies;
pub(crate) mod blueprint;
pub(crate) mod definitions;
pub(crate) mod directive_location;
pub(crate) mod field_set;
pub(crate) mod locations;
pub(crate) mod position;
pub(crate) mod referencer;
pub(crate) mod schema_upgrader;
pub(crate) mod subgraph_metadata;
pub(crate) mod validators;
pub(crate) fn compute_subgraph_metadata(
schema: &FederationSchema,
) -> Result<Option<SubgraphMetadata>, FederationError> {
Ok(
if let Ok(federation_spec_definition) = get_federation_spec_definition_from_subgraph(schema)
{
Some(SubgraphMetadata::new(schema, federation_spec_definition)?)
} else {
None
},
)
}
pub(crate) mod type_and_directive_specification;
#[derive(Clone, Debug)]
pub struct FederationSchema {
schema: Schema,
referencers: Referencers,
links_metadata: Option<Box<LinksMetadata>>,
subgraph_metadata: Option<Box<SubgraphMetadata>>,
}
impl FederationSchema {
pub(crate) fn schema(&self) -> &Schema {
&self.schema
}
pub(crate) fn schema_mut(&mut self) -> &mut Schema {
&mut self.schema
}
pub fn into_inner(self) -> Schema {
self.schema
}
pub(crate) fn metadata(&self) -> Option<&LinksMetadata> {
self.links_metadata.as_deref()
}
pub(crate) fn referencers(&self) -> &Referencers {
&self.referencers
}
pub(crate) fn get_types(&self) -> impl Iterator<Item = TypeDefinitionPosition> {
self.schema
.types
.iter()
.filter(|(_, ty)| !ty.is_built_in())
.map(|(type_name, type_)| {
let type_name = type_name.clone();
match type_ {
ExtendedType::Scalar(_) => ScalarTypeDefinitionPosition { type_name }.into(),
ExtendedType::Object(_) => ObjectTypeDefinitionPosition { type_name }.into(),
ExtendedType::Interface(_) => {
InterfaceTypeDefinitionPosition { type_name }.into()
}
ExtendedType::Union(_) => UnionTypeDefinitionPosition { type_name }.into(),
ExtendedType::Enum(_) => EnumTypeDefinitionPosition { type_name }.into(),
ExtendedType::InputObject(_) => {
InputObjectTypeDefinitionPosition { type_name }.into()
}
}
})
}
pub(crate) fn get_directive_definitions(
&self,
) -> impl Iterator<Item = DirectiveDefinitionPosition> {
self.schema
.directive_definitions
.keys()
.map(|name| DirectiveDefinitionPosition {
directive_name: name.clone(),
})
}
pub(crate) fn get_type(
&self,
type_name: Name,
) -> Result<TypeDefinitionPosition, FederationError> {
let type_ =
self.schema
.types
.get(&type_name)
.ok_or_else(|| SingleFederationError::Internal {
message: format!("Schema has no type \"{type_name}\""),
})?;
Ok(match type_ {
ExtendedType::Scalar(_) => ScalarTypeDefinitionPosition { type_name }.into(),
ExtendedType::Object(_) => ObjectTypeDefinitionPosition { type_name }.into(),
ExtendedType::Interface(_) => InterfaceTypeDefinitionPosition { type_name }.into(),
ExtendedType::Union(_) => UnionTypeDefinitionPosition { type_name }.into(),
ExtendedType::Enum(_) => EnumTypeDefinitionPosition { type_name }.into(),
ExtendedType::InputObject(_) => InputObjectTypeDefinitionPosition { type_name }.into(),
})
}
pub(crate) fn try_get_type(&self, type_name: Name) -> Option<TypeDefinitionPosition> {
self.get_type(type_name).ok()
}
pub(crate) fn is_root_type(&self, type_name: &Name) -> bool {
self.schema()
.schema_definition
.iter_root_operations()
.any(|op| *op.1 == *type_name)
}
pub(crate) fn is_subscription_root_type(&self, type_name: &Name) -> bool {
let subscription = &self.schema().schema_definition.subscription;
subscription.as_ref().is_some_and(|name| name == type_name)
}
pub(crate) fn possible_runtime_types(
&self,
composite_type_definition_position: CompositeTypeDefinitionPosition,
) -> Result<IndexSet<ObjectTypeDefinitionPosition>, FederationError> {
Ok(match composite_type_definition_position {
CompositeTypeDefinitionPosition::Object(pos) => IndexSet::from_iter([pos]),
CompositeTypeDefinitionPosition::Interface(pos) => self
.referencers()
.get_interface_type(&pos.type_name)?
.object_types
.clone(),
CompositeTypeDefinitionPosition::Union(pos) => pos
.get(self.schema())?
.members
.iter()
.map(|t| ObjectTypeDefinitionPosition {
type_name: t.name.clone(),
})
.collect::<IndexSet<_>>(),
})
}
pub(crate) fn all_implementation_types(
&self,
interface_type_definition_position: &InterfaceTypeDefinitionPosition,
) -> Result<IndexSet<ObjectOrInterfaceTypeDefinitionPosition>, FederationError> {
let referencers = self
.referencers()
.get_interface_type(&interface_type_definition_position.type_name)?;
Ok(referencers
.object_types
.iter()
.cloned()
.map(ObjectOrInterfaceTypeDefinitionPosition::from)
.chain(
referencers
.interface_types
.iter()
.cloned()
.map(ObjectOrInterfaceTypeDefinitionPosition::from),
)
.collect())
}
#[allow(clippy::result_large_err)] pub(crate) fn validate_or_return_self(
mut self,
) -> Result<ValidFederationSchema, (Self, FederationError)> {
let schema = match self.schema.validate() {
Ok(schema) => schema.into_inner(),
Err(e) => {
self.schema = e.partial;
return Err((self, e.errors.into()));
}
};
ValidFederationSchema::new_assume_valid(FederationSchema { schema, ..self })
}
pub(crate) fn assume_valid(self) -> Result<ValidFederationSchema, FederationError> {
ValidFederationSchema::new_assume_valid(self).map_err(|(_schema, error)| error)
}
pub(crate) fn get_directive_definition(
&self,
name: &Name,
) -> Option<DirectiveDefinitionPosition> {
self.schema
.directive_definitions
.contains_key(name)
.then(|| DirectiveDefinitionPosition {
directive_name: name.clone(),
})
}
pub(crate) fn entity_type(
&self,
) -> Result<Option<UnionTypeDefinitionPosition>, FederationError> {
match self.schema.types.get(&FEDERATION_ENTITY_TYPE_NAME_IN_SPEC) {
Some(ExtendedType::Union(_)) => Ok(Some(UnionTypeDefinitionPosition {
type_name: FEDERATION_ENTITY_TYPE_NAME_IN_SPEC,
})),
Some(_) => Err(FederationError::internal(format!(
"Unexpectedly found non-union for federation spec's `{FEDERATION_ENTITY_TYPE_NAME_IN_SPEC}` type definition"
))),
None => Ok(None),
}
}
pub(crate) fn service_type(&self) -> Result<ObjectTypeDefinitionPosition, FederationError> {
match self.schema.types.get(&FEDERATION_SERVICE_TYPE_NAME_IN_SPEC) {
Some(ExtendedType::Object(_)) => Ok(ObjectTypeDefinitionPosition {
type_name: FEDERATION_SERVICE_TYPE_NAME_IN_SPEC,
}),
Some(_) => bail!(
"Unexpected type found for federation spec's `{spec_name}` type definition",
spec_name = FEDERATION_SERVICE_TYPE_NAME_IN_SPEC,
),
None => bail!(
"Unexpected: type not found for federation spec's `{spec_name}`",
spec_name = FEDERATION_SERVICE_TYPE_NAME_IN_SPEC,
),
}
}
pub(crate) fn is_fed_2(&self) -> bool {
self.federation_link()
.is_some_and(|link| link.url.version.satisfies(&Version { major: 2, minor: 0 }))
}
fn federation_link(&self) -> Option<&Arc<Link>> {
self.metadata().and_then(|metadata| {
metadata
.by_identity
.get(FederationSpecDefinition::latest().identity())
})
}
pub(crate) fn field_set_type(&self) -> Result<ScalarTypeDefinitionPosition, FederationError> {
let name_in_schema =
self.federation_type_name_in_schema(FEDERATION_FIELDSET_TYPE_NAME_IN_SPEC)?;
match self.schema.types.get(&name_in_schema) {
Some(ExtendedType::Scalar(_)) => Ok(ScalarTypeDefinitionPosition {
type_name: name_in_schema,
}),
Some(_) => bail!(
"Unexpected type found for federation spec's `{name_in_schema}` type definition"
),
None => {
bail!("Unexpected: type not found for federation spec's `{name_in_schema}`")
}
}
}
pub(crate) fn federation_type_name_in_schema(
&self,
name: Name,
) -> Result<Name, FederationError> {
if name.starts_with('_') {
return Ok(name);
}
if self.is_fed_2() {
let Some(links) = self.metadata() else {
bail!("Schema should be a core schema")
};
let Some(federation_link) = links
.by_identity
.get(FederationSpecDefinition::latest().identity())
else {
bail!("Schema should have the latest federation link")
};
Ok(federation_link.type_name_in_schema(&name))
} else {
Name::new(&format!("_{name}"))
.map_err(|e| internal_error!("Invalid name `_{name}`: {e}"))
}
}
pub(crate) fn compose_directive_applications(
&self,
) -> FallibleDirectiveIterator<ComposeDirectiveDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let compose_directive_definition = federation_spec.compose_directive_definition(self)?;
let directives = self
.schema()
.schema_definition
.directives
.get_all(&compose_directive_definition.name)
.map(|d| {
let arguments = federation_spec.compose_directive_arguments(d);
arguments.map(|args| ComposeDirectiveDirective { arguments: args })
})
.collect();
Ok(directives)
}
pub(crate) fn context_directive_applications(
&self,
) -> FallibleDirectiveIterator<ContextDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let context_directive_definition = federation_spec.context_directive_definition(self)?;
let context_directive_referencers = self
.referencers()
.get_directive(&context_directive_definition.name);
let mut applications = Vec::new();
for interface_type_position in &context_directive_referencers.interface_types {
match interface_type_position.get(self.schema()) {
Ok(interface_type) => {
let directives = &interface_type.directives;
for directive in directives.get_all(&context_directive_definition.name) {
let arguments = federation_spec.context_directive_arguments(directive);
applications.push(arguments.map(|args| ContextDirective {
arguments: args,
target: interface_type_position.clone().into(),
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for object_type_position in &context_directive_referencers.object_types {
match object_type_position.get(self.schema()) {
Ok(object_type) => {
let directives = &object_type.directives;
for directive in directives.get_all(&context_directive_definition.name) {
let arguments = federation_spec.context_directive_arguments(directive);
applications.push(arguments.map(|args| ContextDirective {
arguments: args,
target: object_type_position.clone().into(),
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for union_type_position in &context_directive_referencers.union_types {
match union_type_position.get(self.schema()) {
Ok(union_type) => {
let directives = &union_type.directives;
for directive in directives.get_all(&context_directive_definition.name) {
let arguments = federation_spec.context_directive_arguments(directive);
applications.push(arguments.map(|args| ContextDirective {
arguments: args,
target: union_type_position.clone().into(),
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
Ok(applications)
}
pub(crate) fn context_directive_applications_in_supergraph(
&self,
context_spec: &ContextSpecDefinition,
) -> FallibleDirectiveIterator<ContextDirective<'_>> {
let context_directive_definition = context_spec.context_directive_definition(self)?;
let context_directive_referencers = self
.referencers()
.get_directive(&context_directive_definition.name);
let mut applications = Vec::new();
for type_pos in context_directive_referencers.composite_type_positions() {
let directive_apps =
type_pos.get_applied_directives(self, &context_directive_definition.name);
for app in directive_apps {
let arguments = context_spec.context_directive_arguments(app);
applications.push(arguments.map(|args| ContextDirective {
arguments: ContextDirectiveArguments { name: args.name },
target: type_pos.clone(),
}));
}
}
Ok(applications)
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn from_context_directive_applications(
&self,
) -> FallibleDirectiveIterator<FromContextDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let from_context_directive_definition =
federation_spec.from_context_directive_definition(self)?;
let from_context_directive_referencers = self
.referencers()
.get_directive(&from_context_directive_definition.name);
let mut applications = Vec::new();
for directive_argument_position in &from_context_directive_referencers.directive_arguments {
applications.push(Err(SingleFederationError::ContextNotSet {
message: format!(
"@fromContext argument cannot be used on a directive definition argument \"{}\".",
directive_argument_position
),
}
.into()));
}
for interface_field_argument_position in
&from_context_directive_referencers.interface_field_arguments
{
match interface_field_argument_position.get(self.schema()) {
Ok(interface_field_argument) => {
let directives = &interface_field_argument.directives;
for directive in directives.get_all(&from_context_directive_definition.name) {
let arguments = federation_spec.from_context_directive_arguments(directive);
applications.push(arguments.map(|args| FromContextDirective {
arguments: args,
target: interface_field_argument_position.clone().into(),
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for object_field_argument_position in
&from_context_directive_referencers.object_field_arguments
{
match object_field_argument_position.get(self.schema()) {
Ok(object_field_argument) => {
let directives = &object_field_argument.directives;
for directive in directives.get_all(&from_context_directive_definition.name) {
let arguments = federation_spec.from_context_directive_arguments(directive);
applications.push(arguments.map(|args| FromContextDirective {
arguments: args,
target: object_field_argument_position.clone().into(),
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
Ok(applications)
}
pub(crate) fn key_directive_applications(&self) -> FallibleDirectiveIterator<KeyDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let key_directive_definition = federation_spec.key_directive_definition(self)?;
let key_directive_referencers = self
.referencers()
.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 {
match object_type_position.get(self.schema()) {
Ok(object_type) => {
let directives = &object_type.directives;
for directive in directives.get_all(&key_directive_definition.name) {
if !matches!(
directive
.argument_by_name(&FEDERATION_FIELDS_ARGUMENT_NAME, self.schema())
.map(|arg| arg.as_ref()),
Ok(Value::String(_)),
) {
applications.push(Err(SingleFederationError::KeyInvalidFieldsType {
target_type: object_type_position.type_name.clone(),
application: directive.to_string(),
}
.into()))
} else {
let arguments = federation_spec.key_directive_arguments(directive);
applications.push(arguments.map(|args| KeyDirective {
arguments: args,
schema_directive: directive,
sibling_directives: directives,
target: object_type_position.clone().into(),
}));
}
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for interface_type_position in &key_directive_referencers.interface_types {
match interface_type_position.get(self.schema()) {
Ok(interface_type) => {
let directives = &interface_type.directives;
for directive in directives.get_all(&key_directive_definition.name) {
let arguments = federation_spec.key_directive_arguments(directive);
applications.push(arguments.map(|args| KeyDirective {
arguments: args,
schema_directive: directive,
sibling_directives: directives,
target: interface_type_position.clone().into(),
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
Ok(applications)
}
pub(crate) fn provides_directive_applications(
&self,
) -> FallibleDirectiveIterator<ProvidesDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let provides_directive_definition = federation_spec.provides_directive_definition(self)?;
let provides_directive_referencers = self
.referencers()
.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()
{
match field_definition_position.get(self.schema()) {
Ok(field_definition) => {
let directives = &field_definition.directives;
for provides_directive_application in
directives.get_all(&provides_directive_definition.name)
{
if !matches!(
provides_directive_application
.argument_by_name(&FEDERATION_FIELDS_ARGUMENT_NAME, self.schema())
.map(|arg| arg.as_ref()),
Ok(Value::String(_)),
) {
applications.push(Err(
SingleFederationError::ProvidesInvalidFieldsType {
coordinate: field_definition_position.coordinate(),
application: provides_directive_application.to_string(),
}
.into(),
))
} else {
let arguments = federation_spec
.provides_directive_arguments(provides_directive_application);
applications.push(arguments.map(|args| ProvidesDirective {
arguments: args,
schema_directive: provides_directive_application,
target: field_definition_position.clone(),
target_return_type: field_definition.ty.inner_named_type(),
}));
}
}
}
Err(error) => applications.push(Err(error.into())),
}
}
Ok(applications)
}
pub(crate) fn requires_directive_applications(
&self,
) -> FallibleDirectiveIterator<RequiresDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let requires_directive_definition = federation_spec.requires_directive_definition(self)?;
let requires_directive_referencers = self
.referencers()
.get_directive(&requires_directive_definition.name);
let mut applications = Vec::new();
for field_definition_position in requires_directive_referencers.object_or_interface_fields()
{
match field_definition_position.get(self.schema()) {
Ok(field_definition) => {
let directives = &field_definition.directives;
for requires_directive_application in
directives.get_all(&requires_directive_definition.name)
{
if !matches!(
requires_directive_application
.argument_by_name(&FEDERATION_FIELDS_ARGUMENT_NAME, self.schema())
.map(|arg| arg.as_ref()),
Ok(Value::String(_)),
) {
applications.push(Err(
SingleFederationError::RequiresInvalidFieldsType {
coordinate: field_definition_position.coordinate(),
application: requires_directive_application.to_string(),
}
.into(),
))
} else {
let arguments = federation_spec
.requires_directive_arguments(requires_directive_application);
applications.push(arguments.map(|args| RequiresDirective {
arguments: args,
schema_directive: requires_directive_application,
target: field_definition_position.clone(),
}));
}
}
}
Err(error) => applications.push(Err(error.into())),
}
}
Ok(applications)
}
pub(crate) fn tag_directive_applications(&self) -> FallibleDirectiveIterator<TagDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let tag_directive_definition = federation_spec.tag_directive_definition(self)?;
let tag_directive_referencers = self
.referencers()
.get_directive(&tag_directive_definition.name);
let mut applications = Vec::new();
if let Some(schema_position) = &tag_directive_referencers.schema {
let schema_def = schema_position.get(self.schema());
let directives = &schema_def.directives;
for tag_directive_application in directives.get_all(&tag_directive_definition.name) {
let arguments = federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::Schema(schema_position.clone()),
directive: tag_directive_application,
}));
}
}
for interface_type_position in &tag_directive_referencers.interface_types {
match interface_type_position.get(self.schema()) {
Ok(interface_type) => {
let directives = &interface_type.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::Interface(
interface_type_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for field_definition_position in &tag_directive_referencers.interface_fields {
match field_definition_position.get(self.schema()) {
Ok(field_definition) => {
let directives = &field_definition.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::InterfaceField(
field_definition_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for argument_definition_position in &tag_directive_referencers.interface_field_arguments {
match argument_definition_position.get(self.schema()) {
Ok(argument_definition) => {
let directives = &argument_definition.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::ArgumentDefinition(
argument_definition_position.clone().into(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for object_type_position in &tag_directive_referencers.object_types {
match object_type_position.get(self.schema()) {
Ok(object_type) => {
let directives = &object_type.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::Object(
object_type_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for field_definition_position in &tag_directive_referencers.object_fields {
match field_definition_position.get(self.schema()) {
Ok(field_definition) => {
let directives = &field_definition.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::ObjectField(
field_definition_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for argument_definition_position in &tag_directive_referencers.object_field_arguments {
match argument_definition_position.get(self.schema()) {
Ok(argument_definition) => {
let directives = &argument_definition.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::ArgumentDefinition(
argument_definition_position.clone().into(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for union_type_position in &tag_directive_referencers.union_types {
match union_type_position.get(self.schema()) {
Ok(union_type) => {
let directives = &union_type.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::Union(union_type_position.clone()),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for scalar_type_position in &tag_directive_referencers.scalar_types {
match scalar_type_position.get(self.schema()) {
Ok(scalar_type) => {
let directives = &scalar_type.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::Scalar(
scalar_type_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for enum_type_position in &tag_directive_referencers.enum_types {
match enum_type_position.get(self.schema()) {
Ok(enum_type) => {
let directives = &enum_type.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::Enum(enum_type_position.clone()),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for enum_value_position in &tag_directive_referencers.enum_values {
match enum_value_position.get(self.schema()) {
Ok(enum_value) => {
let directives = &enum_value.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::EnumValue(
enum_value_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for input_object_type_position in &tag_directive_referencers.input_object_types {
match input_object_type_position.get(self.schema()) {
Ok(input_object_type) => {
let directives = &input_object_type.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::InputObject(
input_object_type_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for input_field_definition_position in &tag_directive_referencers.input_object_fields {
match input_field_definition_position.get(self.schema()) {
Ok(input_field_definition) => {
let directives = &input_field_definition.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::InputObjectFieldDefinition(
input_field_definition_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
for directive_definition_position in &tag_directive_referencers.directive_arguments {
match directive_definition_position.get(self.schema()) {
Ok(directive_definition) => {
let directives = &directive_definition.directives;
for tag_directive_application in
directives.get_all(&tag_directive_definition.name)
{
let arguments =
federation_spec.tag_directive_arguments(tag_directive_application);
applications.push(arguments.map(|args| TagDirective {
arguments: args,
target: TagDirectiveTargetPosition::DirectiveArgumentDefinition(
directive_definition_position.clone(),
),
directive: tag_directive_application,
}));
}
}
Err(error) => applications.push(Err(error.into())),
}
}
Ok(applications)
}
pub(crate) fn list_size_directive_applications(
&self,
) -> FallibleDirectiveIterator<ListSizeDirective<'_>> {
let Some(list_size_directive_name) = CostSpecDefinition::list_size_directive_name(self)?
else {
return Ok(Vec::new());
};
let list_size_directive_referencers = self
.referencers()
.get_directive(list_size_directive_name.as_str());
let mut applications = Vec::new();
for field_definition_position in
list_size_directive_referencers.object_or_interface_fields()
{
let field_definition = field_definition_position.get(self.schema())?;
match CostSpecDefinition::list_size_directive_from_field_definition(
self,
field_definition,
) {
Ok(Some(list_size_directive)) => {
applications.push(Ok(ListSizeDirective {
directive: list_size_directive,
parent_type: field_definition_position.type_name().clone(),
target: field_definition,
}));
}
Ok(None) => {
}
Err(error) => {
applications.push(Err(error));
}
}
}
Ok(applications)
}
pub(crate) fn cache_tag_directive_applications(
&self,
) -> FallibleDirectiveIterator<CacheTagDirective<'_>> {
let federation_spec = get_federation_spec_definition_from_subgraph(self)?;
let Ok(cache_tag_directive_definition) =
federation_spec.cache_tag_directive_definition(self)
else {
return Ok(Vec::new());
};
let result = self
.referencers()
.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 {
arguments: args,
target: pos,
})
})
.collect();
Ok(result)
}
pub(crate) fn is_interface(&self, type_name: &Name) -> bool {
self.referencers().interface_types.contains_key(type_name)
}
pub(crate) fn all_features(&self) -> Result<Vec<&'static dyn SpecDefinition>, FederationError> {
let Some(links) = self.metadata() else {
return Ok(Vec::new());
};
let mut features: Vec<&'static dyn SpecDefinition> =
Vec::with_capacity(links.all_links().len());
for link in links.all_links() {
if let Some(spec) = SPEC_REGISTRY.get_definition(&link.url) {
features.push(*spec);
} else if let Some(supported_versions) = SPEC_REGISTRY.get_versions(&link.url.identity)
{
return Err(
SingleFederationError::UnknownLinkVersion {
message: format!(
"Detected unsupported {} specification version {}. Please upgrade to a composition version which supports that version, or select one of the following supported versions: {}.",
link.url.identity.name,
link.url.version,
supported_versions.iter().join(", ")
),
}.into());
}
}
Ok(features)
}
pub(crate) fn node_locations<T>(
&self,
node: &Node<T>,
) -> impl Iterator<Item = Range<LineColumn>> {
node.line_column_range(&self.schema().sources).into_iter()
}
}
type FallibleDirectiveIterator<D> = Result<Vec<Result<D, FederationError>>, FederationError>;
#[derive(Clone)]
pub(crate) struct ComposeDirectiveDirective<'schema> {
pub(crate) arguments: ComposeDirectiveArguments<'schema>,
}
pub(crate) struct ContextDirective<'schema> {
arguments: ContextDirectiveArguments<'schema>,
target: CompositeTypeDefinitionPosition,
}
impl ContextDirective<'_> {
pub(crate) fn arguments(&self) -> &ContextDirectiveArguments<'_> {
&self.arguments
}
pub(crate) fn target(&self) -> &CompositeTypeDefinitionPosition {
&self.target
}
}
pub(crate) struct FromContextDirective<'schema> {
arguments: FromContextDirectiveArguments<'schema>,
target: FieldArgumentDefinitionPosition,
}
pub(crate) struct KeyDirective<'schema> {
arguments: KeyDirectiveArguments<'schema>,
schema_directive: &'schema apollo_compiler::schema::Component<Directive>,
sibling_directives: &'schema apollo_compiler::schema::DirectiveList,
target: ObjectOrInterfaceTypeDefinitionPosition,
}
impl HasFields for KeyDirective<'_> {
fn fields(&self) -> &str {
self.arguments.fields
}
fn target_type(&self) -> &Name {
self.target.type_name()
}
}
impl KeyDirective<'_> {
pub(crate) fn target(&self) -> &ObjectOrInterfaceTypeDefinitionPosition {
&self.target
}
}
pub(crate) struct ListSizeDirective<'schema> {
directive: cost_spec_definition::ListSizeDirective,
parent_type: Name,
target: &'schema FieldDefinition,
}
pub(crate) struct ProvidesDirective<'schema> {
arguments: ProvidesDirectiveArguments<'schema>,
schema_directive: &'schema Node<Directive>,
target: ObjectOrInterfaceFieldDefinitionPosition,
target_return_type: &'schema Name,
}
impl HasFields for ProvidesDirective<'_> {
fn fields(&self) -> &str {
self.arguments.fields
}
fn target_type(&self) -> &Name {
self.target_return_type
}
}
pub(crate) struct RequiresDirective<'schema> {
arguments: RequiresDirectiveArguments<'schema>,
schema_directive: &'schema Node<Directive>,
target: ObjectOrInterfaceFieldDefinitionPosition,
}
impl HasFields for RequiresDirective<'_> {
fn fields(&self) -> &str {
self.arguments.fields
}
fn target_type(&self) -> &Name {
self.target.type_name()
}
}
pub(crate) struct TagDirective<'schema> {
arguments: TagDirectiveArguments<'schema>,
target: TagDirectiveTargetPosition, directive: &'schema Node<Directive>,
}
pub(crate) struct CacheTagDirective<'schema> {
arguments: CacheTagDirectiveArguments<'schema>,
target: DirectiveTargetPosition,
}
pub(crate) trait HasFields {
fn fields(&self) -> &str;
fn target_type(&self) -> &Name;
fn parse_fields(&self, schema: &Schema) -> Result<FieldSet, WithErrors<FieldSet>> {
FieldSet::parse(
Valid::assume_valid_ref(schema),
self.target_type().clone(),
self.fields(),
"field_set.graphql",
)
}
}
#[derive(Clone)]
pub struct ValidFederationSchema {
schema: Arc<Valid<FederationSchema>>,
}
impl ValidFederationSchema {
pub fn new(schema: Valid<Schema>) -> Result<ValidFederationSchema, FederationError> {
let schema = FederationSchema::new(schema.into_inner())?;
Self::new_assume_valid(schema).map_err(|(_schema, error)| error)
}
#[allow(clippy::result_large_err)] pub fn new_assume_valid(
mut schema: FederationSchema,
) -> Result<ValidFederationSchema, (FederationSchema, FederationError)> {
let valid_schema = Valid::assume_valid_ref(&schema);
let subgraph_metadata = match compute_subgraph_metadata(valid_schema) {
Ok(metadata) => metadata.map(Box::new),
Err(err) => return Err((schema, err)),
};
schema.subgraph_metadata = subgraph_metadata;
let schema = Arc::new(Valid::assume_valid(schema));
Ok(ValidFederationSchema { schema })
}
pub fn schema(&self) -> &Valid<Schema> {
Valid::assume_valid_ref(&self.schema.schema)
}
pub(crate) fn subgraph_metadata(&self) -> Option<&SubgraphMetadata> {
self.schema.subgraph_metadata.as_deref()
}
pub(crate) fn federation_type_name_in_schema(
&self,
name: Name,
) -> Result<Name, FederationError> {
if name.starts_with('_') {
return Ok(name);
}
Err(FederationError::internal(
"typename should have been looked in a federation feature",
))
}
pub(crate) fn is_interface_object_type(
&self,
type_definition_position: TypeDefinitionPosition,
) -> Result<bool, FederationError> {
let Some(subgraph_metadata) = &self.subgraph_metadata else {
return Ok(false);
};
let Some(interface_object_directive_definition) = subgraph_metadata
.federation_spec_definition()
.interface_object_directive_definition(self)?
else {
return Ok(false);
};
match type_definition_position {
TypeDefinitionPosition::Object(type_) => Ok(type_
.get(self.schema())?
.directives
.has(&interface_object_directive_definition.name)),
_ => Ok(false),
}
}
}
impl Deref for ValidFederationSchema {
type Target = FederationSchema;
fn deref(&self) -> &Self::Target {
&self.schema
}
}
impl Eq for ValidFederationSchema {}
impl PartialEq for ValidFederationSchema {
fn eq(&self, other: &ValidFederationSchema) -> bool {
Arc::ptr_eq(&self.schema, &other.schema)
}
}
impl Hash for ValidFederationSchema {
fn hash<H: Hasher>(&self, state: &mut H) {
Arc::as_ptr(&self.schema).hash(state);
}
}
impl std::fmt::Debug for ValidFederationSchema {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ValidFederationSchema @ {:?}", Arc::as_ptr(&self.schema))
}
}
impl From<ValidFederationSchema> for FederationSchema {
fn from(value: ValidFederationSchema) -> Self {
Arc::unwrap_or_clone(value.schema).into_inner()
}
}
pub(crate) trait SchemaElement {
fn iter_origins(&self) -> impl Iterator<Item = &ComponentOrigin>;
fn definition_and_extensions(&self) -> (bool, IndexSet<&ExtensionId>) {
let mut extensions = IndexSet::default();
let mut has_definition = false;
for origin in self.iter_origins() {
if let Some(extension_id) = origin.extension_id() {
extensions.insert(extension_id);
} else {
has_definition = true;
}
}
(has_definition, extensions)
}
fn extensions(&self) -> IndexSet<&ExtensionId> {
self.definition_and_extensions().1
}
fn has_extension_elements(&self) -> bool {
!self.extensions().is_empty()
}
fn origin_to_use(&self) -> ComponentOrigin {
let extensions = self.extensions();
let first_extension = extensions.first();
if let Some(first_extension) = first_extension {
ComponentOrigin::Extension((*first_extension).clone())
} else {
ComponentOrigin::Definition
}
}
}
impl SchemaElement for SchemaDefinition {
fn iter_origins(&self) -> impl Iterator<Item = &ComponentOrigin> {
self.iter_origins()
}
}
impl SchemaElement for ExtendedType {
fn iter_origins(&self) -> impl Iterator<Item = &ComponentOrigin> {
self.iter_origins()
}
}